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