xref: /aosp_15_r20/external/perfetto/src/trace_processor/perfetto_sql/parser/perfetto_sql_parser.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2023 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/parser/perfetto_sql_parser.h"
18 
19 #include <algorithm>
20 #include <cctype>
21 #include <functional>
22 #include <optional>
23 #include <string>
24 #include <string_view>
25 #include <utility>
26 #include <vector>
27 
28 #include "perfetto/base/logging.h"
29 #include "perfetto/base/status.h"
30 #include "perfetto/ext/base/flat_hash_map.h"
31 #include "perfetto/ext/base/string_utils.h"
32 #include "src/trace_processor/perfetto_sql/parser/function_util.h"
33 #include "src/trace_processor/perfetto_sql/preprocessor/perfetto_sql_preprocessor.h"
34 #include "src/trace_processor/perfetto_sql/tokenizer/sqlite_tokenizer.h"
35 #include "src/trace_processor/sqlite/sql_source.h"
36 #include "src/trace_processor/util/sql_argument.h"
37 
38 namespace perfetto {
39 namespace trace_processor {
40 namespace {
41 
42 using Token = SqliteTokenizer::Token;
43 using Statement = PerfettoSqlParser::Statement;
44 
45 enum class State {
46   kDrop,
47   kDropPerfetto,
48   kCreate,
49   kCreateOr,
50   kCreateOrReplace,
51   kCreateOrReplacePerfetto,
52   kCreatePerfetto,
53   kInclude,
54   kIncludePerfetto,
55   kPassthrough,
56   kStmtStart,
57 };
58 
IsValidModuleWord(const std::string & word)59 bool IsValidModuleWord(const std::string& word) {
60   for (const char& c : word) {
61     if (!std::isalnum(c) && (c != '_') && !std::islower(c)) {
62       return false;
63     }
64   }
65   return true;
66 }
67 
ValidateModuleName(const std::string & name)68 bool ValidateModuleName(const std::string& name) {
69   if (name.empty()) {
70     return false;
71   }
72 
73   std::vector<std::string> packages = base::SplitString(name, ".");
74 
75   // The last part of the path can be a wildcard.
76   if (!packages.empty() && packages.back() == "*") {
77     packages.pop_back();
78   }
79 
80   // The rest of the path must be valid words.
81   return std::find_if(packages.begin(), packages.end(),
82                       std::not_fn(IsValidModuleWord)) == packages.end();
83 }
84 
85 }  // namespace
86 
PerfettoSqlParser(SqlSource source,const base::FlatHashMap<std::string,PerfettoSqlPreprocessor::Macro> & macros)87 PerfettoSqlParser::PerfettoSqlParser(
88     SqlSource source,
89     const base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro>&
90         macros)
91     : preprocessor_(std::move(source), macros),
92       tokenizer_(SqlSource::FromTraceProcessorImplementation("")) {}
93 
Next()94 bool PerfettoSqlParser::Next() {
95   PERFETTO_CHECK(status_.ok());
96 
97   if (!preprocessor_.NextStatement()) {
98     status_ = preprocessor_.status();
99     return false;
100   }
101   tokenizer_.Reset(preprocessor_.statement());
102 
103   State state = State::kStmtStart;
104   std::optional<Token> first_non_space_token;
105   for (Token token = tokenizer_.Next();; token = tokenizer_.Next()) {
106     // Space should always be completely ignored by any logic below as it will
107     // never change the current state in the state machine.
108     if (token.token_type == TK_SPACE) {
109       continue;
110     }
111 
112     if (token.IsTerminal()) {
113       // If we have a non-space character we've seen, just return all the stuff
114       // after that point.
115       if (first_non_space_token) {
116         statement_ = SqliteSql{};
117         statement_sql_ = tokenizer_.Substr(*first_non_space_token, token);
118         return true;
119       }
120       // This means we've seen a semi-colon without any non-space content. Just
121       // try and find the next statement as this "statement" is a noop.
122       if (token.token_type == TK_SEMI) {
123         continue;
124       }
125       // This means we've reached the end of the SQL.
126       PERFETTO_DCHECK(token.str.empty());
127       return false;
128     }
129 
130     // If we've not seen a space character, keep track of the current position.
131     if (!first_non_space_token) {
132       first_non_space_token = token;
133     }
134 
135     switch (state) {
136       case State::kPassthrough:
137         statement_ = SqliteSql{};
138         statement_sql_ = preprocessor_.statement();
139         return true;
140       case State::kStmtStart:
141         if (token.token_type == TK_CREATE) {
142           state = State::kCreate;
143         } else if (token.token_type == TK_INCLUDE) {
144           state = State::kInclude;
145         } else if (token.token_type == TK_DROP) {
146           state = State::kDrop;
147         } else {
148           state = State::kPassthrough;
149         }
150         break;
151       case State::kInclude:
152         if (token.token_type == TK_PERFETTO) {
153           state = State::kIncludePerfetto;
154         } else {
155           return ErrorAtToken(token,
156                               "Use 'INCLUDE PERFETTO MODULE {include_key}'.");
157         }
158         break;
159       case State::kIncludePerfetto:
160         if (token.token_type == TK_MODULE) {
161           return ParseIncludePerfettoModule(*first_non_space_token);
162         } else {
163           return ErrorAtToken(token,
164                               "Use 'INCLUDE PERFETTO MODULE {include_key}'.");
165         }
166       case State::kDrop:
167         if (token.token_type == TK_PERFETTO) {
168           state = State::kDropPerfetto;
169         } else {
170           state = State::kPassthrough;
171         }
172         break;
173       case State::kDropPerfetto:
174         if (token.token_type == TK_INDEX) {
175           return ParseDropPerfettoIndex(*first_non_space_token);
176         } else {
177           return ErrorAtToken(token, "Only Perfetto index can be dropped");
178         }
179       case State::kCreate:
180         if (token.token_type == TK_TRIGGER) {
181           // TODO(lalitm): add this to the "errors" documentation page
182           // explaining why this is the case.
183           return ErrorAtToken(
184               token, "Creating triggers is not supported in PerfettoSQL.");
185         }
186         if (token.token_type == TK_PERFETTO) {
187           state = State::kCreatePerfetto;
188         } else if (token.token_type == TK_OR) {
189           state = State::kCreateOr;
190         } else {
191           state = State::kPassthrough;
192         }
193         break;
194       case State::kCreateOr:
195         state = token.token_type == TK_REPLACE ? State::kCreateOrReplace
196                                                : State::kPassthrough;
197         break;
198       case State::kCreateOrReplace:
199         state = token.token_type == TK_PERFETTO
200                     ? State::kCreateOrReplacePerfetto
201                     : State::kPassthrough;
202         break;
203       case State::kCreateOrReplacePerfetto:
204       case State::kCreatePerfetto:
205         bool replace = state == State::kCreateOrReplacePerfetto;
206         if (token.token_type == TK_FUNCTION) {
207           return ParseCreatePerfettoFunction(replace, *first_non_space_token);
208         }
209         if (token.token_type == TK_TABLE) {
210           return ParseCreatePerfettoTableOrView(replace, *first_non_space_token,
211                                                 TableOrView::kTable);
212         }
213         if (token.token_type == TK_VIEW) {
214           return ParseCreatePerfettoTableOrView(replace, *first_non_space_token,
215                                                 TableOrView::kView);
216         }
217         if (token.token_type == TK_MACRO) {
218           return ParseCreatePerfettoMacro(replace);
219         }
220         if (token.token_type == TK_INDEX) {
221           return ParseCreatePerfettoIndex(replace, *first_non_space_token);
222         }
223         base::StackString<1024> err(
224             "Expected 'FUNCTION', 'TABLE', 'MACRO' OR 'INDEX' after 'CREATE "
225             "PERFETTO', received '%*s'.",
226             static_cast<int>(token.str.size()), token.str.data());
227         return ErrorAtToken(token, err.c_str());
228     }
229   }
230 }
231 
ParseIncludePerfettoModule(Token first_non_space_token)232 bool PerfettoSqlParser::ParseIncludePerfettoModule(
233     Token first_non_space_token) {
234   auto tok = tokenizer_.NextNonWhitespace();
235   auto terminal = tokenizer_.NextTerminal();
236   std::string key = tokenizer_.Substr(tok, terminal).sql();
237 
238   if (!ValidateModuleName(key)) {
239     base::StackString<1024> err(
240         "Include key should be a dot-separated list of module names, with the "
241         "last name optionally being a wildcard: '%s'",
242         key.c_str());
243     return ErrorAtToken(tok, err.c_str());
244   }
245 
246   statement_ = Include{key};
247   statement_sql_ = tokenizer_.Substr(first_non_space_token, terminal);
248   return true;
249 }
250 
ParseCreatePerfettoTableOrView(bool replace,Token first_non_space_token,TableOrView table_or_view)251 bool PerfettoSqlParser::ParseCreatePerfettoTableOrView(
252     bool replace,
253     Token first_non_space_token,
254     TableOrView table_or_view) {
255   Token table_name = tokenizer_.NextNonWhitespace();
256   if (table_name.token_type != TK_ID) {
257     base::StackString<1024> err("Invalid table name %.*s",
258                                 static_cast<int>(table_name.str.size()),
259                                 table_name.str.data());
260     return ErrorAtToken(table_name, err.c_str());
261   }
262   std::string name(table_name.str);
263   std::vector<sql_argument::ArgumentDefinition> schema;
264 
265   auto token = tokenizer_.NextNonWhitespace();
266 
267   // If the next token is a left parenthesis, then the table or view have a
268   // schema.
269   if (token.token_type == TK_LP) {
270     if (!ParseArguments(schema)) {
271       return false;
272     }
273     token = tokenizer_.NextNonWhitespace();
274   }
275 
276   if (token.token_type != TK_AS) {
277     base::StackString<1024> err(
278         "Expected 'AS' after table_name, received "
279         "%*s.",
280         static_cast<int>(token.str.size()), token.str.data());
281     return ErrorAtToken(token, err.c_str());
282   }
283 
284   Token first = tokenizer_.NextNonWhitespace();
285   Token terminal = tokenizer_.NextTerminal();
286   switch (table_or_view) {
287     case TableOrView::kTable:
288       statement_ = CreateTable{replace, std::move(name),
289                                tokenizer_.Substr(first, terminal), schema};
290       break;
291     case TableOrView::kView:
292       SqlSource original_statement =
293           tokenizer_.Substr(first_non_space_token, terminal);
294       SqlSource header = SqlSource::FromTraceProcessorImplementation(
295           "CREATE VIEW " + name + " AS ");
296       SqlSource::Rewriter rewriter(original_statement);
297       tokenizer_.Rewrite(rewriter, first_non_space_token, first, header,
298                          SqliteTokenizer::EndToken::kExclusive);
299       statement_ = CreateView{replace, std::move(name),
300                               tokenizer_.Substr(first, terminal),
301                               std::move(rewriter).Build(), schema};
302       break;
303   }
304   statement_sql_ = tokenizer_.Substr(first_non_space_token, terminal);
305   return true;
306 }
307 
ParseCreatePerfettoIndex(bool replace,Token first_non_space_token)308 bool PerfettoSqlParser::ParseCreatePerfettoIndex(bool replace,
309                                                  Token first_non_space_token) {
310   Token index_name_tok = tokenizer_.NextNonWhitespace();
311   if (index_name_tok.token_type != TK_ID) {
312     base::StackString<1024> err("Invalid index name %.*s",
313                                 static_cast<int>(index_name_tok.str.size()),
314                                 index_name_tok.str.data());
315     return ErrorAtToken(index_name_tok, err.c_str());
316   }
317   std::string index_name(index_name_tok.str);
318 
319   auto token = tokenizer_.NextNonWhitespace();
320   if (token.token_type != TK_ON) {
321     base::StackString<1024> err("Expected 'ON' after index name, received %*s.",
322                                 static_cast<int>(token.str.size()),
323                                 token.str.data());
324     return ErrorAtToken(token, err.c_str());
325   }
326 
327   Token table_name_tok = tokenizer_.NextNonWhitespace();
328   if (table_name_tok.token_type != TK_ID) {
329     base::StackString<1024> err("Invalid table name %.*s",
330                                 static_cast<int>(table_name_tok.str.size()),
331                                 table_name_tok.str.data());
332     return ErrorAtToken(table_name_tok, err.c_str());
333   }
334   std::string table_name(table_name_tok.str);
335 
336   token = tokenizer_.NextNonWhitespace();
337   if (token.token_type != TK_LP) {
338     base::StackString<1024> err(
339         "Expected parenthesis after table name, received '%*s'.",
340         static_cast<int>(token.str.size()), token.str.data());
341     return ErrorAtToken(token, err.c_str());
342   }
343 
344   std::vector<std::string> cols;
345 
346   do {
347     Token col_name_tok = tokenizer_.NextNonWhitespace();
348     cols.push_back(std::string(col_name_tok.str));
349     token = tokenizer_.NextNonWhitespace();
350   } while (token.token_type == TK_COMMA);
351 
352   if (token.token_type != TK_RP) {
353     base::StackString<1024> err("Expected closed parenthesis, received '%*s'.",
354                                 static_cast<int>(token.str.size()),
355                                 token.str.data());
356     return ErrorAtToken(token, err.c_str());
357   }
358 
359   token = tokenizer_.NextNonWhitespace();
360   if (!token.IsTerminal()) {
361     return ErrorAtToken(
362         token,
363         "Expected semicolon after columns list in CREATE PERFETTO INDEX.");
364   }
365 
366   statement_sql_ = tokenizer_.Substr(first_non_space_token, token);
367   statement_ = CreateIndex{replace, index_name, table_name, cols};
368   return true;
369 }
370 
ParseDropPerfettoIndex(SqliteTokenizer::Token first_non_space_token)371 bool PerfettoSqlParser::ParseDropPerfettoIndex(
372     SqliteTokenizer::Token first_non_space_token) {
373   Token index_name_tok = tokenizer_.NextNonWhitespace();
374   if (index_name_tok.token_type != TK_ID) {
375     base::StackString<1024> err("Invalid index name %.*s",
376                                 static_cast<int>(index_name_tok.str.size()),
377                                 index_name_tok.str.data());
378     return ErrorAtToken(index_name_tok, err.c_str());
379   }
380   std::string index_name(index_name_tok.str);
381 
382   auto token = tokenizer_.NextNonWhitespace();
383   if (token.token_type != TK_ON) {
384     base::StackString<1024> err("Expected 'ON' after index name, received %*s.",
385                                 static_cast<int>(token.str.size()),
386                                 token.str.data());
387     return ErrorAtToken(token, err.c_str());
388   }
389 
390   Token table_name_tok = tokenizer_.NextNonWhitespace();
391   if (table_name_tok.token_type != TK_ID) {
392     base::StackString<1024> err("Invalid table name %.*s",
393                                 static_cast<int>(table_name_tok.str.size()),
394                                 table_name_tok.str.data());
395     return ErrorAtToken(table_name_tok, err.c_str());
396   }
397   std::string table_name(table_name_tok.str);
398 
399   token = tokenizer_.NextNonWhitespace();
400   if (!token.IsTerminal()) {
401     return ErrorAtToken(
402         token, "Nothing is allowed after table name in DROP PERFETTO INDEX");
403   }
404   statement_sql_ = tokenizer_.Substr(first_non_space_token, token);
405   statement_ = DropIndex{index_name, table_name};
406   return true;
407 }
408 
ParseCreatePerfettoFunction(bool replace,Token first_non_space_token)409 bool PerfettoSqlParser::ParseCreatePerfettoFunction(
410     bool replace,
411     Token first_non_space_token) {
412   Token function_name = tokenizer_.NextNonWhitespace();
413   if (function_name.token_type != TK_ID) {
414     // TODO(lalitm): add a link to create function documentation.
415     base::StackString<1024> err("Invalid function name %.*s",
416                                 static_cast<int>(function_name.str.size()),
417                                 function_name.str.data());
418     return ErrorAtToken(function_name, err.c_str());
419   }
420 
421   // TK_LP == '(' (i.e. left parenthesis).
422   if (Token lp = tokenizer_.NextNonWhitespace(); lp.token_type != TK_LP) {
423     // TODO(lalitm): add a link to create function documentation.
424     return ErrorAtToken(lp, "Malformed function prototype: '(' expected");
425   }
426 
427   std::vector<sql_argument::ArgumentDefinition> args;
428   if (!ParseArguments(args)) {
429     return false;
430   }
431 
432   if (Token returns = tokenizer_.NextNonWhitespace();
433       returns.token_type != TK_RETURNS) {
434     // TODO(lalitm): add a link to create function documentation.
435     return ErrorAtToken(returns, "Expected keyword 'returns'");
436   }
437 
438   Token ret_token = tokenizer_.NextNonWhitespace();
439   std::string ret;
440   bool table_return = ret_token.token_type == TK_TABLE;
441   if (table_return) {
442     if (Token lp = tokenizer_.NextNonWhitespace(); lp.token_type != TK_LP) {
443       // TODO(lalitm): add a link to create function documentation.
444       return ErrorAtToken(lp, "Malformed table return: '(' expected");
445     }
446     // Table function return.
447     std::vector<sql_argument::ArgumentDefinition> ret_args;
448     if (!ParseArguments(ret_args)) {
449       return false;
450     }
451     ret = sql_argument::SerializeArguments(ret_args);
452   } else if (ret_token.token_type != TK_ID) {
453     // TODO(lalitm): add a link to create function documentation.
454     return ErrorAtToken(ret_token, "Invalid return type");
455   } else {
456     // Scalar function return.
457     ret = ret_token.str;
458   }
459 
460   if (Token as_token = tokenizer_.NextNonWhitespace();
461       as_token.token_type != TK_AS) {
462     // TODO(lalitm): add a link to create function documentation.
463     return ErrorAtToken(as_token, "Expected keyword 'as'");
464   }
465 
466   Token first = tokenizer_.NextNonWhitespace();
467   Token terminal = tokenizer_.NextTerminal();
468   statement_ = CreateFunction{
469       replace,
470       FunctionPrototype{std::string(function_name.str), std::move(args)},
471       std::move(ret), tokenizer_.Substr(first, terminal), table_return};
472   statement_sql_ = tokenizer_.Substr(first_non_space_token, terminal);
473   return true;
474 }
475 
ParseCreatePerfettoMacro(bool replace)476 bool PerfettoSqlParser::ParseCreatePerfettoMacro(bool replace) {
477   Token name = tokenizer_.NextNonWhitespace();
478   if (name.token_type != TK_ID) {
479     // TODO(lalitm): add a link to create macro documentation.
480     base::StackString<1024> err("Invalid macro name %.*s",
481                                 static_cast<int>(name.str.size()),
482                                 name.str.data());
483     return ErrorAtToken(name, err.c_str());
484   }
485 
486   // TK_LP == '(' (i.e. left parenthesis).
487   if (Token lp = tokenizer_.NextNonWhitespace(); lp.token_type != TK_LP) {
488     // TODO(lalitm): add a link to create macro documentation.
489     return ErrorAtToken(lp, "Malformed macro prototype: '(' expected");
490   }
491 
492   std::vector<RawArgument> raw_args;
493   std::vector<std::pair<SqlSource, SqlSource>> args;
494   if (!ParseRawArguments(raw_args)) {
495     return false;
496   }
497   for (const auto& arg : raw_args) {
498     args.emplace_back(tokenizer_.SubstrToken(arg.name),
499                       tokenizer_.SubstrToken(arg.type));
500   }
501 
502   if (Token returns = tokenizer_.NextNonWhitespace();
503       returns.token_type != TK_RETURNS) {
504     // TODO(lalitm): add a link to create macro documentation.
505     return ErrorAtToken(returns, "Expected keyword 'returns'");
506   }
507 
508   Token returns_value = tokenizer_.NextNonWhitespace();
509   if (returns_value.token_type != TK_ID) {
510     // TODO(lalitm): add a link to create function documentation.
511     return ErrorAtToken(returns_value, "Expected return type");
512   }
513 
514   if (Token as_token = tokenizer_.NextNonWhitespace();
515       as_token.token_type != TK_AS) {
516     // TODO(lalitm): add a link to create macro documentation.
517     return ErrorAtToken(as_token, "Expected keyword 'as'");
518   }
519 
520   Token first = tokenizer_.NextNonWhitespace();
521   Token tok = tokenizer_.NextTerminal();
522   statement_ = CreateMacro{
523       replace, tokenizer_.SubstrToken(name), std::move(args),
524       tokenizer_.SubstrToken(returns_value), tokenizer_.Substr(first, tok)};
525   return true;
526 }
527 
ParseComplexArgumentType(std::pair<SqliteTokenizer::Token,SqliteTokenizer::Token> & table_and_col)528 bool PerfettoSqlParser::ParseComplexArgumentType(
529     std::pair<SqliteTokenizer::Token, SqliteTokenizer::Token>& table_and_col) {
530   enum TokenType { kRp, kDot, kTableName, kColumnName };
531   TokenType expected = kTableName;
532   for (Token tok = tokenizer_.NextNonWhitespace();;
533        tok = tokenizer_.NextNonWhitespace()) {
534     switch (expected) {
535       case kTableName: {
536         if (tok.token_type != TK_ID) {
537           return ErrorAtToken(tok, "Expected table name");
538         }
539         table_and_col.first = tok;
540         expected = kDot;
541         continue;
542       }
543       case kDot: {
544         if (tok.token_type != TK_DOT) {
545           return ErrorAtToken(tok,
546                               "Expected dot between table and column names");
547         }
548         expected = kColumnName;
549         continue;
550       }
551       case kColumnName: {
552         if (tok.token_type != TK_ID) {
553           return ErrorAtToken(tok, "Expected column name");
554         }
555         table_and_col.second = tok;
556         expected = kRp;
557         continue;
558       }
559       case kRp:
560         if (tok.token_type == TK_RP) {
561           return true;
562         }
563         return ErrorAtToken(tok, "Expected closing parenthesis");
564     }
565   }
566 }
567 
ParseRawArguments(std::vector<RawArgument> & args)568 bool PerfettoSqlParser::ParseRawArguments(std::vector<RawArgument>& args) {
569   Token tok = tokenizer_.NextNonWhitespace();
570   // Fast path for no args
571   if (tok.token_type == TK_RP)
572     return true;
573 
574   enum TokenType {
575     kArgName,
576     kArgType,
577     kCommaOrLpOrRp,
578   };
579 
580   std::optional<Token> id = std::nullopt;
581   TokenType expected = kArgName;
582   std::optional<RawArgument> maybe_arg;
583   for (;; tok = tokenizer_.NextNonWhitespace()) {
584     switch (expected) {
585       case kArgName: {
586         if (tok.token_type != TK_ID && tok.token_type != TK_KEY &&
587             tok.token_type != TK_FUNCTION) {
588           // TODO(lalitm): add a link to documentation.
589           base::StackString<1024> err("'%.*s' is not a valid argument name",
590                                       static_cast<int>(tok.str.size()),
591                                       tok.str.data());
592           return ErrorAtToken(tok, err.c_str());
593         }
594         id = tok;
595         expected = kArgType;
596         break;
597       }
598 
599       case kArgType: {
600         if (tok.token_type != TK_ID) {
601           // TODO(lalitm): add a link to documentation.
602           base::StackString<1024> err("'%.*s' is not a valid argument type",
603                                       static_cast<int>(tok.str.size()),
604                                       tok.str.data());
605           return ErrorAtToken(tok, err.c_str());
606         }
607         PERFETTO_CHECK(id);
608         RawArgument arg;
609         arg.name = *id;
610         arg.type = tok;
611         maybe_arg = arg;
612         id = std::nullopt;
613         expected = kCommaOrLpOrRp;
614         break;
615       }
616 
617       case kCommaOrLpOrRp: {
618         if (tok.token_type == TK_RP) {
619           if (maybe_arg)
620             args.push_back(*maybe_arg);
621           return true;
622         }
623 
624         // Next argument
625         if (tok.token_type == TK_COMMA) {
626           PERFETTO_CHECK(maybe_arg);
627           args.push_back(*maybe_arg);
628           maybe_arg = std::nullopt;
629           expected = kArgName;
630           continue;
631         }
632 
633         // Start of complex argument.
634         if (tok.token_type == TK_LP) {
635           PERFETTO_CHECK(maybe_arg);
636           std::pair<SqliteTokenizer::Token, SqliteTokenizer::Token>
637               table_and_col;
638           if (!ParseComplexArgumentType(table_and_col))
639             return false;
640           maybe_arg->complex_arg_table_and_column = table_and_col;
641         }
642       }
643     }
644   }
645 }
646 
ParseArguments(std::vector<sql_argument::ArgumentDefinition> & args)647 bool PerfettoSqlParser::ParseArguments(
648     std::vector<sql_argument::ArgumentDefinition>& args) {
649   std::vector<RawArgument> raw_args;
650   if (!ParseRawArguments(raw_args)) {
651     return false;
652   }
653   for (const auto& raw_arg : raw_args) {
654     std::optional<sql_argument::ArgumentDefinition> arg =
655         ResolveRawArgument(raw_arg);
656     if (!arg) {
657       return false;
658     }
659     args.emplace_back(std::move(*arg));
660   }
661   return true;
662 }
663 
664 std::optional<sql_argument::ArgumentDefinition>
ResolveRawArgument(RawArgument arg)665 PerfettoSqlParser::ResolveRawArgument(RawArgument arg) {
666   std::string arg_name = tokenizer_.SubstrToken(arg.name).sql();
667   std::string arg_type = tokenizer_.SubstrToken(arg.type).sql();
668   if (!sql_argument::IsValidName(base::StringView(arg_name))) {
669     base::StackString<1024> err("Name %s is not alphanumeric",
670                                 arg_name.c_str());
671     ErrorAtToken(arg.name, err.c_str());
672     return std::nullopt;
673   }
674   std::optional<sql_argument::Type> parsed_arg_type =
675       sql_argument::ParseType(base::StringView(arg_type));
676   if (!parsed_arg_type) {
677     base::StackString<1024> err("Invalid type %s", arg_type.c_str());
678     ErrorAtToken(arg.type, err.c_str());
679     return std::nullopt;
680   }
681   return sql_argument::ArgumentDefinition("$" + arg_name, *parsed_arg_type);
682 }
683 
ErrorAtToken(const SqliteTokenizer::Token & token,const char * error,...)684 bool PerfettoSqlParser::ErrorAtToken(const SqliteTokenizer::Token& token,
685                                      const char* error,
686                                      ...) {
687   std::string traceback = tokenizer_.AsTraceback(token);
688   status_ = base::ErrStatus("%s%s", traceback.c_str(), error);
689   return false;
690 }
691 
692 }  // namespace trace_processor
693 }  // namespace perfetto
694