• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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/generator/structured_query_generator.h"
18 
19 #include <algorithm>
20 #include <cstddef>
21 #include <cstdint>
22 #include <cstring>
23 #include <memory>
24 #include <optional>
25 #include <string>
26 #include <utility>
27 #include <vector>
28 
29 #include "perfetto/base/logging.h"
30 #include "perfetto/base/status.h"
31 #include "perfetto/ext/base/flat_hash_map.h"
32 #include "perfetto/ext/base/status_or.h"
33 #include "perfetto/ext/base/string_utils.h"
34 #include "perfetto/protozero/field.h"
35 #include "perfetto/protozero/proto_decoder.h"
36 #include "protos/perfetto/perfetto_sql/structured_query.pbzero.h"
37 #include "src/trace_processor/util/status_macros.h"
38 
39 namespace perfetto::trace_processor::perfetto_sql::generator {
40 
41 namespace {
42 
43 using StructuredQuery = protos::pbzero::PerfettoSqlStructuredQuery;
44 
45 enum QueryType : uint8_t {
46   kRoot,
47   kShared,
48   kNested,
49 };
50 
51 struct QueryState {
QueryStateperfetto::trace_processor::perfetto_sql::generator::__anon8c2575f40111::QueryState52   QueryState(QueryType _type, protozero::ConstBytes _bytes, size_t index)
53       : type(_type), bytes(_bytes) {
54     protozero::ProtoDecoder decoder(bytes);
55     std::string prefix = type == QueryType::kShared ? "shared_sq_" : "sq_";
56     if (auto id = decoder.FindField(StructuredQuery::kIdFieldNumber); id) {
57       id_from_proto = id.as_std_string();
58       table_name = prefix + *id_from_proto;
59     } else {
60       table_name = prefix + std::to_string(index);
61     }
62   }
63 
64   QueryType type;
65   protozero::ConstBytes bytes;
66   std::optional<std::string> id_from_proto;
67   std::string table_name;
68 
69   std::string sql;
70 };
71 
72 using Query = StructuredQueryGenerator::Query;
73 using QueryProto = StructuredQueryGenerator::QueryProto;
74 
75 class GeneratorImpl {
76  public:
GeneratorImpl(base::FlatHashMap<std::string,QueryProto> & protos,std::vector<Query> & queries,base::FlatHashMap<std::string,std::nullptr_t> & modules,std::vector<std::string> & preambles)77   GeneratorImpl(base::FlatHashMap<std::string, QueryProto>& protos,
78                 std::vector<Query>& queries,
79                 base::FlatHashMap<std::string, std::nullptr_t>& modules,
80                 std::vector<std::string>& preambles)
81       : query_protos_(protos),
82         queries_(queries),
83         referenced_modules_(modules),
84         preambles_(preambles) {}
85 
86   base::StatusOr<std::string> Generate(protozero::ConstBytes);
87 
88  private:
89   using RepeatedString =
90       protozero::RepeatedFieldIterator<protozero::ConstChars>;
91   using RepeatedProto = protozero::RepeatedFieldIterator<protozero::ConstBytes>;
92 
93   base::StatusOr<std::string> GenerateImpl();
94 
95   // Base sources
96   base::StatusOr<std::string> Table(const StructuredQuery::Table::Decoder&);
97   base::StatusOr<std::string> SimpleSlices(
98       const StructuredQuery::SimpleSlices::Decoder&);
99   base::StatusOr<std::string> SqlSource(const StructuredQuery::Sql::Decoder&);
100 
101   // Nested sources
102   std::string NestedSource(protozero::ConstBytes);
103   base::StatusOr<std::string> ReferencedSharedQuery(
104       protozero::ConstChars raw_id);
105 
106   base::StatusOr<std::string> IntervalIntersect(
107       const StructuredQuery::IntervalIntersect::Decoder&);
108 
109   // Filtering.
110   static base::StatusOr<std::string> Filters(RepeatedProto filters);
111 
112   // Aggregation.
113   static base::StatusOr<std::string> GroupBy(RepeatedString group_by);
114   static base::StatusOr<std::string> SelectColumnsAggregates(
115       RepeatedString group_by,
116       RepeatedProto aggregates,
117       RepeatedProto select_cols);
118   static base::StatusOr<std::string> SelectColumnsNoAggregates(
119       RepeatedProto select_columns);
120 
121   // Helpers.
122   static base::StatusOr<std::string> OperatorToString(
123       StructuredQuery::Filter::Operator op);
124   static base::StatusOr<std::string> AggregateToString(
125       StructuredQuery::GroupBy::Aggregate::Op op,
126       protozero::ConstChars column_name);
127 
128   // Index of the current query we are processing in the `state_` vector.
129   size_t state_index_ = 0;
130   std::vector<QueryState> state_;
131   base::FlatHashMap<std::string, QueryProto>& query_protos_;
132   std::vector<Query>& queries_;
133   base::FlatHashMap<std::string, std::nullptr_t>& referenced_modules_;
134   std::vector<std::string>& preambles_;
135 };
136 
Generate(protozero::ConstBytes bytes)137 base::StatusOr<std::string> GeneratorImpl::Generate(
138     protozero::ConstBytes bytes) {
139   state_.emplace_back(QueryType::kRoot, bytes, state_.size());
140   for (; state_index_ < state_.size(); ++state_index_) {
141     base::StatusOr<std::string> sql = GenerateImpl();
142     if (!sql.ok()) {
143       return base::ErrStatus(
144           "Failed to generate SQL for query (id=%s, idx=%zu): %s",
145           state_[state_index_].id_from_proto.value_or("unknown").c_str(),
146           state_index_, sql.status().c_message());
147     }
148     state_[state_index_].sql = *sql;
149   }
150   std::string sql = "WITH ";
151   for (size_t i = 0; i < state_.size(); ++i) {
152     QueryState& state = state_[state_.size() - i - 1];
153     if (state.type == QueryType::kShared) {
154       queries_.emplace_back(
155           Query{state.id_from_proto.value(), state.table_name, state.sql});
156       continue;
157     }
158     sql += state.table_name + " AS (" + state.sql + ")";
159     if (i < state_.size() - 1) {
160       sql += ", ";
161     }
162   }
163   sql += " SELECT * FROM " + state_[0].table_name;
164   return sql;
165 }
166 
GenerateImpl()167 base::StatusOr<std::string> GeneratorImpl::GenerateImpl() {
168   StructuredQuery::Decoder q(state_[state_index_].bytes);
169 
170   // Warning: do *not* keep a reference to elemenets in `state_` across any of
171   // these functions: `state_` can be modified by them.
172   std::string source;
173   {
174     if (q.has_table()) {
175       StructuredQuery::Table::Decoder table(q.table());
176       ASSIGN_OR_RETURN(source, Table(table));
177     } else if (q.has_simple_slices()) {
178       StructuredQuery::SimpleSlices::Decoder slices(q.simple_slices());
179       ASSIGN_OR_RETURN(source, SimpleSlices(slices));
180     } else if (q.has_interval_intersect()) {
181       StructuredQuery::IntervalIntersect::Decoder ii(q.interval_intersect());
182       ASSIGN_OR_RETURN(source, IntervalIntersect(ii));
183     } else if (q.has_sql()) {
184       StructuredQuery::Sql::Decoder sql_source(q.sql());
185       ASSIGN_OR_RETURN(source, SqlSource(sql_source));
186     } else if (q.has_inner_query()) {
187       source = NestedSource(q.inner_query());
188     } else if (q.has_inner_query_id()) {
189       ASSIGN_OR_RETURN(source, ReferencedSharedQuery(q.inner_query_id()));
190     } else {
191       return base::ErrStatus("Query must specify a source");
192     }
193   }
194 
195   ASSIGN_OR_RETURN(std::string filters, Filters(q.filters()));
196 
197   std::string select;
198   std::string group_by;
199   if (q.has_group_by()) {
200     StructuredQuery::GroupBy::Decoder gb(q.group_by());
201     ASSIGN_OR_RETURN(group_by, GroupBy(gb.column_names()));
202     ASSIGN_OR_RETURN(select,
203                      SelectColumnsAggregates(gb.column_names(), gb.aggregates(),
204                                              q.select_columns()));
205   } else {
206     ASSIGN_OR_RETURN(select, SelectColumnsNoAggregates(q.select_columns()));
207   }
208 
209   std::string sql = "SELECT " + select + " FROM " + source;
210   if (!filters.empty()) {
211     sql += " WHERE " + filters;
212   }
213   if (!group_by.empty()) {
214     sql += " " + group_by;
215   }
216   return sql;
217 }
218 
Table(const StructuredQuery::Table::Decoder & table)219 base::StatusOr<std::string> GeneratorImpl::Table(
220     const StructuredQuery::Table::Decoder& table) {
221   if (table.table_name().size == 0) {
222     return base::ErrStatus("Table must specify a table name");
223   }
224   if (table.module_name().size > 0) {
225     referenced_modules_.Insert(table.module_name().ToStdString(), nullptr);
226   }
227   return table.table_name().ToStdString();
228 }
229 
SqlSource(const StructuredQuery::Sql::Decoder & sql)230 base::StatusOr<std::string> GeneratorImpl::SqlSource(
231     const StructuredQuery::Sql::Decoder& sql) {
232   if (sql.sql().size == 0) {
233     return base::ErrStatus("Sql field must be specified");
234   }
235   if (sql.column_names()->size() == 0) {
236     return base::ErrStatus("Sql must specify columns");
237   }
238 
239   if (sql.has_preamble()) {
240     preambles_.push_back(sql.preamble().ToStdString());
241   }
242 
243   std::vector<std::string> cols;
244   for (auto it = sql.column_names(); it; ++it) {
245     cols.push_back(it->as_std_string());
246   }
247   std::string join_str = base::Join(cols, ", ");
248 
249   return "(SELECT " + join_str + " FROM (" + sql.sql().ToStdString() + "))";
250 }
251 
SimpleSlices(const StructuredQuery::SimpleSlices::Decoder & slices)252 base::StatusOr<std::string> GeneratorImpl::SimpleSlices(
253     const StructuredQuery::SimpleSlices::Decoder& slices) {
254   referenced_modules_.Insert("slices.with_context", nullptr);
255 
256   std::string sql =
257       "SELECT id, ts, dur, name AS slice_name, thread_name, process_name, "
258       "track_name FROM thread_or_process_slice";
259 
260   std::vector<std::string> conditions;
261   if (slices.has_slice_name_glob()) {
262     conditions.push_back("slice_name GLOB '" +
263                          slices.slice_name_glob().ToStdString() + "'");
264   }
265   if (slices.has_thread_name_glob()) {
266     conditions.push_back("thread_name GLOB '" +
267                          slices.thread_name_glob().ToStdString() + "'");
268   }
269   if (slices.has_process_name_glob()) {
270     conditions.push_back("process_name GLOB '" +
271                          slices.process_name_glob().ToStdString() + "'");
272   }
273   if (slices.has_track_name_glob()) {
274     conditions.push_back("track_name GLOB '" +
275                          slices.track_name_glob().ToStdString() + "'");
276   }
277   if (!conditions.empty()) {
278     sql += " WHERE " + conditions[0];
279     for (size_t i = 1; i < conditions.size(); ++i) {
280       sql += " AND " + conditions[i];
281     }
282   }
283   return "(" + sql + ")";
284 }
285 
IntervalIntersect(const StructuredQuery::IntervalIntersect::Decoder & interval)286 base::StatusOr<std::string> GeneratorImpl::IntervalIntersect(
287     const StructuredQuery::IntervalIntersect::Decoder& interval) {
288   if (interval.base().size == 0) {
289     return base::ErrStatus("IntervalIntersect must specify a base query");
290   }
291   if (!interval.interval_intersect()) {
292     return base::ErrStatus(
293         "IntervalIntersect must specify at least one interval query");
294   }
295   referenced_modules_.Insert("intervals.intersect", nullptr);
296 
297   std::string sql =
298       "(WITH iibase AS (SELECT * FROM " + NestedSource(interval.base()) + ")";
299   auto ii = interval.interval_intersect();
300   for (size_t i = 0; ii; ++ii, ++i) {
301     sql += ", iisource" + std::to_string(i) + " AS (SELECT * FROM " +
302            NestedSource(*ii) + ") ";
303   }
304 
305   sql += "SELECT ii.ts, ii.dur, iibase.*";
306   ii = interval.interval_intersect();
307   for (size_t i = 0; ii; ++ii) {
308     sql += ", iisource" + std::to_string(i) + ".*";
309   }
310   sql += " FROM _interval_intersect!((iibase";
311   ii = interval.interval_intersect();
312   for (size_t i = 0; ii; ++ii) {
313     sql += ", iisource" + std::to_string(i);
314   }
315   sql += "), ()) ii JOIN iibase ON ii.id_0 = iibase.id";
316 
317   ii = interval.interval_intersect();
318   for (size_t i = 0; ii; ++ii) {
319     sql += " JOIN iisource" + std::to_string(i) + " ON ii.id_" +
320            std::to_string(i + 1) + " = iisource" + std::to_string(i) + ".id";
321   }
322   sql += ")";
323   return sql;
324 }
325 
ReferencedSharedQuery(protozero::ConstChars raw_id)326 base::StatusOr<std::string> GeneratorImpl::ReferencedSharedQuery(
327     protozero::ConstChars raw_id) {
328   std::string id = raw_id.ToStdString();
329   auto* it = query_protos_.Find(id);
330   if (!it) {
331     return base::ErrStatus("Shared query with id '%s' not found", id.c_str());
332   }
333   auto sq = std::find_if(queries_.begin(), queries_.end(),
334                          [&](const Query& sq) { return id == sq.id; });
335   if (sq != queries_.end()) {
336     return sq->table_name;
337   }
338   state_.emplace_back(QueryType::kShared,
339                       protozero::ConstBytes{it->data.get(), it->size},
340                       state_.size());
341   return state_.back().table_name;
342 }
343 
NestedSource(protozero::ConstBytes bytes)344 std::string GeneratorImpl::NestedSource(protozero::ConstBytes bytes) {
345   state_.emplace_back(QueryType::kNested, bytes, state_.size());
346   return state_.back().table_name;
347 }
348 
Filters(protozero::RepeatedFieldIterator<protozero::ConstBytes> filters)349 base::StatusOr<std::string> GeneratorImpl::Filters(
350     protozero::RepeatedFieldIterator<protozero::ConstBytes> filters) {
351   std::string sql;
352   for (auto it = filters; it; ++it) {
353     StructuredQuery::Filter::Decoder filter(*it);
354     if (!sql.empty()) {
355       sql += " AND ";
356     }
357 
358     std::string column_name = filter.column_name().ToStdString();
359     auto op = static_cast<StructuredQuery::Filter::Operator>(filter.op());
360     ASSIGN_OR_RETURN(std::string op_str, OperatorToString(op));
361 
362     if (op == StructuredQuery::Filter::Operator::IS_NULL ||
363         op == StructuredQuery::Filter::Operator::IS_NOT_NULL) {
364       sql += column_name + " " + op_str;
365       continue;
366     }
367 
368     sql += column_name + " " + op_str + " ";
369 
370     if (auto srhs = filter.string_rhs(); srhs) {
371       sql += "'" + (*srhs++).ToStdString() + "'";
372       for (; srhs; ++srhs) {
373         sql += " OR " + column_name + " " + op_str + " '" +
374                (*srhs).ToStdString() + "'";
375       }
376     } else if (auto drhs = filter.double_rhs(); drhs) {
377       sql += std::to_string((*drhs++));
378       for (; drhs; ++drhs) {
379         sql +=
380             " OR " + column_name + " " + op_str + " " + std::to_string(*drhs);
381       }
382     } else if (auto irhs = filter.int64_rhs(); irhs) {
383       sql += std::to_string(*irhs++);
384       for (; irhs; ++irhs) {
385         sql +=
386             " OR " + column_name + " " + op_str + " " + std::to_string(*irhs);
387       }
388     } else {
389       return base::ErrStatus("Filter must specify a right-hand side");
390     }
391   }
392   return sql;
393 }
394 
GroupBy(protozero::RepeatedFieldIterator<protozero::ConstChars> group_by)395 base::StatusOr<std::string> GeneratorImpl::GroupBy(
396     protozero::RepeatedFieldIterator<protozero::ConstChars> group_by) {
397   std::string sql;
398   for (auto it = group_by; it; ++it) {
399     if (sql.empty()) {
400       sql += "GROUP BY ";
401     } else {
402       sql += ", ";
403     }
404     sql += (*it).ToStdString();
405   }
406   return sql;
407 }
408 
SelectColumnsAggregates(protozero::RepeatedFieldIterator<protozero::ConstChars> group_by_cols,protozero::RepeatedFieldIterator<protozero::ConstBytes> aggregates,protozero::RepeatedFieldIterator<protozero::ConstBytes> select_cols)409 base::StatusOr<std::string> GeneratorImpl::SelectColumnsAggregates(
410     protozero::RepeatedFieldIterator<protozero::ConstChars> group_by_cols,
411     protozero::RepeatedFieldIterator<protozero::ConstBytes> aggregates,
412     protozero::RepeatedFieldIterator<protozero::ConstBytes> select_cols) {
413   base::FlatHashMap<std::string, std::optional<std::string>> output;
414   if (select_cols) {
415     for (auto it = select_cols; it; ++it) {
416       StructuredQuery::SelectColumn::Decoder select(*it);
417       std::string selected_col_name = select.column_name().ToStdString();
418       output.Insert(select.column_name().ToStdString(),
419                     select.has_alias()
420                         ? std::make_optional(select.alias().ToStdString())
421                         : std::nullopt);
422     }
423   } else {
424     for (auto it = group_by_cols; it; ++it) {
425       output.Insert((*it).ToStdString(), std::nullopt);
426     }
427     for (auto it = aggregates; it; ++it) {
428       StructuredQuery::GroupBy::Aggregate::Decoder aggregate(*it);
429       output.Insert(aggregate.result_column_name().ToStdString(), std::nullopt);
430     }
431   }
432 
433   std::string sql;
434   auto itg = group_by_cols;
435   for (; itg; ++itg) {
436     std::string column_name = (*itg).ToStdString();
437     auto* o = output.Find(column_name);
438     if (!o) {
439       continue;
440     }
441     if (!sql.empty()) {
442       sql += ", ";
443     }
444     if (o->has_value()) {
445       sql += column_name + " AS " + o->value();
446     } else {
447       sql += column_name;
448     }
449   }
450 
451   for (auto ita = aggregates; ita; ++ita) {
452     StructuredQuery::GroupBy::Aggregate::Decoder aggregate(*ita);
453     std::string res_column_name = aggregate.result_column_name().ToStdString();
454     auto* o = output.Find(res_column_name);
455     if (!o) {
456       continue;
457     }
458     if (!sql.empty()) {
459       sql += ", ";
460     }
461     ASSIGN_OR_RETURN(
462         std::string agg,
463         AggregateToString(static_cast<StructuredQuery::GroupBy::Aggregate::Op>(
464                               aggregate.op()),
465                           aggregate.column_name()));
466     if (o->has_value()) {
467       sql += agg + " AS " + o->value();
468     } else {
469       sql += agg + " AS " + res_column_name;
470     }
471   }
472   return sql;
473 }
474 
SelectColumnsNoAggregates(protozero::RepeatedFieldIterator<protozero::ConstBytes> select_columns)475 base::StatusOr<std::string> GeneratorImpl::SelectColumnsNoAggregates(
476     protozero::RepeatedFieldIterator<protozero::ConstBytes> select_columns) {
477   if (!select_columns) {
478     return std::string("*");
479   }
480   std::string sql;
481   for (auto it = select_columns; it; ++it) {
482     StructuredQuery::SelectColumn::Decoder column(*it);
483     if (!sql.empty()) {
484       sql += ", ";
485     }
486     if (column.has_alias()) {
487       sql += column.column_name().ToStdString() + " AS " +
488              column.alias().ToStdString();
489     } else {
490       sql += column.column_name().ToStdString();
491     }
492   }
493   return sql;
494 }
495 
OperatorToString(StructuredQuery::Filter::Operator op)496 base::StatusOr<std::string> GeneratorImpl::OperatorToString(
497     StructuredQuery::Filter::Operator op) {
498   switch (op) {
499     case StructuredQuery::Filter::EQUAL:
500       return std::string("=");
501     case StructuredQuery::Filter::NOT_EQUAL:
502       return std::string("!=");
503     case StructuredQuery::Filter::LESS_THAN:
504       return std::string("<");
505     case StructuredQuery::Filter::LESS_THAN_EQUAL:
506       return std::string("<=");
507     case StructuredQuery::Filter::GREATER_THAN:
508       return std::string(">");
509     case StructuredQuery::Filter::GREATER_THAN_EQUAL:
510       return std::string(">=");
511     case StructuredQuery::Filter::GLOB:
512       return std::string("GLOB");
513     case StructuredQuery::Filter::IS_NULL:
514       return std::string("IS NULL");
515     case StructuredQuery::Filter::IS_NOT_NULL:
516       return std::string("IS NOT NULL");
517     case StructuredQuery::Filter::UNKNOWN:
518       return base::ErrStatus("Invalid filter operator %d", op);
519   }
520   PERFETTO_FATAL("For GCC");
521 }
522 
AggregateToString(StructuredQuery::GroupBy::Aggregate::Op op,protozero::ConstChars raw_column_name)523 base::StatusOr<std::string> GeneratorImpl::AggregateToString(
524     StructuredQuery::GroupBy::Aggregate::Op op,
525     protozero::ConstChars raw_column_name) {
526   std::string column_name = raw_column_name.ToStdString();
527   switch (op) {
528     case StructuredQuery::GroupBy::Aggregate::COUNT:
529       return "COUNT(" + column_name + ")";
530     case StructuredQuery::GroupBy::Aggregate::SUM:
531       return "SUM(" + column_name + ")";
532     case StructuredQuery::GroupBy::Aggregate::MIN:
533       return "MIN(" + column_name + ")";
534     case StructuredQuery::GroupBy::Aggregate::MAX:
535       return "MAX(" + column_name + ")";
536     case StructuredQuery::GroupBy::Aggregate::MEAN:
537       return "AVG(" + column_name + ")";
538     case StructuredQuery::GroupBy::Aggregate::MEDIAN:
539       return "MEDIAN(" + column_name + ")";
540     case StructuredQuery::GroupBy::Aggregate::DURATION_WEIGHTED_MEAN:
541       return "SUM(cast_double!(" + column_name +
542              " * dur)) / cast_double!(SUM(dur))";
543     case StructuredQuery::GroupBy::Aggregate::UNSPECIFIED:
544       return base::ErrStatus("Invalid aggregate operator %d", op);
545   }
546   PERFETTO_FATAL("For GCC");
547 }
548 
549 }  // namespace
550 
Generate(const uint8_t * data,size_t size)551 base::StatusOr<std::string> StructuredQueryGenerator::Generate(
552     const uint8_t* data,
553     size_t size) {
554   GeneratorImpl impl(query_protos_, referenced_queries_, referenced_modules_,
555                      preambles_);
556   ASSIGN_OR_RETURN(std::string sql,
557                    impl.Generate(protozero::ConstBytes{data, size}));
558   return sql;
559 }
560 
GenerateById(const std::string & id)561 base::StatusOr<std::string> StructuredQueryGenerator::GenerateById(
562     const std::string& id) {
563   auto* ptr = query_protos_.Find(id);
564   if (!ptr) {
565     return base::ErrStatus("Query with id %s not found", id.c_str());
566   }
567   return Generate(ptr->data.get(), ptr->size);
568 }
569 
AddQuery(const uint8_t * data,size_t size)570 base::Status StructuredQueryGenerator::AddQuery(const uint8_t* data,
571                                                 size_t size) {
572   protozero::ProtoDecoder decoder(data, size);
573   auto field = decoder.FindField(
574       protos::pbzero::PerfettoSqlStructuredQuery::kIdFieldNumber);
575   if (!field) {
576     return base::ErrStatus(
577         "Unable to find id for shared query: all shared queries must have an "
578         "id specified");
579   }
580   std::string id = field.as_std_string();
581   auto ptr = std::make_unique<uint8_t[]>(size);
582   memcpy(ptr.get(), data, size);
583   auto [it, inserted] =
584       query_protos_.Insert(id, QueryProto{std::move(ptr), size});
585   if (!inserted) {
586     return base::ErrStatus("Multiple shared queries specified with the ids %s",
587                            id.c_str());
588   }
589   return base::OkStatus();
590 }
591 
ComputeReferencedModules() const592 std::vector<std::string> StructuredQueryGenerator::ComputeReferencedModules()
593     const {
594   std::vector<std::string> modules;
595   for (auto it = referenced_modules_.GetIterator(); it; ++it) {
596     modules.emplace_back(it.key());
597   }
598   return modules;
599 }
600 
601 }  // namespace perfetto::trace_processor::perfetto_sql::generator
602