1 /*
2 * Copyright (C) 2019 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/sqlite/db_sqlite_table.h"
18
19 #include "perfetto/ext/base/string_writer.h"
20 #include "src/trace_processor/sqlite/query_cache.h"
21 #include "src/trace_processor/sqlite/sqlite_utils.h"
22 #include "src/trace_processor/tp_metatrace.h"
23
24 namespace perfetto {
25 namespace trace_processor {
26
27 namespace {
28
SqliteOpToFilterOp(int sqlite_op)29 base::Optional<FilterOp> SqliteOpToFilterOp(int sqlite_op) {
30 switch (sqlite_op) {
31 case SQLITE_INDEX_CONSTRAINT_EQ:
32 case SQLITE_INDEX_CONSTRAINT_IS:
33 return FilterOp::kEq;
34 case SQLITE_INDEX_CONSTRAINT_GT:
35 return FilterOp::kGt;
36 case SQLITE_INDEX_CONSTRAINT_LT:
37 return FilterOp::kLt;
38 case SQLITE_INDEX_CONSTRAINT_ISNOT:
39 case SQLITE_INDEX_CONSTRAINT_NE:
40 return FilterOp::kNe;
41 case SQLITE_INDEX_CONSTRAINT_GE:
42 return FilterOp::kGe;
43 case SQLITE_INDEX_CONSTRAINT_LE:
44 return FilterOp::kLe;
45 case SQLITE_INDEX_CONSTRAINT_ISNULL:
46 return FilterOp::kIsNull;
47 case SQLITE_INDEX_CONSTRAINT_ISNOTNULL:
48 return FilterOp::kIsNotNull;
49 case SQLITE_INDEX_CONSTRAINT_LIKE:
50 case SQLITE_INDEX_CONSTRAINT_GLOB:
51 return base::nullopt;
52 default:
53 PERFETTO_FATAL("Currently unsupported constraint");
54 }
55 }
56
SqliteValueToSqlValue(sqlite3_value * sqlite_val)57 SqlValue SqliteValueToSqlValue(sqlite3_value* sqlite_val) {
58 auto col_type = sqlite3_value_type(sqlite_val);
59 SqlValue value;
60 switch (col_type) {
61 case SQLITE_INTEGER:
62 value.type = SqlValue::kLong;
63 value.long_value = sqlite3_value_int64(sqlite_val);
64 break;
65 case SQLITE_TEXT:
66 value.type = SqlValue::kString;
67 value.string_value =
68 reinterpret_cast<const char*>(sqlite3_value_text(sqlite_val));
69 break;
70 case SQLITE_FLOAT:
71 value.type = SqlValue::kDouble;
72 value.double_value = sqlite3_value_double(sqlite_val);
73 break;
74 case SQLITE_BLOB:
75 value.type = SqlValue::kBytes;
76 value.bytes_value = sqlite3_value_blob(sqlite_val);
77 value.bytes_count = static_cast<size_t>(sqlite3_value_bytes(sqlite_val));
78 break;
79 case SQLITE_NULL:
80 value.type = SqlValue::kNull;
81 break;
82 }
83 return value;
84 }
85
86 } // namespace
87
DbSqliteTable(sqlite3 *,Context context)88 DbSqliteTable::DbSqliteTable(sqlite3*, Context context)
89 : cache_(context.cache),
90 schema_(std::move(context.schema)),
91 computation_(context.computation),
92 static_table_(context.static_table),
93 generator_(std::move(context.generator)) {}
94 DbSqliteTable::~DbSqliteTable() = default;
95
RegisterTable(sqlite3 * db,QueryCache * cache,Table::Schema schema,const Table * table,const std::string & name)96 void DbSqliteTable::RegisterTable(sqlite3* db,
97 QueryCache* cache,
98 Table::Schema schema,
99 const Table* table,
100 const std::string& name) {
101 Context context{cache, schema, TableComputation::kStatic, table, nullptr};
102 SqliteTable::Register<DbSqliteTable, Context>(db, std::move(context), name);
103 }
104
RegisterTable(sqlite3 * db,QueryCache * cache,std::unique_ptr<DynamicTableGenerator> generator)105 void DbSqliteTable::RegisterTable(
106 sqlite3* db,
107 QueryCache* cache,
108 std::unique_ptr<DynamicTableGenerator> generator) {
109 Table::Schema schema = generator->CreateSchema();
110 std::string name = generator->TableName();
111
112 // Figure out if the table needs explicit args (in the form of constraints
113 // on hidden columns) passed to it in order to make the query valid.
114 util::Status status = generator->ValidateConstraints({});
115 bool requires_args = !status.ok();
116
117 Context context{cache, std::move(schema), TableComputation::kDynamic, nullptr,
118 std::move(generator)};
119 SqliteTable::Register<DbSqliteTable, Context>(db, std::move(context), name,
120 false, requires_args);
121 }
122
Init(int,const char * const *,Schema * schema)123 util::Status DbSqliteTable::Init(int, const char* const*, Schema* schema) {
124 *schema = ComputeSchema(schema_, name().c_str());
125 return util::OkStatus();
126 }
127
ComputeSchema(const Table::Schema & schema,const char * table_name)128 SqliteTable::Schema DbSqliteTable::ComputeSchema(const Table::Schema& schema,
129 const char* table_name) {
130 std::vector<SqliteTable::Column> schema_cols;
131 for (uint32_t i = 0; i < schema.columns.size(); ++i) {
132 const auto& col = schema.columns[i];
133 schema_cols.emplace_back(i, col.name, col.type, col.is_hidden);
134 }
135
136 // TODO(lalitm): this is hardcoded to be the id column but change this to be
137 // more generic in the future.
138 auto it = std::find_if(
139 schema.columns.begin(), schema.columns.end(),
140 [](const Table::Schema::Column& c) { return c.name == "id"; });
141 if (it == schema.columns.end()) {
142 PERFETTO_FATAL(
143 "id column not found in %s. Currently all db Tables need to contain an "
144 "id column; this constraint will be relaxed in the future.",
145 table_name);
146 }
147
148 std::vector<size_t> primary_keys;
149 primary_keys.emplace_back(std::distance(schema.columns.begin(), it));
150 return Schema(std::move(schema_cols), std::move(primary_keys));
151 }
152
BestIndex(const QueryConstraints & qc,BestIndexInfo * info)153 int DbSqliteTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
154 switch (computation_) {
155 case TableComputation::kStatic:
156 BestIndex(schema_, static_table_->row_count(), qc, info);
157 break;
158 case TableComputation::kDynamic:
159 util::Status status = generator_->ValidateConstraints(qc);
160 if (!status.ok())
161 return SQLITE_CONSTRAINT;
162 BestIndex(schema_, generator_->EstimateRowCount(), qc, info);
163 break;
164 }
165 return SQLITE_OK;
166 }
167
BestIndex(const Table::Schema & schema,uint32_t row_count,const QueryConstraints & qc,BestIndexInfo * info)168 void DbSqliteTable::BestIndex(const Table::Schema& schema,
169 uint32_t row_count,
170 const QueryConstraints& qc,
171 BestIndexInfo* info) {
172 auto cost_and_rows = EstimateCost(schema, row_count, qc);
173 info->estimated_cost = cost_and_rows.cost;
174 info->estimated_rows = cost_and_rows.rows;
175
176 const auto& cs = qc.constraints();
177 for (uint32_t i = 0; i < cs.size(); ++i) {
178 // SqliteOpToFilterOp will return nullopt for any constraint which we don't
179 // support filtering ourselves. Only omit filtering by SQLite when we can
180 // handle filtering.
181 base::Optional<FilterOp> opt_op = SqliteOpToFilterOp(cs[i].op);
182 info->sqlite_omit_constraint[i] = opt_op.has_value();
183 }
184
185 // We can sort on any column correctly.
186 info->sqlite_omit_order_by = true;
187 }
188
ModifyConstraints(QueryConstraints * qc)189 int DbSqliteTable::ModifyConstraints(QueryConstraints* qc) {
190 ModifyConstraints(schema_, qc);
191 return SQLITE_OK;
192 }
193
ModifyConstraints(const Table::Schema & schema,QueryConstraints * qc)194 void DbSqliteTable::ModifyConstraints(const Table::Schema& schema,
195 QueryConstraints* qc) {
196 using C = QueryConstraints::Constraint;
197
198 // Reorder constraints to consider the constraints on columns which are
199 // cheaper to filter first.
200 auto* cs = qc->mutable_constraints();
201 std::sort(cs->begin(), cs->end(), [&schema](const C& a, const C& b) {
202 uint32_t a_idx = static_cast<uint32_t>(a.column);
203 uint32_t b_idx = static_cast<uint32_t>(b.column);
204 const auto& a_col = schema.columns[a_idx];
205 const auto& b_col = schema.columns[b_idx];
206
207 // Id columns are always very cheap to filter on so try and get them
208 // first.
209 if (a_col.is_id && !b_col.is_id)
210 return true;
211
212 // Sorted columns are also quite cheap to filter so order them after
213 // any id columns.
214 if (a_col.is_sorted && !b_col.is_sorted)
215 return true;
216
217 // TODO(lalitm): introduce more orderings here based on empirical data.
218 return false;
219 });
220
221 // Remove any order by constraints which also have an equality constraint.
222 auto* ob = qc->mutable_order_by();
223 {
224 auto p = [&cs](const QueryConstraints::OrderBy& o) {
225 auto inner_p = [&o](const QueryConstraints::Constraint& c) {
226 return c.column == o.iColumn && sqlite_utils::IsOpEq(c.op);
227 };
228 return std::any_of(cs->begin(), cs->end(), inner_p);
229 };
230 auto remove_it = std::remove_if(ob->begin(), ob->end(), p);
231 ob->erase(remove_it, ob->end());
232 }
233
234 // Go through the order by constraints in reverse order and eliminate
235 // constraints until the first non-sorted column or the first order by in
236 // descending order.
237 {
238 auto p = [&schema](const QueryConstraints::OrderBy& o) {
239 const auto& col = schema.columns[static_cast<uint32_t>(o.iColumn)];
240 return o.desc || !col.is_sorted;
241 };
242 auto first_non_sorted_it = std::find_if(ob->rbegin(), ob->rend(), p);
243 auto pop_count = std::distance(ob->rbegin(), first_non_sorted_it);
244 ob->resize(ob->size() - static_cast<uint32_t>(pop_count));
245 }
246 }
247
EstimateCost(const Table::Schema & schema,uint32_t row_count,const QueryConstraints & qc)248 DbSqliteTable::QueryCost DbSqliteTable::EstimateCost(
249 const Table::Schema& schema,
250 uint32_t row_count,
251 const QueryConstraints& qc) {
252 // Currently our cost estimation algorithm is quite simplistic but is good
253 // enough for the simplest cases.
254 // TODO(lalitm): replace hardcoded constants with either more heuristics
255 // based on the exact type of constraint or profiling the queries themselves.
256
257 // We estimate the fixed cost of set-up and tear-down of a query in terms of
258 // the number of rows scanned.
259 constexpr double kFixedQueryCost = 1000.0;
260
261 // Setup the variables for estimating the number of rows we will have at the
262 // end of filtering. Note that |current_row_count| should always be at least 1
263 // unless we are absolutely certain that we will return no rows as otherwise
264 // SQLite can make some bad choices.
265 uint32_t current_row_count = row_count;
266
267 // If the table is empty, any constraint set only pays the fixed cost. Also we
268 // can return 0 as the row count as we are certain that we will return no
269 // rows.
270 if (current_row_count == 0)
271 return QueryCost{kFixedQueryCost, 0};
272
273 // Setup the variables for estimating the cost of filtering.
274 double filter_cost = 0.0;
275 const auto& cs = qc.constraints();
276 for (const auto& c : cs) {
277 if (current_row_count < 2)
278 break;
279 const auto& col_schema = schema.columns[static_cast<uint32_t>(c.column)];
280 if (sqlite_utils::IsOpEq(c.op) && col_schema.is_id) {
281 // If we have an id equality constraint, it's a bit expensive to find
282 // the exact row but it filters down to a single row.
283 filter_cost += 100;
284 current_row_count = 1;
285 } else if (sqlite_utils::IsOpEq(c.op)) {
286 // If there is only a single equality constraint, we have special logic
287 // to sort by that column and then binary search if we see the constraint
288 // set often. Model this by dividing by the log of the number of rows as
289 // a good approximation. Otherwise, we'll need to do a full table scan.
290 // Alternatively, if the column is sorted, we can use the same binary
291 // search logic so we have the same low cost (even better because we don't
292 // have to sort at all).
293 filter_cost += cs.size() == 1 || col_schema.is_sorted
294 ? (2 * current_row_count) / log2(current_row_count)
295 : current_row_count;
296
297 // We assume that an equalty constraint will cut down the number of rows
298 // by approximate log of the number of rows.
299 double estimated_rows = current_row_count / log2(current_row_count);
300 current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
301 } else {
302 // Otherwise, we will need to do a full table scan and we estimate we will
303 // maybe (at best) halve the number of rows.
304 filter_cost += current_row_count;
305 current_row_count = std::max(current_row_count / 2u, 1u);
306 }
307 }
308
309 // Now, to figure out the cost of sorting, multiply the final row count
310 // by |qc.order_by().size()| * log(row count). This should act as a crude
311 // estimation of the cost.
312 double sort_cost =
313 static_cast<double>(qc.order_by().size() * current_row_count) *
314 log2(current_row_count);
315
316 // The cost of iterating rows is more expensive than filtering the rows
317 // so multiply by an appropriate factor.
318 double iteration_cost = current_row_count * 2.0;
319
320 // To get the final cost, add up all the individual components.
321 double final_cost =
322 kFixedQueryCost + filter_cost + sort_cost + iteration_cost;
323 return QueryCost{final_cost, current_row_count};
324 }
325
CreateCursor()326 std::unique_ptr<SqliteTable::Cursor> DbSqliteTable::CreateCursor() {
327 return std::unique_ptr<Cursor>(new Cursor(this, cache_));
328 }
329
Cursor(DbSqliteTable * sqlite_table,QueryCache * cache)330 DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
331 : SqliteTable::Cursor(sqlite_table),
332 db_sqlite_table_(sqlite_table),
333 cache_(cache) {}
334
TryCacheCreateSortedTable(const QueryConstraints & qc,FilterHistory history)335 void DbSqliteTable::Cursor::TryCacheCreateSortedTable(
336 const QueryConstraints& qc,
337 FilterHistory history) {
338 // Check if we have a cache. Some subclasses (e.g. the flamegraph table) may
339 // pass nullptr to disable caching.
340 if (!cache_)
341 return;
342
343 if (history == FilterHistory::kDifferent) {
344 repeated_cache_count_ = 0;
345
346 // Check if the new constraint set is cached by another cursor.
347 sorted_cache_table_ =
348 cache_->GetIfCached(upstream_table_, qc.constraints());
349 return;
350 }
351
352 PERFETTO_DCHECK(history == FilterHistory::kSame);
353
354 // TODO(lalitm): all of the caching policy below should live in QueryCache and
355 // not here. This is only here temporarily to allow migration of sched without
356 // regressing UI performance and should be removed ASAP.
357
358 // Only try and create the cached table on exactly the third time we see this
359 // constraint set.
360 constexpr uint32_t kRepeatedThreshold = 3;
361 if (sorted_cache_table_ || repeated_cache_count_++ != kRepeatedThreshold)
362 return;
363
364 // If we have more than one constraint, we can't cache the table using
365 // this method.
366 if (qc.constraints().size() != 1)
367 return;
368
369 // If the constraing is not an equality constraint, there's little
370 // benefit to caching
371 const auto& c = qc.constraints().front();
372 if (!sqlite_utils::IsOpEq(c.op))
373 return;
374
375 // If the column is already sorted, we don't need to cache at all.
376 uint32_t col = static_cast<uint32_t>(c.column);
377 if (upstream_table_->GetColumn(col).IsSorted())
378 return;
379
380 // Try again to get the result or start caching it.
381 sorted_cache_table_ =
382 cache_->GetOrCache(upstream_table_, qc.constraints(), [this, col]() {
383 return upstream_table_->Sort({Order{col, false}});
384 });
385 }
386
Filter(const QueryConstraints & qc,sqlite3_value ** argv,FilterHistory history)387 int DbSqliteTable::Cursor::Filter(const QueryConstraints& qc,
388 sqlite3_value** argv,
389 FilterHistory history) {
390 PERFETTO_TP_TRACE("DB_TABLE_XFILTER", [this](metatrace::Record* r) {
391 r->AddArg("Table", db_sqlite_table_->name());
392 });
393
394 // Clear out the iterator before filtering to ensure the destructor is run
395 // before the table's destructor.
396 iterator_ = base::nullopt;
397
398 // We reuse this vector to reduce memory allocations on nested subqueries.
399 constraints_.resize(qc.constraints().size());
400 uint32_t constraints_pos = 0;
401 for (size_t i = 0; i < qc.constraints().size(); ++i) {
402 const auto& cs = qc.constraints()[i];
403 uint32_t col = static_cast<uint32_t>(cs.column);
404
405 // If we get a nullopt FilterOp, that means we should allow SQLite
406 // to handle the constraint.
407 base::Optional<FilterOp> opt_op = SqliteOpToFilterOp(cs.op);
408 if (!opt_op)
409 continue;
410
411 SqlValue value = SqliteValueToSqlValue(argv[i]);
412 constraints_[constraints_pos++] = Constraint{col, *opt_op, value};
413 }
414 constraints_.resize(constraints_pos);
415
416 // We reuse this vector to reduce memory allocations on nested subqueries.
417 orders_.resize(qc.order_by().size());
418 for (size_t i = 0; i < qc.order_by().size(); ++i) {
419 const auto& ob = qc.order_by()[i];
420 uint32_t col = static_cast<uint32_t>(ob.iColumn);
421 orders_[i] = Order{col, static_cast<bool>(ob.desc)};
422 }
423
424 // Setup the upstream table based on the computation state.
425 switch (db_sqlite_table_->computation_) {
426 case TableComputation::kStatic:
427 // If we have a static table, just set the upstream table to be the static
428 // table.
429 upstream_table_ = db_sqlite_table_->static_table_;
430
431 // Tries to create a sorted cached table which can be used to speed up
432 // filters below.
433 TryCacheCreateSortedTable(qc, history);
434 break;
435 case TableComputation::kDynamic: {
436 PERFETTO_TP_TRACE("DYNAMIC_TABLE_GENERATE", [this](metatrace::Record* r) {
437 r->AddArg("Table", db_sqlite_table_->name());
438 });
439 // If we have a dynamically created table, regenerate the table based on
440 // the new constraints.
441 dynamic_table_ =
442 db_sqlite_table_->generator_->ComputeTable(constraints_, orders_);
443 upstream_table_ = dynamic_table_.get();
444 if (!upstream_table_)
445 return SQLITE_CONSTRAINT;
446 break;
447 }
448 }
449
450 PERFETTO_TP_TRACE("DB_TABLE_FILTER_AND_SORT", [this](metatrace::Record* r) {
451 const Table* source = SourceTable();
452 char buffer[2048];
453 for (const Constraint& c : constraints_) {
454 base::StringWriter writer(buffer, sizeof(buffer));
455 writer.AppendString(source->GetColumn(c.col_idx).name());
456
457 writer.AppendChar(' ');
458 switch (c.op) {
459 case FilterOp::kEq:
460 writer.AppendString("=");
461 break;
462 case FilterOp::kGe:
463 writer.AppendString(">=");
464 break;
465 case FilterOp::kGt:
466 writer.AppendString(">");
467 break;
468 case FilterOp::kLe:
469 writer.AppendString("<=");
470 break;
471 case FilterOp::kLt:
472 writer.AppendString("<");
473 break;
474 case FilterOp::kNe:
475 writer.AppendString("!=");
476 break;
477 case FilterOp::kIsNull:
478 writer.AppendString("IS");
479 break;
480 case FilterOp::kIsNotNull:
481 writer.AppendString("IS NOT");
482 break;
483 }
484 writer.AppendChar(' ');
485
486 switch (c.value.type) {
487 case SqlValue::kString:
488 writer.AppendString(c.value.AsString());
489 break;
490 case SqlValue::kBytes:
491 writer.AppendString("<bytes>");
492 break;
493 case SqlValue::kNull:
494 writer.AppendString("<null>");
495 break;
496 case SqlValue::kDouble: {
497 writer.AppendDouble(c.value.AsDouble());
498 break;
499 }
500 case SqlValue::kLong: {
501 writer.AppendInt(c.value.AsLong());
502 break;
503 }
504 }
505 r->AddArg("Constraint", writer.GetStringView());
506 }
507
508 for (const auto& o : orders_) {
509 base::StringWriter writer(buffer, sizeof(buffer));
510 writer.AppendString(source->GetColumn(o.col_idx).name());
511 if (o.desc)
512 writer.AppendString(" desc");
513 r->AddArg("Order by", writer.GetStringView());
514 }
515 });
516
517 // Attempt to filter into a RowMap first - weall figure out whether to apply
518 // this to the table or we should use the RowMap directly. Also, if we are
519 // going to sort on the RowMap, it makes sense that we optimize for lookup
520 // speed so our sorting is not super slow.
521 RowMap::OptimizeFor optimize_for = orders_.empty()
522 ? RowMap::OptimizeFor::kMemory
523 : RowMap::OptimizeFor::kLookupSpeed;
524 RowMap filter_map = SourceTable()->FilterToRowMap(constraints_, optimize_for);
525
526 // If we have no order by constraints and it's cheap for us to use the
527 // RowMap, just use the RowMap directoy.
528 if (filter_map.IsRange() && filter_map.size() <= 1) {
529 // Currently, our criteria where we have a special fast path is if it's
530 // a single ranged row. We have tihs fast path for joins on id columns
531 // where we get repeated queries filtering down to a single row. The
532 // other path performs allocations when creating the new table as well
533 // as the iterator on the new table whereas this path only uses a single
534 // number and lives entirely on the stack.
535
536 // TODO(lalitm): investigate some other criteria where it is beneficial
537 // to have a fast path and expand to them.
538 mode_ = Mode::kSingleRow;
539 single_row_ = filter_map.size() == 1
540 ? base::make_optional(filter_map.Get(0))
541 : base::nullopt;
542 eof_ = !single_row_.has_value();
543 } else {
544 mode_ = Mode::kTable;
545
546 db_table_ = SourceTable()->Apply(std::move(filter_map));
547 if (!orders_.empty())
548 db_table_ = db_table_->Sort(orders_);
549
550 iterator_ = db_table_->IterateRows();
551
552 eof_ = !*iterator_;
553 }
554
555 return SQLITE_OK;
556 }
557
Next()558 int DbSqliteTable::Cursor::Next() {
559 if (mode_ == Mode::kSingleRow) {
560 eof_ = true;
561 } else {
562 iterator_->Next();
563 eof_ = !*iterator_;
564 }
565 return SQLITE_OK;
566 }
567
Eof()568 int DbSqliteTable::Cursor::Eof() {
569 return eof_;
570 }
571
Column(sqlite3_context * ctx,int raw_col)572 int DbSqliteTable::Cursor::Column(sqlite3_context* ctx, int raw_col) {
573 uint32_t column = static_cast<uint32_t>(raw_col);
574 SqlValue value = mode_ == Mode::kSingleRow
575 ? SourceTable()->GetColumn(column).Get(*single_row_)
576 : iterator_->Get(column);
577 switch (value.type) {
578 case SqlValue::Type::kLong:
579 sqlite3_result_int64(ctx, value.long_value);
580 break;
581 case SqlValue::Type::kDouble:
582 sqlite3_result_double(ctx, value.double_value);
583 break;
584 case SqlValue::Type::kString: {
585 // We can say kSqliteStatic here because all strings are expected to
586 // come from the string pool and thus will be valid for the lifetime
587 // of trace processor.
588 sqlite3_result_text(ctx, value.string_value, -1,
589 sqlite_utils::kSqliteStatic);
590 break;
591 }
592 case SqlValue::Type::kBytes: {
593 // We can say kSqliteStatic here because for our iterator will hold
594 // onto the pointer as long as we don't call Next() but that only
595 // happens with Next() is called on the Cursor itself at which point
596 // SQLite no longer cares about the bytes pointer.
597 sqlite3_result_blob(ctx, value.bytes_value,
598 static_cast<int>(value.bytes_count),
599 sqlite_utils::kSqliteStatic);
600 break;
601 }
602 case SqlValue::Type::kNull:
603 sqlite3_result_null(ctx);
604 break;
605 }
606 return SQLITE_OK;
607 }
608
609 DbSqliteTable::DynamicTableGenerator::~DynamicTableGenerator() = default;
610
611 } // namespace trace_processor
612 } // namespace perfetto
613