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 qc.order_by().size() * current_row_count * log2(current_row_count);
314
315 // The cost of iterating rows is more expensive than filtering the rows
316 // so multiply by an appropriate factor.
317 double iteration_cost = current_row_count * 2.0;
318
319 // To get the final cost, add up all the individual components.
320 double final_cost =
321 kFixedQueryCost + filter_cost + sort_cost + iteration_cost;
322 return QueryCost{final_cost, current_row_count};
323 }
324
CreateCursor()325 std::unique_ptr<SqliteTable::Cursor> DbSqliteTable::CreateCursor() {
326 return std::unique_ptr<Cursor>(new Cursor(this, cache_));
327 }
328
Cursor(DbSqliteTable * sqlite_table,QueryCache * cache)329 DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
330 : SqliteTable::Cursor(sqlite_table),
331 db_sqlite_table_(sqlite_table),
332 cache_(cache) {}
333
TryCacheCreateSortedTable(const QueryConstraints & qc,FilterHistory history)334 void DbSqliteTable::Cursor::TryCacheCreateSortedTable(
335 const QueryConstraints& qc,
336 FilterHistory history) {
337 // Check if we have a cache. Some subclasses (e.g. the flamegraph table) may
338 // pass nullptr to disable caching.
339 if (!cache_)
340 return;
341
342 if (history == FilterHistory::kDifferent) {
343 repeated_cache_count_ = 0;
344
345 // Check if the new constraint set is cached by another cursor.
346 sorted_cache_table_ =
347 cache_->GetIfCached(upstream_table_, qc.constraints());
348 return;
349 }
350
351 PERFETTO_DCHECK(history == FilterHistory::kSame);
352
353 // TODO(lalitm): all of the caching policy below should live in QueryCache and
354 // not here. This is only here temporarily to allow migration of sched without
355 // regressing UI performance and should be removed ASAP.
356
357 // Only try and create the cached table on exactly the third time we see this
358 // constraint set.
359 constexpr uint32_t kRepeatedThreshold = 3;
360 if (sorted_cache_table_ || repeated_cache_count_++ != kRepeatedThreshold)
361 return;
362
363 // If we have more than one constraint, we can't cache the table using
364 // this method.
365 if (qc.constraints().size() != 1)
366 return;
367
368 // If the constraing is not an equality constraint, there's little
369 // benefit to caching
370 const auto& c = qc.constraints().front();
371 if (!sqlite_utils::IsOpEq(c.op))
372 return;
373
374 // If the column is already sorted, we don't need to cache at all.
375 uint32_t col = static_cast<uint32_t>(c.column);
376 if (upstream_table_->GetColumn(col).IsSorted())
377 return;
378
379 // Try again to get the result or start caching it.
380 sorted_cache_table_ =
381 cache_->GetOrCache(upstream_table_, qc.constraints(), [this, col]() {
382 return upstream_table_->Sort({Order{col, false}});
383 });
384 }
385
Filter(const QueryConstraints & qc,sqlite3_value ** argv,FilterHistory history)386 int DbSqliteTable::Cursor::Filter(const QueryConstraints& qc,
387 sqlite3_value** argv,
388 FilterHistory history) {
389 PERFETTO_TP_TRACE("DB_TABLE_XFILTER", [this](metatrace::Record* r) {
390 r->AddArg("Table", db_sqlite_table_->name());
391 });
392
393 // Clear out the iterator before filtering to ensure the destructor is run
394 // before the table's destructor.
395 iterator_ = base::nullopt;
396
397 // We reuse this vector to reduce memory allocations on nested subqueries.
398 constraints_.resize(qc.constraints().size());
399 uint32_t constraints_pos = 0;
400 for (size_t i = 0; i < qc.constraints().size(); ++i) {
401 const auto& cs = qc.constraints()[i];
402 uint32_t col = static_cast<uint32_t>(cs.column);
403
404 // If we get a nullopt FilterOp, that means we should allow SQLite
405 // to handle the constraint.
406 base::Optional<FilterOp> opt_op = SqliteOpToFilterOp(cs.op);
407 if (!opt_op)
408 continue;
409
410 SqlValue value = SqliteValueToSqlValue(argv[i]);
411 constraints_[constraints_pos++] = Constraint{col, *opt_op, value};
412 }
413 constraints_.resize(constraints_pos);
414
415 // We reuse this vector to reduce memory allocations on nested subqueries.
416 orders_.resize(qc.order_by().size());
417 for (size_t i = 0; i < qc.order_by().size(); ++i) {
418 const auto& ob = qc.order_by()[i];
419 uint32_t col = static_cast<uint32_t>(ob.iColumn);
420 orders_[i] = Order{col, static_cast<bool>(ob.desc)};
421 }
422
423 // Setup the upstream table based on the computation state.
424 switch (db_sqlite_table_->computation_) {
425 case TableComputation::kStatic:
426 // If we have a static table, just set the upstream table to be the static
427 // table.
428 upstream_table_ = db_sqlite_table_->static_table_;
429
430 // Tries to create a sorted cached table which can be used to speed up
431 // filters below.
432 TryCacheCreateSortedTable(qc, history);
433 break;
434 case TableComputation::kDynamic: {
435 PERFETTO_TP_TRACE("DYNAMIC_TABLE_GENERATE", [this](metatrace::Record* r) {
436 r->AddArg("Table", db_sqlite_table_->name());
437 });
438 // If we have a dynamically created table, regenerate the table based on
439 // the new constraints.
440 dynamic_table_ =
441 db_sqlite_table_->generator_->ComputeTable(constraints_, orders_);
442 upstream_table_ = dynamic_table_.get();
443 if (!upstream_table_)
444 return SQLITE_CONSTRAINT;
445 break;
446 }
447 }
448
449 PERFETTO_TP_TRACE("DB_TABLE_FILTER_AND_SORT", [this](metatrace::Record* r) {
450 const Table* source = SourceTable();
451 char buffer[2048];
452 for (const Constraint& c : constraints_) {
453 base::StringWriter writer(buffer, sizeof(buffer));
454 writer.AppendString(source->GetColumn(c.col_idx).name());
455
456 writer.AppendChar(' ');
457 switch (c.op) {
458 case FilterOp::kEq:
459 writer.AppendString("=");
460 break;
461 case FilterOp::kGe:
462 writer.AppendString(">=");
463 break;
464 case FilterOp::kGt:
465 writer.AppendString(">");
466 break;
467 case FilterOp::kLe:
468 writer.AppendString("<=");
469 break;
470 case FilterOp::kLt:
471 writer.AppendString("<");
472 break;
473 case FilterOp::kNe:
474 writer.AppendString("!=");
475 break;
476 case FilterOp::kIsNull:
477 writer.AppendString("IS");
478 break;
479 case FilterOp::kIsNotNull:
480 writer.AppendString("IS NOT");
481 break;
482 }
483 writer.AppendChar(' ');
484
485 switch (c.value.type) {
486 case SqlValue::kString:
487 writer.AppendString(c.value.AsString());
488 break;
489 case SqlValue::kBytes:
490 writer.AppendString("<bytes>");
491 break;
492 case SqlValue::kNull:
493 writer.AppendString("<null>");
494 break;
495 case SqlValue::kDouble: {
496 writer.AppendDouble(c.value.AsDouble());
497 break;
498 }
499 case SqlValue::kLong: {
500 writer.AppendInt(c.value.AsLong());
501 break;
502 }
503 }
504 r->AddArg("Constraint", writer.GetStringView());
505 }
506
507 for (const auto& o : orders_) {
508 base::StringWriter writer(buffer, sizeof(buffer));
509 writer.AppendString(source->GetColumn(o.col_idx).name());
510 if (o.desc)
511 writer.AppendString(" desc");
512 r->AddArg("Order by", writer.GetStringView());
513 }
514 });
515
516 // Attempt to filter into a RowMap first - weall figure out whether to apply
517 // this to the table or we should use the RowMap directly. Also, if we are
518 // going to sort on the RowMap, it makes sense that we optimize for lookup
519 // speed so our sorting is not super slow.
520 RowMap::OptimizeFor optimize_for = orders_.empty()
521 ? RowMap::OptimizeFor::kMemory
522 : RowMap::OptimizeFor::kLookupSpeed;
523 RowMap filter_map = SourceTable()->FilterToRowMap(constraints_, optimize_for);
524
525 // If we have no order by constraints and it's cheap for us to use the
526 // RowMap, just use the RowMap directoy.
527 if (filter_map.IsRange() && filter_map.size() <= 1) {
528 // Currently, our criteria where we have a special fast path is if it's
529 // a single ranged row. We have tihs fast path for joins on id columns
530 // where we get repeated queries filtering down to a single row. The
531 // other path performs allocations when creating the new table as well
532 // as the iterator on the new table whereas this path only uses a single
533 // number and lives entirely on the stack.
534
535 // TODO(lalitm): investigate some other criteria where it is beneficial
536 // to have a fast path and expand to them.
537 mode_ = Mode::kSingleRow;
538 single_row_ = filter_map.size() == 1
539 ? base::make_optional(filter_map.Get(0))
540 : base::nullopt;
541 eof_ = !single_row_.has_value();
542 } else {
543 mode_ = Mode::kTable;
544
545 db_table_ = SourceTable()->Apply(std::move(filter_map));
546 if (!orders_.empty())
547 db_table_ = db_table_->Sort(orders_);
548
549 iterator_ = db_table_->IterateRows();
550
551 eof_ = !*iterator_;
552 }
553
554 return SQLITE_OK;
555 }
556
Next()557 int DbSqliteTable::Cursor::Next() {
558 if (mode_ == Mode::kSingleRow) {
559 eof_ = true;
560 } else {
561 iterator_->Next();
562 eof_ = !*iterator_;
563 }
564 return SQLITE_OK;
565 }
566
Eof()567 int DbSqliteTable::Cursor::Eof() {
568 return eof_;
569 }
570
Column(sqlite3_context * ctx,int raw_col)571 int DbSqliteTable::Cursor::Column(sqlite3_context* ctx, int raw_col) {
572 uint32_t column = static_cast<uint32_t>(raw_col);
573 SqlValue value = mode_ == Mode::kSingleRow
574 ? SourceTable()->GetColumn(column).Get(*single_row_)
575 : iterator_->Get(column);
576 switch (value.type) {
577 case SqlValue::Type::kLong:
578 sqlite3_result_int64(ctx, value.long_value);
579 break;
580 case SqlValue::Type::kDouble:
581 sqlite3_result_double(ctx, value.double_value);
582 break;
583 case SqlValue::Type::kString: {
584 // We can say kSqliteStatic here because all strings are expected to
585 // come from the string pool and thus will be valid for the lifetime
586 // of trace processor.
587 sqlite3_result_text(ctx, value.string_value, -1,
588 sqlite_utils::kSqliteStatic);
589 break;
590 }
591 case SqlValue::Type::kBytes: {
592 // We can say kSqliteStatic here because for our iterator will hold
593 // onto the pointer as long as we don't call Next() but that only
594 // happens with Next() is called on the Cursor itself at which point
595 // SQLite no longer cares about the bytes pointer.
596 sqlite3_result_blob(ctx, value.bytes_value,
597 static_cast<int>(value.bytes_count),
598 sqlite_utils::kSqliteStatic);
599 break;
600 }
601 case SqlValue::Type::kNull:
602 sqlite3_result_null(ctx);
603 break;
604 }
605 return SQLITE_OK;
606 }
607
608 DbSqliteTable::DynamicTableGenerator::~DynamicTableGenerator() = default;
609
610 } // namespace trace_processor
611 } // namespace perfetto
612