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