Skip to content

Commit 74b28f5

Browse files
committed
tp: read a pipeline's rows from a query
A pipeline of operators has had nowhere to get its rows from except an array built by hand, which is enough to test an operator and not enough to run one over a trace. Everything a query would want to feed it is already SQL, and stays SQL for the foreseeable future, so the way in has to be a query. This is that way in: a source which steps a prepared statement and fills batches from it. Nothing downstream learns that SQLite produced the rows, and nothing downstream has to be taught to. It is also the first source whose storage does not stand still, which is what holding a batch's values rather than a view of them was for. The column types are inferred, which is a stopgap: a bare query is all this is given, so it settles each column on its first non-null value and refuses a later value which disagrees, because by then the earlier rows have gone downstream and cannot be retyped. Once a FROM stage is built from a logical plan it will be handed the types and do as it is told.
1 parent 409d137 commit 74b28f5

6 files changed

Lines changed: 649 additions & 0 deletions

File tree

Android.bp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18771,6 +18771,22 @@ filegroup {
1877118771
],
1877218772
}
1877318773

18774+
// GN: //src/trace_processor/perfetto_sql/exec:exec
18775+
filegroup {
18776+
name: "perfetto_src_trace_processor_perfetto_sql_exec_exec",
18777+
srcs: [
18778+
"src/trace_processor/perfetto_sql/exec/sql_scan.cc",
18779+
],
18780+
}
18781+
18782+
// GN: //src/trace_processor/perfetto_sql/exec:unittests
18783+
filegroup {
18784+
name: "perfetto_src_trace_processor_perfetto_sql_exec_unittests",
18785+
srcs: [
18786+
"src/trace_processor/perfetto_sql/exec/sql_scan_unittest.cc",
18787+
],
18788+
}
18789+
1877418790
// GN: //src/trace_processor/perfetto_sql/generator:gen_cc_perfetto_sql_descriptor
1877518791
genrule {
1877618792
name: "perfetto_src_trace_processor_perfetto_sql_generator_gen_cc_perfetto_sql_descriptor",
@@ -24050,6 +24066,8 @@ cc_test {
2405024066
":perfetto_src_trace_processor_metrics_unittests",
2405124067
":perfetto_src_trace_processor_perfetto_sql_engine_engine",
2405224068
":perfetto_src_trace_processor_perfetto_sql_engine_unittests",
24069+
":perfetto_src_trace_processor_perfetto_sql_exec_exec",
24070+
":perfetto_src_trace_processor_perfetto_sql_exec_unittests",
2405324071
":perfetto_src_trace_processor_perfetto_sql_generator_generator",
2405424072
":perfetto_src_trace_processor_perfetto_sql_generator_unittests",
2405524073
":perfetto_src_trace_processor_perfetto_sql_intrinsics_types_types",

src/trace_processor/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ perfetto_unittest_source_set("unittests") {
469469
if (enable_perfetto_trace_processor_sqlite) {
470470
deps += [
471471
"perfetto_sql/engine:unittests",
472+
"perfetto_sql/exec:unittests",
472473
"perfetto_sql/parser:unittests",
473474
"perfetto_sql/tokenizer:unittests",
474475
"plugins/ancestor:unittests",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright (C) 2026 The Android Open Source Project
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import("../../../../gn/test.gni")
16+
17+
source_set("exec") {
18+
sources = [
19+
"sql_scan.cc",
20+
"sql_scan.h",
21+
]
22+
deps = [
23+
"../../../../gn:default_deps",
24+
"../../../../gn:sqlite",
25+
"../../../base",
26+
"../../containers",
27+
"../../core/common",
28+
"../../core/exec",
29+
"../../core/util",
30+
"../../sqlite",
31+
]
32+
}
33+
34+
perfetto_unittest_source_set("unittests") {
35+
testonly = true
36+
sources = [ "sql_scan_unittest.cc" ]
37+
deps = [
38+
":exec",
39+
"../../../../gn:default_deps",
40+
"../../../../gn:gtest_and_gmock",
41+
"../../../base",
42+
"../../containers",
43+
"../../core/common",
44+
"../../core/exec",
45+
"../../sqlite",
46+
]
47+
}
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/perfetto_sql/exec/sql_scan.h"
18+
19+
#include <sqlite3.h>
20+
21+
#include <cstdint>
22+
#include <memory>
23+
#include <string>
24+
#include <utility>
25+
#include <vector>
26+
27+
#include "perfetto/base/status.h"
28+
#include "perfetto/ext/base/status_macros.h"
29+
#include "perfetto/ext/base/status_or.h"
30+
#include "src/trace_processor/containers/string_pool.h"
31+
#include "src/trace_processor/core/common/storage_types.h"
32+
#include "src/trace_processor/core/exec/column_view.h"
33+
#include "src/trace_processor/core/exec/row_batch.h"
34+
#include "src/trace_processor/core/exec/row_selection.h"
35+
#include "src/trace_processor/core/util/bit_vector.h"
36+
#include "src/trace_processor/sqlite/sql_source.h"
37+
#include "src/trace_processor/sqlite/sqlite_connection.h"
38+
39+
namespace perfetto::trace_processor::exec {
40+
namespace {
41+
42+
using core::Double;
43+
using core::Int64;
44+
using core::StorageType;
45+
using core::String;
46+
using core::exec::ColumnView;
47+
using core::exec::kMaxBatchRows;
48+
using core::exec::RowBatch;
49+
using core::exec::RowSelection;
50+
51+
const char* TypeName(StorageType type) {
52+
if (type.Is<Int64>()) {
53+
return "an integer";
54+
}
55+
if (type.Is<Double>()) {
56+
return "a float";
57+
}
58+
return "a string";
59+
}
60+
61+
} // namespace
62+
63+
base::StatusOr<std::unique_ptr<SqlScan>>
64+
SqlScan::Create(SqliteConnection* connection, SqlSource sql, StringPool* pool) {
65+
SqliteConnection::PreparedStatement statement =
66+
connection->PrepareStatement(std::move(sql));
67+
RETURN_IF_ERROR(statement.status());
68+
69+
// The columns are known once the statement is prepared, before any row is
70+
// read, which is what lets a stage resolve the names it was given first.
71+
sqlite3_stmt* stmt = statement.sqlite_stmt();
72+
auto count = static_cast<uint32_t>(sqlite3_column_count(stmt));
73+
std::vector<std::string> names;
74+
names.reserve(count);
75+
for (uint32_t i = 0; i < count; ++i) {
76+
const char* name = sqlite3_column_name(stmt, static_cast<int>(i));
77+
names.emplace_back(name ? name : "");
78+
}
79+
return std::unique_ptr<SqlScan>(
80+
new SqlScan(std::move(statement), std::move(names), pool));
81+
}
82+
83+
SqlScan::SqlScan(SqliteConnection::PreparedStatement statement,
84+
std::vector<std::string> names,
85+
StringPool* pool)
86+
: statement_(std::move(statement)),
87+
names_(std::move(names)),
88+
pool_(pool),
89+
columns_(names_.size()) {
90+
for (Column& column : columns_) {
91+
column.validity = core::BitVector::CreateWithSize(kMaxBatchRows);
92+
}
93+
}
94+
95+
SqlScan::~SqlScan() = default;
96+
97+
void SqlScan::Reset() {
98+
sqlite3_reset(statement_.sqlite_stmt());
99+
done_ = false;
100+
}
101+
102+
bool SqlScan::Settle(uint32_t index, StorageType type) {
103+
Column& column = columns_[index];
104+
if (column.type) {
105+
if (*column.type == type) {
106+
return true;
107+
}
108+
status_ = base::ErrStatus(
109+
"SQL source: column '%s' holds %s after already holding %s; a "
110+
"column's type is settled by its first value and cannot change",
111+
names_[index].c_str(), TypeName(type), TypeName(*column.type));
112+
return false;
113+
}
114+
column.type = type;
115+
if (type.Is<Int64>()) {
116+
column.ints.resize(kMaxBatchRows);
117+
} else if (type.Is<Double>()) {
118+
column.doubles.resize(kMaxBatchRows);
119+
} else {
120+
column.strings.resize(kMaxBatchRows);
121+
}
122+
return true;
123+
}
124+
125+
bool SqlScan::ReadValue(sqlite3_stmt* stmt, uint32_t index, uint32_t row) {
126+
Column& column = columns_[index];
127+
auto col = static_cast<int>(index);
128+
switch (sqlite3_column_type(stmt, col)) {
129+
case SQLITE_NULL:
130+
// The validity bit was cleared when the batch started.
131+
return true;
132+
case SQLITE_INTEGER:
133+
if (!Settle(index, StorageType{Int64{}})) {
134+
return false;
135+
}
136+
column.ints[row] = sqlite3_column_int64(stmt, col);
137+
break;
138+
case SQLITE_FLOAT:
139+
if (!Settle(index, StorageType{Double{}})) {
140+
return false;
141+
}
142+
column.doubles[row] = sqlite3_column_double(stmt, col);
143+
break;
144+
case SQLITE_TEXT:
145+
if (!Settle(index, StorageType{String{}})) {
146+
return false;
147+
}
148+
column.strings[row] = pool_->InternString(
149+
reinterpret_cast<const char*>(sqlite3_column_text(stmt, col)));
150+
break;
151+
default:
152+
status_ = base::ErrStatus(
153+
"SQL source: column '%s' holds a blob, which a pipeline cannot "
154+
"carry",
155+
names_[index].c_str());
156+
return false;
157+
}
158+
column.validity.set(row);
159+
return true;
160+
}
161+
162+
ColumnView SqlScan::MakeView(const Column& column) const {
163+
const void* data = nullptr;
164+
if (column.type->Is<Int64>()) {
165+
data = column.ints.data();
166+
} else if (column.type->Is<Double>()) {
167+
data = column.doubles.data();
168+
} else {
169+
data = column.strings.data();
170+
}
171+
return ColumnView::Reference(*column.type, data, &column.validity);
172+
}
173+
174+
RowBatch* SqlScan::Next() {
175+
if (done_ || !status_.ok()) {
176+
return nullptr;
177+
}
178+
for (Column& column : columns_) {
179+
column.validity.ClearAllBits();
180+
}
181+
182+
sqlite3_stmt* stmt = statement_.sqlite_stmt();
183+
uint32_t count = 0;
184+
while (count < kMaxBatchRows && statement_.Step()) {
185+
for (uint32_t i = 0; i < columns_.size(); ++i) {
186+
if (!ReadValue(stmt, i, count)) {
187+
return nullptr;
188+
}
189+
}
190+
++count;
191+
}
192+
if (!statement_.status().ok()) {
193+
status_ = statement_.status();
194+
return nullptr;
195+
}
196+
done_ = count < kMaxBatchRows;
197+
if (count == 0) {
198+
return nullptr;
199+
}
200+
201+
if (batch_.column_count() == 0) {
202+
for (uint32_t i = 0; i < columns_.size(); ++i) {
203+
// A column which never held a value still has to be some type for
204+
// anything to read it; nothing will, because none of its rows are valid.
205+
if (!columns_[i].type) {
206+
Settle(i, StorageType{Int64{}});
207+
}
208+
pristine_.push_back(MakeView(columns_[i]));
209+
batch_.AddColumn(pristine_.back());
210+
}
211+
}
212+
213+
batch_.PrepareForFill();
214+
for (uint32_t i = 0; i < columns_.size(); ++i) {
215+
// Operators only replace a column's row view, so restoring the views is
216+
// all a reused batch needs before being filled again.
217+
batch_.mutable_column(i).AdoptSelection(pristine_[i]);
218+
}
219+
batch_.Compose(RowSelection::Range(0), count);
220+
batch_.SetCardinality(count);
221+
return &batch_;
222+
}
223+
224+
} // namespace perfetto::trace_processor::exec

0 commit comments

Comments
 (0)