Skip to content

Commit 2298a36

Browse files
committed
tp: assert that a column is of a given type
A column whose values carry their own type has to become a single type before anything typed can read it, and that claim needs checking rather than assuming. AssertType is where that happens. It checks every row against the type asked for and returns a flat column, widening a narrower integer where the conversion is exact and converting nothing else. It belongs here rather than in the source because a source cannot know what a column is for, and the operator which does is not the one reading it.
1 parent b3fdc90 commit 2298a36

5 files changed

Lines changed: 488 additions & 0 deletions

File tree

Android.bp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17599,6 +17599,7 @@ filegroup {
1759917599
filegroup {
1760017600
name: "perfetto_src_trace_processor_core_exec_exec",
1760117601
srcs: [
17602+
"src/trace_processor/core/exec/assert_type.cc",
1760217603
"src/trace_processor/core/exec/column_view.cc",
1760317604
"src/trace_processor/core/exec/dataframe_scan.cc",
1760417605
"src/trace_processor/core/exec/operator.cc",
@@ -17616,6 +17617,7 @@ filegroup {
1761617617
filegroup {
1761717618
name: "perfetto_src_trace_processor_core_exec_unittests",
1761817619
srcs: [
17620+
"src/trace_processor/core/exec/assert_type_unittest.cc",
1761917621
"src/trace_processor/core/exec/dataframe_scan_unittest.cc",
1762017622
"src/trace_processor/core/exec/operator_unittest.cc",
1762117623
"src/trace_processor/core/exec/row_store_unittest.cc",

src/trace_processor/core/exec/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import("../../../../gn/test.gni")
1616

1717
source_set("exec") {
1818
sources = [
19+
"assert_type.cc",
20+
"assert_type.h",
1921
"column_view.cc",
2022
"column_view.h",
2123
"dataframe_scan.cc",
@@ -52,6 +54,7 @@ source_set("exec") {
5254
perfetto_unittest_source_set("unittests") {
5355
testonly = true
5456
sources = [
57+
"assert_type_unittest.cc",
5558
"dataframe_scan_unittest.cc",
5659
"operator_unittest.cc",
5760
"row_store_unittest.cc",
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Copyright (C) 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "src/trace_processor/core/exec/assert_type.h"
18+
19+
#include <cmath>
20+
#include <cstdint>
21+
#include <memory>
22+
#include <string>
23+
#include <utility>
24+
25+
#include "perfetto/base/status.h"
26+
#include "src/trace_processor/containers/string_pool.h"
27+
#include "src/trace_processor/core/common/storage_types.h"
28+
#include "src/trace_processor/core/exec/column_view.h"
29+
#include "src/trace_processor/core/exec/operator.h"
30+
#include "src/trace_processor/core/exec/row_batch.h"
31+
#include "src/trace_processor/core/exec/row_selection.h"
32+
#include "src/trace_processor/core/exec/variant.h"
33+
#include "src/trace_processor/core/util/flex_vector.h"
34+
35+
namespace perfetto::trace_processor::core::exec {
36+
namespace {
37+
38+
const char* Name(Variant::Type type) {
39+
switch (type) {
40+
case Variant::Type::kNull:
41+
return "a null";
42+
case Variant::Type::kInt64:
43+
return "an integer";
44+
case Variant::Type::kDouble:
45+
return "a float";
46+
case Variant::Type::kString:
47+
return "a string";
48+
}
49+
return "something";
50+
}
51+
52+
const char* Name(StorageType type) {
53+
if (type.Is<Int64>()) {
54+
return "an integer";
55+
}
56+
return type.Is<Double>() ? "a float" : "a string";
57+
}
58+
59+
// `resolve` is a template parameter so the loop carries no per-row branch on
60+
// how a row is reached.
61+
template <typename Resolve, typename Write>
62+
bool Walk(Resolve resolve, uint32_t count, Write write) {
63+
for (uint32_t i = 0; i < count; ++i) {
64+
if (!write(i, resolve(i))) {
65+
return false;
66+
}
67+
}
68+
return true;
69+
}
70+
71+
// Reads `count` values of a narrower integer column into `values.ints`.
72+
template <typename T>
73+
void WidenAs(const ColumnView& column,
74+
uint32_t count,
75+
FlexVector<int64_t>& out) {
76+
const auto* data = static_cast<const T*>(column.data());
77+
RowSelection selection = column.selection();
78+
int64_t* dest = out.data();
79+
if (selection.is_range()) {
80+
const T* from = data + selection.offset();
81+
for (uint32_t i = 0; i < count; ++i) {
82+
dest[i] = from[i];
83+
}
84+
return;
85+
}
86+
const uint32_t* rows = selection.data();
87+
for (uint32_t i = 0; i < count; ++i) {
88+
dest[i] = data[rows[i]];
89+
}
90+
}
91+
92+
} // namespace
93+
94+
AssertType::AssertType(uint32_t column, StorageType type, std::string name)
95+
: column_(column), type_(type), name_(std::move(name)) {}
96+
97+
AssertType::~AssertType() = default;
98+
99+
void AssertType::Widen(const ColumnView& column,
100+
uint32_t count,
101+
Values& values) const {
102+
if (column.type().Is<Uint32>()) {
103+
WidenAs<uint32_t>(column, count, values.ints);
104+
} else {
105+
WidenAs<int32_t>(column, count, values.ints);
106+
}
107+
}
108+
AssertType::State::~State() = default;
109+
110+
std::unique_ptr<OperatorState> AssertType::MakeState() const {
111+
auto state = std::make_unique<State>();
112+
if (type_.Is<Int64>()) {
113+
state->values->ints.resize(kMaxBatchRows);
114+
} else if (type_.Is<Double>()) {
115+
state->values->doubles.resize(kMaxBatchRows);
116+
} else {
117+
state->values->strings.resize(kMaxBatchRows);
118+
}
119+
state->values->validity = BitVector::CreateWithSize(kMaxBatchRows);
120+
return state;
121+
}
122+
123+
base::Status AssertType::status(const OperatorState& state) const {
124+
return state.Cast<const State>().status;
125+
}
126+
127+
OpResult AssertType::Execute(const RowBatch& in,
128+
RowBatch& out,
129+
OperatorState& state) const {
130+
State& s = state.Cast<State>();
131+
out.CopyFrom(in);
132+
const ColumnView& column = in.column(column_);
133+
if (column.kind() != ColumnView::Kind::kVariant) {
134+
if (column.type() == type_) {
135+
return OpResult::kNeedMoreInput;
136+
}
137+
// Widening a narrower integer to Int64 always loses nothing.
138+
if (type_.Is<Int64>() &&
139+
(column.type().Is<Uint32>() || column.type().Is<Int32>())) {
140+
Widen(column, in.size(), *s.values);
141+
out.SetColumn(column_,
142+
ColumnView::Reference(type_, s.values->ints.data(),
143+
column.validity()),
144+
s.values);
145+
return OpResult::kNeedMoreInput;
146+
}
147+
s.status = base::ErrStatus("column '%s' is %s, not %s", name_.c_str(),
148+
Name(column.type()), Name(type_));
149+
return OpResult::kError;
150+
}
151+
152+
uint32_t count = in.size();
153+
const auto* cells = static_cast<const Variant*>(column.data());
154+
Values& values = *s.values;
155+
values.validity.ClearAllBits();
156+
auto write = [&](uint32_t row, const Variant& cell) {
157+
if (cell.type == Variant::Type::kNull) {
158+
// Written even for a null row, so nothing downstream reads an
159+
// uninitialised slot.
160+
if (type_.Is<Int64>()) {
161+
values.ints[row] = 0;
162+
} else if (type_.Is<Double>()) {
163+
values.doubles[row] = 0;
164+
} else {
165+
values.strings[row] = StringPool::Id::Null();
166+
}
167+
return true;
168+
}
169+
if (type_.Is<Int64>() && cell.type == Variant::Type::kInt64) {
170+
values.ints[row] = cell.AsInt64();
171+
} else if (type_.Is<Double>() && cell.type == Variant::Type::kDouble) {
172+
values.doubles[row] = cell.AsDouble();
173+
} else if (type_.Is<Double>() && cell.type == Variant::Type::kInt64) {
174+
// Only convert where the widening is exact.
175+
int64_t value = cell.AsInt64();
176+
auto widened = static_cast<double>(value);
177+
if (static_cast<int64_t>(widened) != value) {
178+
s.status = base::ErrStatus(
179+
"column '%s' holds an integer too large to be a float",
180+
name_.c_str());
181+
return false;
182+
}
183+
values.doubles[row] = widened;
184+
} else if (type_.Is<String>() && cell.type == Variant::Type::kString) {
185+
values.strings[row] = cell.AsString();
186+
} else {
187+
s.status = base::ErrStatus("column '%s' holds %s, not %s", name_.c_str(),
188+
Name(cell.type), Name(type_));
189+
return false;
190+
}
191+
values.validity.set(row);
192+
return true;
193+
};
194+
195+
RowSelection selection = column.selection();
196+
bool ok;
197+
if (selection.is_range()) {
198+
const Variant* from = cells + selection.offset();
199+
ok = Walk([from](uint32_t i) -> const Variant& { return from[i]; }, count,
200+
write);
201+
} else {
202+
const uint32_t* rows = selection.data();
203+
ok = Walk(
204+
[cells, rows](uint32_t i) -> const Variant& { return cells[rows[i]]; },
205+
count, write);
206+
}
207+
if (!ok) {
208+
return OpResult::kError;
209+
}
210+
211+
const void* data = nullptr;
212+
if (type_.Is<Int64>()) {
213+
data = values.ints.data();
214+
} else if (type_.Is<Double>()) {
215+
data = values.doubles.data();
216+
} else {
217+
data = values.strings.data();
218+
}
219+
out.SetColumn(column_, ColumnView::Reference(type_, data, &values.validity),
220+
s.values);
221+
return OpResult::kNeedMoreInput;
222+
}
223+
224+
} // namespace perfetto::trace_processor::core::exec
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (C) 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef SRC_TRACE_PROCESSOR_CORE_EXEC_ASSERT_TYPE_H_
18+
#define SRC_TRACE_PROCESSOR_CORE_EXEC_ASSERT_TYPE_H_
19+
20+
#include <cstdint>
21+
#include <memory>
22+
#include <string>
23+
24+
#include "perfetto/base/status.h"
25+
#include "src/trace_processor/containers/string_pool.h"
26+
#include "src/trace_processor/core/common/storage_types.h"
27+
#include "src/trace_processor/core/exec/column_view.h"
28+
#include "src/trace_processor/core/exec/operator.h"
29+
#include "src/trace_processor/core/exec/row_batch.h"
30+
#include "src/trace_processor/core/util/bit_vector.h"
31+
#include "src/trace_processor/core/util/flex_vector.h"
32+
33+
namespace perfetto::trace_processor::core::exec {
34+
35+
// Converts a column whose values carry their own type into a column of a
36+
// single type, failing on any row which disagrees.
37+
//
38+
// A source reading SQLite cannot promise a column's type, because a declared
39+
// type in SQLite is not binding: an INTEGER column holds text if something
40+
// puts text in it. So anything downstream which needs a typed column has to
41+
// come through here. An integer widens to a float where the conversion is
42+
// exact; nothing else converts.
43+
class AssertType : public Operator {
44+
public:
45+
AssertType(uint32_t column, StorageType type, std::string name);
46+
~AssertType() override;
47+
48+
std::unique_ptr<OperatorState> MakeState() const override;
49+
OpResult Execute(const RowBatch& in,
50+
RowBatch& out,
51+
OperatorState&) const override;
52+
base::Status status(const OperatorState&) const override;
53+
54+
private:
55+
struct Values {
56+
FlexVector<int64_t> ints;
57+
FlexVector<double> doubles;
58+
FlexVector<StringPool::Id> strings;
59+
BitVector validity;
60+
};
61+
struct State : OperatorState {
62+
~State() override;
63+
std::shared_ptr<Values> values = std::make_shared<Values>();
64+
base::Status status = base::OkStatus();
65+
};
66+
67+
void Widen(const ColumnView&, uint32_t count, Values&) const;
68+
69+
uint32_t column_;
70+
StorageType type_;
71+
// Used in the error message when a row disagrees.
72+
std::string name_;
73+
};
74+
75+
} // namespace perfetto::trace_processor::core::exec
76+
77+
#endif // SRC_TRACE_PROCESSOR_CORE_EXEC_ASSERT_TYPE_H_

0 commit comments

Comments
 (0)