001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.TBaseType; 005import gudusoft.gsqlparser.TCustomLexer; 006import gudusoft.gsqlparser.TCustomParser; 007import gudusoft.gsqlparser.TCustomSqlStatement; 008import gudusoft.gsqlparser.TLexerBigquery; 009import gudusoft.gsqlparser.TParserBigquery; 010import gudusoft.gsqlparser.TSourceToken; 011import gudusoft.gsqlparser.TSourceTokenList; 012import gudusoft.gsqlparser.TStatementList; 013import gudusoft.gsqlparser.TSyntaxError; 014import gudusoft.gsqlparser.EFindSqlStateType; 015import gudusoft.gsqlparser.ETokenType; 016import gudusoft.gsqlparser.ETokenStatus; 017import gudusoft.gsqlparser.ESqlStatementType; 018import gudusoft.gsqlparser.EErrorType; 019import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 020import gudusoft.gsqlparser.stmt.TCommonStoredProcedureSqlStatement; 021import gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement; 022import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage; 023import gudusoft.gsqlparser.nodes.TTypeName; 024import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 025import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 026import gudusoft.gsqlparser.compiler.TContext; 027import gudusoft.gsqlparser.sqlenv.TSQLEnv; 028import gudusoft.gsqlparser.compiler.TGlobalScope; 029import gudusoft.gsqlparser.compiler.TFrame; 030import gudusoft.gsqlparser.resolver.TSQLResolver; 031import gudusoft.gsqlparser.TLog; 032import gudusoft.gsqlparser.compiler.TASTEvaluator; 033 034import java.io.BufferedReader; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.List; 038import java.util.Stack; 039 040import static gudusoft.gsqlparser.ESqlStatementType.*; 041 042/** 043 * Google BigQuery SQL parser implementation. 044 * 045 * <p>This parser handles BigQuery-specific SQL syntax including: 046 * <ul> 047 * <li>BigQuery scripting language (BEGIN...END blocks, IF/WHILE/FOR/LOOP/REPEAT statements)</li> 048 * <li>BigQuery UDFs (CREATE FUNCTION with SQL or JavaScript)</li> 049 * <li>BigQuery procedures (CREATE PROCEDURE with BEGIN...END blocks)</li> 050 * <li>Special type handling (STRUCT, ARRAY, DATE/TIME/TIMESTAMP literals)</li> 051 * <li>Backtick-quoted identifiers including qualified names (`schema.table`)</li> 052 * <li>BigQuery-specific keywords and functions</li> 053 * </ul> 054 * 055 * <p><b>Design Notes:</b> 056 * <ul> 057 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 058 * <li>Uses {@link TLexerBigquery} for tokenization</li> 059 * <li>Uses {@link TParserBigquery} for parsing</li> 060 * <li>Delimiter character: ';' for SQL statements</li> 061 * <li>Splits backtick-quoted qualified names (`schema.table`) into individual tokens</li> 062 * </ul> 063 * 064 * <p><b>Usage Example:</b> 065 * <pre> 066 * // Get BigQuery parser from factory 067 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvbigquery); 068 * 069 * // Build context 070 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvbigquery) 071 * .sqlText("SELECT * FROM `project.dataset.table` WHERE region = 'US'") 072 * .build(); 073 * 074 * // Parse 075 * SqlParseResult result = parser.parse(context); 076 * 077 * // Access statements 078 * TStatementList statements = result.getSqlStatements(); 079 * </pre> 080 * 081 * @see SqlParser 082 * @see AbstractSqlParser 083 * @see TLexerBigquery 084 * @see TParserBigquery 085 * @since 3.2.0.0 086 */ 087public class BigQuerySqlParser extends AbstractSqlParser { 088 089 /** 090 * Construct BigQuery SQL parser. 091 * <p> 092 * Configures the parser for BigQuery database with default delimiter (;). 093 * <p> 094 * Following the original TGSqlParser pattern, the lexer and parser are 095 * created once in the constructor and reused for all parsing operations. 096 */ 097 public BigQuerySqlParser() { 098 super(EDbVendor.dbvbigquery); 099 this.delimiterChar = ';'; 100 this.defaultDelimiterStr = ";"; 101 102 // Create lexer once - will be reused for all parsing operations 103 this.flexer = new TLexerBigquery(); 104 this.flexer.delimiterchar = this.delimiterChar; 105 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 106 107 // Set parent's lexer reference for shared tokenization logic 108 this.lexer = this.flexer; 109 110 // Create parser once - will be reused for all parsing operations 111 this.fparser = new TParserBigquery(null); 112 this.fparser.lexer = this.flexer; 113 } 114 115 // ========== Parser Components ========== 116 117 /** The BigQuery lexer used for tokenization */ 118 public TLexerBigquery flexer; 119 120 /** SQL parser (for BigQuery statements) */ 121 private TParserBigquery fparser; 122 123 /** Current statement being built during extraction */ 124 private TCustomSqlStatement gcurrentsqlstatement; 125 126 /** Parser context for current operation */ 127 private ParserContext parserContext; 128 129 /** User-defined delimiter string for MySQL DELIMITER command */ 130 private String userDelimiterStr; 131 132 // Stored procedure parsing state tracking 133 private enum stored_procedure_type { 134 procedure, function, package_spec, package_body, block_with_declare, 135 block_with_begin, create_trigger, create_library, others 136 } 137 138 private enum stored_procedure_status { 139 start, is_as, body, bodyend, end 140 } 141 142 private static final int stored_procedure_nested_level = 50; 143 144 // Note: Global context and frame stack fields inherited from AbstractSqlParser: 145 // - protected TContext globalContext 146 // - protected TSQLEnv sqlEnv 147 // - protected Stack<TFrame> frameStack 148 // - protected TFrame globalFrame 149 150 // ========== AbstractSqlParser Abstract Methods Implementation ========== 151 152 /** 153 * Return the BigQuery lexer instance. 154 */ 155 @Override 156 protected TCustomLexer getLexer(ParserContext context) { 157 return this.flexer; 158 } 159 160 /** 161 * Return the BigQuery SQL parser instance with updated token list. 162 */ 163 @Override 164 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 165 this.fparser.sourcetokenlist = tokens; 166 return this.fparser; 167 } 168 169 /** 170 * BigQuery does not use a secondary parser (unlike Oracle with PL/SQL). 171 */ 172 @Override 173 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 174 return null; 175 } 176 177 /** 178 * Call BigQuery-specific tokenization logic. 179 * <p> 180 * Delegates to dobigquerysqltexttotokenlist which handles BigQuery's 181 * specific keyword recognition, backtick-quoted identifiers, and 182 * qualified name splitting. 183 */ 184 @Override 185 protected void tokenizeVendorSql() { 186 dobigquerysqltexttotokenlist(); 187 } 188 189 /** 190 * Setup BigQuery parser for raw statement extraction. 191 * <p> 192 * BigQuery uses a single parser, so we inject sqlcmds and update 193 * the token list for the main parser only. 194 */ 195 @Override 196 protected void setupVendorParsersForExtraction() { 197 // Inject sqlcmds into parser (required for make_stmt) 198 this.fparser.sqlcmds = this.sqlcmds; 199 200 // Update token list for parser 201 this.fparser.sourcetokenlist = this.sourcetokenlist; 202 } 203 204 /** 205 * Call BigQuery-specific raw statement extraction logic. 206 * <p> 207 * Delegates to dobigquerygetrawsqlstatements which handles BigQuery's 208 * statement delimiters and scripting language boundaries (BEGIN...END blocks, 209 * IF/WHILE/FOR/LOOP statements). 210 */ 211 @Override 212 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 213 int errorCount = dobigquerygetrawsqlstatements(builder); 214 // Error count is tracked internally; errors are already added to syntaxErrors list 215 216 // Set the extracted statements in the builder 217 builder.sqlStatements(this.sqlstatements); 218 } 219 220 // ========== Tokenization Methods ========== 221 222 /** 223 * Tokenize BigQuery SQL text into a list of tokens. 224 * <p> 225 * This method handles BigQuery-specific token processing: 226 * <ul> 227 * <li>Splits backtick-quoted qualified names (`schema.table`) into separate tokens</li> 228 * <li>Handles all standard SQL tokens (keywords, identifiers, operators, etc.)</li> 229 * </ul> 230 * <p> 231 * Migrated from TGSqlParser.dobigquerysqltexttotokenlist() 232 */ 233 private void dobigquerysqltexttotokenlist() { 234 TSourceToken asourcetoken, lcprevst; 235 int yychar; 236 237 flexer.tmpDelimiter = ""; 238 239 asourcetoken = getanewsourcetoken(); 240 if (asourcetoken == null) return; 241 yychar = asourcetoken.tokencode; 242 243 while (yychar > 0) { 244 if (asourcetoken != null) { 245 sourcetokenlist.add(asourcetoken); 246 } 247 248 asourcetoken = getanewsourcetoken(); 249 if (asourcetoken == null) break; 250 yychar = asourcetoken.tokencode; 251 252 // `schema.table_name` - split into separate tokens 253 if ((asourcetoken.tokencode == TBaseType.ident) 254 && (asourcetoken.toString().startsWith("`")) && (asourcetoken.toString().endsWith("`")) 255 && (asourcetoken.toString().indexOf(".") > 0)) { 256 yychar = splitQualifiedNameInBacktick(asourcetoken); 257 asourcetoken = null; 258 } 259 } 260 } 261 262 /** 263 * Turn one token: `schema.table_name` into 3 tokens: `schema` . `table_name` 264 * <p> 265 * This helper method splits backtick-quoted qualified names into individual 266 * identifier and period tokens, preserving line/column information for each part. 267 * <p> 268 * Migrated from TGSqlParser.splitQualifiedNameInBacktick() 269 * 270 * @param asourcetoken the token to split 271 * @return the token code of the last token created 272 */ 273 private int splitQualifiedNameInBacktick(TSourceToken asourcetoken) { 274 int yychar = 0; 275 276 List<String> elephantList = Arrays.asList(TBaseType.getTextWithoutQuoted(asourcetoken.toString()).split("\\.")); 277 int p = 0, offset = 0; 278 for (String s : elephantList) { 279 TSourceToken pst = new TSourceToken("`" + s + "`"); 280 pst.tokencode = asourcetoken.tokencode; 281 pst.tokentype = asourcetoken.tokentype; 282 pst.tokenstatus = asourcetoken.tokenstatus; 283 pst.splitFrom = asourcetoken; 284 pst.lineNo = asourcetoken.lineNo; 285 pst.columnNo = asourcetoken.columnNo + offset; 286 if (p == 0) offset++; // this count the first ` token 287 offset = offset + s.length(); 288 pst.container = sourcetokenlist; 289 if (p > 0) { // 第一个token使用被拆分前那个token的位置,从第二个开始的token,需要先把列表的位置指针加 1 290 sourcetokenlist.curpos = sourcetokenlist.curpos + 1; 291 } 292 pst.posinlist = sourcetokenlist.curpos; 293 294 sourcetokenlist.add(pst); 295 yychar = pst.tokencode; 296 297 if (p != elephantList.size() - 1) { 298 //`schema.table_name`, add period token in the middle of the backtick included identifier. 299 TSourceToken periodst = new TSourceToken("."); 300 periodst.tokencode = '.'; 301 periodst.tokentype = ETokenType.ttperiod; 302 periodst.tokenstatus = asourcetoken.tokenstatus; 303 periodst.splitFrom = asourcetoken; 304 periodst.lineNo = asourcetoken.lineNo; 305 periodst.columnNo = asourcetoken.columnNo + offset; 306 offset++; 307 periodst.container = sourcetokenlist; 308 sourcetokenlist.curpos = sourcetokenlist.curpos + 1; 309 periodst.posinlist = sourcetokenlist.curpos; 310 sourcetokenlist.add(periodst); 311 yychar = periodst.tokencode; 312 } 313 314 p++; 315 } 316 317 return yychar; 318 } 319 320 // ========== Raw Statement Extraction ========== 321 322 /** 323 * Check if token code is one that pairs with END keyword. 324 * <p> 325 * Used for BigQuery scripting language to track nested BEGIN/IF/CASE/LOOP/WHILE/FOR/REPEAT blocks. 326 * <p> 327 * Migrated from TGSqlParser.checkTokenPairWithEnd() 328 */ 329 private boolean checkTokenPairWithEnd(int tokencode) { 330 return ((tokencode == TBaseType.rrw_if) || (tokencode == TBaseType.rrw_case) 331 || (tokencode == TBaseType.rrw_loop) || (tokencode == TBaseType.rrw_repeat) 332 || (tokencode == TBaseType.rrw_while) || (tokencode == TBaseType.rrw_for) 333 || (tokencode == TBaseType.rrw_case)); 334 } 335 336 /** 337 * Extract raw SQL statements from the token list. 338 * <p> 339 * This method separates individual SQL statements without full syntax checking. 340 * It handles BigQuery-specific syntax including: 341 * <ul> 342 * <li>Stored procedures and functions with BEGIN...END blocks</li> 343 * <li>Scripting language statements (IF, WHILE, FOR, LOOP, REPEAT, CASE)</li> 344 * <li>STRUCT constructor special handling</li> 345 * <li>Type casting with literals (DATE '2021-01-01')</li> 346 * <li>Nested procedure tracking</li> 347 * </ul> 348 * <p> 349 * Migrated from TGSqlParser.dobigquerygetrawsqlstatements() 350 * 351 * @param builder the result builder to populate 352 * @return error count 353 */ 354 private int dobigquerygetrawsqlstatements(SqlParseResult.Builder builder) { 355 int errorcount = 0; 356 gcurrentsqlstatement = null; 357 EFindSqlStateType gst = EFindSqlStateType.stnormal; 358 int i, c, beginNested = 0, waitingEnd = 0; 359 TSourceToken ast = null, lcprevsolidtoken = null; 360 boolean waitingDelimiter = false; 361 362 int waitingEnds[] = new int[stored_procedure_nested_level]; 363 stored_procedure_type sptype[] = new stored_procedure_type[stored_procedure_nested_level]; 364 stored_procedure_status procedure_status[] = new stored_procedure_status[stored_procedure_nested_level]; 365 boolean endBySlashOnly = true; 366 int nestedProcedures = 0, nestedParenthesis = 0; 367 368 //reset delimiter 369 userDelimiterStr = defaultDelimiterStr; 370 371 for (i = 0; i < sourcetokenlist.size(); i++) { 372 if ((ast != null) && (ast.issolidtoken())) 373 lcprevsolidtoken = ast; 374 375 ast = sourcetokenlist.get(i); 376 sourcetokenlist.curpos = i; 377 378 // Handle STRUCT constructor: STRUCT(...) -> mark as struct constructor 379 if (ast.tokencode == TBaseType.rrw_bigquery_struct) { 380 TSourceToken st1 = ast.nextSolidToken(); 381 if (st1 != null) { 382 if (st1.tokencode == '(') { 383 ast.tokencode = TBaseType.rrw_bigquery_struct_constructor; 384 } 385 } 386 } 387 // Handle type casting: DATE '2021-01-01' -> mark DATE as datatype used to cast 388 else if ((ast.tokencode == TBaseType.sconst)) { 389 if (lcprevsolidtoken != null && TTypeName.searchTypeByName(lcprevsolidtoken.toString()) != null) { 390 if (lcprevsolidtoken.tokencode != TBaseType.rrw_interval) { 391 lcprevsolidtoken.tokencode = TBaseType.rrw_bigquery_datatype_used_to_cast; 392 } 393 } 394 } 395 // Handle TIME/DATE: could be function or type literal 396 else if ((ast.tokencode == TBaseType.rrw_time) || (ast.tokencode == TBaseType.rrw_date)) { 397 TSourceToken st1 = ast.nextSolidToken(); 398 if (st1 != null) { 399 if (st1.tokencode == TBaseType.sconst) { 400 // ast.tokencode = TBaseType.rrw_bigquery_time_before_const; 401 } else if (st1.tokencode == '(') { 402 ast.tokencode = TBaseType.ident; 403 } 404 } 405 } 406 // Handle FROM after period: i.from -> treat FROM as identifier 407 else if (ast.tokencode == TBaseType.rrw_from) { 408 TSourceToken st1 = ast.prevSolidToken(); 409 if (st1 != null) { // select i.from as `from` from t, treats from in i.from as identifier 410 if (st1.tokencode == '.') { 411 ast.tokencode = TBaseType.ident; 412 } 413 } 414 } 415 // Handle TIMESTAMP: distinguish type from identifier based on context 416 else if (ast.tokencode == TBaseType.rrw_timestamp) { 417 TSourceToken prevToken = ast.prevSolidToken(); 418 TSourceToken nextToken = ast.nextSolidToken(); 419 420 // Check if preceded by period -> identifier (e.g., table.timestamp) 421 if (prevToken != null && prevToken.tokencode == '.') { 422 // Keep as RW_TIMESTAMP (will be used as identifier via unreserved_keyword) 423 } 424 // Check type contexts where TIMESTAMP should be marked as type 425 else if (prevToken != null && 426 (prevToken.tokencode == TBaseType.rrw_as || // CAST(x AS TIMESTAMP) 427 prevToken.tokencode == ',' || // func(INT, TIMESTAMP) 428 prevToken.tokencode == '(' || // CAST(TIMESTAMP ...), STRUCT(col TIMESTAMP) 429 prevToken.tokencode == '<' || // ARRAY<TIMESTAMP>, STRUCT<col TIMESTAMP> 430 prevToken.tokencode == TBaseType.ident)) { // column_name TIMESTAMP (column definition) 431 ast.tokencode = TBaseType.rrw_bigquery_timestamp_as_type; 432 } 433 // Check if followed by '(' with precision -> type constructor TIMESTAMP(6) 434 else if (nextToken != null && nextToken.tokencode == '(') { 435 TSourceToken afterParen = nextToken.nextSolidToken(); 436 if (afterParen != null && 437 (afterParen.tokencode == TBaseType.iconst || // TIMESTAMP(6) 438 afterParen.tokencode == ')')) { // TIMESTAMP() 439 ast.tokencode = TBaseType.rrw_bigquery_timestamp_as_type; 440 } 441 } 442 // Check if followed by common tokens after type declarations 443 else if (nextToken != null && 444 (nextToken.tokencode == ',' || // col TIMESTAMP, another 445 nextToken.tokencode == ')' || // col TIMESTAMP) 446 nextToken.tokencode == '>' || // ARRAY<TIMESTAMP> 447 nextToken.tokencode == TBaseType.rrw_not || // TIMESTAMP NOT NULL 448 nextToken.tokencode == TBaseType.rrw_null)) { // TIMESTAMP NULL 449 ast.tokencode = TBaseType.rrw_bigquery_timestamp_as_type; 450 } 451 } 452 // Handle LEFT/INNER/FULL prefixing a set operation (LEFT [OUTER] UNION ...): 453 // rewrite to synthetic *_SET_OP tokens so the grammar can tell a set-op 454 // prefix apart from a join keyword after a parenthesized subquery 455 else if ((ast.tokencode == TBaseType.rrw_left) 456 || (ast.tokencode == TBaseType.rrw_inner) 457 || (ast.tokencode == TBaseType.rrw_full)) { 458 TSourceToken prevToken = ast.prevSolidToken(); 459 // t.LEFT / t.FULL are qualified-name segments, never set-op prefixes 460 if (prevToken == null || prevToken.tokencode != '.') { 461 TSourceToken st1 = ast.nextSolidToken(); 462 // LEFT/FULL allow an optional OUTER before UNION; INNER does not 463 if ((st1 != null) && (st1.tokencode == TBaseType.rrw_outer) 464 && (ast.tokencode != TBaseType.rrw_inner)) { 465 st1 = st1.nextSolidToken(); 466 } 467 if ((st1 != null) && (st1.tokencode == TBaseType.rrw_union)) { 468 if (ast.tokencode == TBaseType.rrw_left) { 469 ast.tokencode = TBaseType.rrw_bigquery_left_set_op; 470 } else if (ast.tokencode == TBaseType.rrw_inner) { 471 ast.tokencode = TBaseType.rrw_bigquery_inner_set_op; 472 } else { 473 ast.tokencode = TBaseType.rrw_bigquery_full_set_op; 474 } 475 } 476 } 477 } 478 479 switch (gst) { 480 case sterror: { 481 if (ast.tokentype == ETokenType.ttsemicolon) { 482 gcurrentsqlstatement.sourcetokenlist.add(ast); 483 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 484 gst = EFindSqlStateType.stnormal; 485 } else { 486 gcurrentsqlstatement.sourcetokenlist.add(ast); 487 } 488 break; 489 } 490 case stnormal: { 491 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 492 || (ast.tokencode == TBaseType.cmtslashstar) 493 || (ast.tokencode == TBaseType.lexspace) 494 || (ast.tokencode == TBaseType.lexnewline) 495 || (ast.tokentype == ETokenType.ttsemicolon)) { 496 if (TBaseType.assigned(gcurrentsqlstatement)) { 497 gcurrentsqlstatement.sourcetokenlist.add(ast); 498 } 499 500 continue; 501 } 502 503 // find a token to start sql or plsql mode 504 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 505 506 if (TBaseType.assigned(gcurrentsqlstatement)) { 507 if (gcurrentsqlstatement.isBigQueryplsql()) { 508 nestedProcedures = 0; 509 gst = EFindSqlStateType.ststoredprocedure; 510 gcurrentsqlstatement.sourcetokenlist.add(ast); 511 512 switch (gcurrentsqlstatement.sqlstatementtype) { 513 case sstplsql_createprocedure: 514 case sstcreateprocedure: 515 sptype[nestedProcedures] = stored_procedure_type.procedure; 516 break; 517 case sstplsql_createfunction: 518 sptype[nestedProcedures] = stored_procedure_type.function; 519 break; 520 case sstplsql_createpackage: 521 sptype[nestedProcedures] = stored_procedure_type.package_spec; 522 if (ast.searchToken(TBaseType.rrw_body, 5) != null) { 523 sptype[nestedProcedures] = stored_procedure_type.package_body; 524 } 525 break; 526 case sst_plsql_block: 527 sptype[nestedProcedures] = stored_procedure_type.block_with_declare; 528 if (ast.tokencode == TBaseType.rrw_begin) { 529 sptype[nestedProcedures] = stored_procedure_type.block_with_begin; 530 } 531 break; 532 case sstplsql_createtrigger: 533 sptype[nestedProcedures] = stored_procedure_type.create_trigger; 534 break; 535 case sstoraclecreatelibrary: 536 sptype[nestedProcedures] = stored_procedure_type.create_library; 537 break; 538 case sstplsql_createtype_placeholder: 539 gst = EFindSqlStateType.stsql; 540 break; 541 default: 542 sptype[nestedProcedures] = stored_procedure_type.others; 543 break; 544 } 545 546 if (sptype[0] == stored_procedure_type.block_with_declare) { 547 // sd 548 endBySlashOnly = false; 549 procedure_status[0] = stored_procedure_status.is_as; 550 } else if (sptype[0] == stored_procedure_type.block_with_begin) { 551 // sb 552 endBySlashOnly = false; 553 procedure_status[0] = stored_procedure_status.body; 554 } else if (sptype[0] == stored_procedure_type.procedure) { 555 // ss 556 endBySlashOnly = false; 557 procedure_status[0] = stored_procedure_status.start; 558 } else if (sptype[0] == stored_procedure_type.function) { 559 // ss 560 endBySlashOnly = false; 561 procedure_status[0] = stored_procedure_status.start; 562 } else if (sptype[0] == stored_procedure_type.package_spec) { 563 // ss 564 endBySlashOnly = false; 565 procedure_status[0] = stored_procedure_status.start; 566 } else if (sptype[0] == stored_procedure_type.package_body) { 567 // ss 568 endBySlashOnly = false; 569 procedure_status[0] = stored_procedure_status.start; 570 } else if (sptype[0] == stored_procedure_type.create_trigger) { 571 // ss 572 endBySlashOnly = false; 573 procedure_status[0] = stored_procedure_status.start; 574 } else if (sptype[0] == stored_procedure_type.create_library) { 575 // ss 576 endBySlashOnly = false; 577 procedure_status[0] = stored_procedure_status.bodyend; 578 } else { 579 // so 580 endBySlashOnly = true; 581 procedure_status[0] = stored_procedure_status.bodyend; 582 } 583 if ((ast.tokencode == TBaseType.rrw_begin) 584 || (ast.tokencode == TBaseType.rrw_package) 585 || (ast.searchToken(TBaseType.rrw_package, 4) != null)) { 586 waitingEnds[nestedProcedures] = 1; 587 } 588 589 } else if ((gcurrentsqlstatement.sqlstatementtype == sst_ifstmt) 590 || (gcurrentsqlstatement.sqlstatementtype == sst_loopstmt) 591 || (gcurrentsqlstatement.sqlstatementtype == sstRepeat) 592 || (gcurrentsqlstatement.sqlstatementtype == sstWhilestmt) 593 || (gcurrentsqlstatement.sqlstatementtype == sstForStmt) 594 || (gcurrentsqlstatement.sqlstatementtype == sst_plsql_block) 595 || (gcurrentsqlstatement.sqlstatementtype == sst_casestmt)) { 596 gst = EFindSqlStateType.stBigQueryIf; 597 // For labeled statements (label: LOOP/WHILE/etc.), the first token 598 // is the label identifier, and the keyword comes later in stBigQueryIf 599 // where it will increment waitingEnd. So start at 0. 600 waitingEnd = (ast.tokencode == TBaseType.ident) ? 0 : 1; 601 gcurrentsqlstatement.sourcetokenlist.add(ast); 602 } else { 603 gst = EFindSqlStateType.stsql; 604 gcurrentsqlstatement.sourcetokenlist.add(ast); 605 } 606 607 } 608 609 if (!TBaseType.assigned(gcurrentsqlstatement)) //error token found 610 { 611 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 612 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 613 614 ast.tokentype = ETokenType.tttokenlizererrortoken; 615 gst = EFindSqlStateType.sterror; 616 617 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 618 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 619 gcurrentsqlstatement.sourcetokenlist.add(ast); 620 621 } 622 break; 623 } 624 case stBigQueryIf: { 625 gcurrentsqlstatement.sourcetokenlist.add(ast); 626 627 if (checkTokenPairWithEnd(ast.tokencode)) { // if... end if 628 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 629 //this is not if after END 630 waitingEnd++; 631 } 632 } else if (ast.tokencode == TBaseType.rrw_end) { // if ... end if 633 TSourceToken next = ast.nextSolidToken(); 634 if (next != null) { 635 if (checkTokenPairWithEnd(next.tokencode)) { // if ... end if; 636 waitingEnd--; 637 } else if (next.tokencode == ';') { // begin ... end ; 638 waitingEnd--; 639 } else if (next.tokencode == TBaseType.ident) { 640 // begin ... end label_name ; (labeled block end) 641 TSourceToken afterLabel = next.nextSolidToken(); 642 if (afterLabel != null && afterLabel.tokencode == ';') { 643 waitingEnd--; 644 } 645 } 646 647 } 648 } else if ((ast.tokencode == ';') && (waitingEnd == 0)) { 649 gst = EFindSqlStateType.stnormal; 650 651 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 652 } 653 break; 654 } 655 case stsqlplus: { 656 if (ast.tokencode == TBaseType.lexnewline) { 657 gst = EFindSqlStateType.stnormal; 658 gcurrentsqlstatement.sourcetokenlist.add(ast); // so add it here 659 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 660 } else { 661 { 662 gcurrentsqlstatement.sourcetokenlist.add(ast); 663 } 664 } 665 666 break; 667 }//case source command or \. command 668 case stsql: { 669 if ((ast.tokentype == ETokenType.ttsemicolon) && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstmysqldelimiter)) { 670 gst = EFindSqlStateType.stnormal; 671 gcurrentsqlstatement.sourcetokenlist.add(ast); 672 gcurrentsqlstatement.semicolonended = ast; 673 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 674 continue; 675 } 676 if (ast.toString().equalsIgnoreCase(userDelimiterStr)) { 677 gst = EFindSqlStateType.stnormal; 678 ast.tokencode = ';';// treat it as semicolon 679 gcurrentsqlstatement.sourcetokenlist.add(ast); 680 gcurrentsqlstatement.semicolonended = ast; 681 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 682 continue; 683 } 684 gcurrentsqlstatement.sourcetokenlist.add(ast); 685 686 if ((ast.tokencode == TBaseType.lexnewline) 687 && (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmysqldelimiter)) { 688 gst = EFindSqlStateType.stnormal; 689 userDelimiterStr = ""; 690 for (int k = 0; k < gcurrentsqlstatement.sourcetokenlist.size(); k++) { 691 TSourceToken st = gcurrentsqlstatement.sourcetokenlist.get(k); 692 if ((st.tokencode == TBaseType.rrw_mysql_delimiter) 693 || (st.tokencode == TBaseType.lexnewline) 694 || (st.tokencode == TBaseType.lexspace)) { 695 continue; 696 } 697 698 userDelimiterStr += st.toString(); 699 } 700 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 701 702 continue; 703 } 704 705 break; 706 } 707 case ststoredprocedure: { 708 if (procedure_status[nestedProcedures] != stored_procedure_status.bodyend) { 709 gcurrentsqlstatement.sourcetokenlist.add(ast); 710 } 711 712 switch (procedure_status[nestedProcedures]) { 713 case start: 714 if ((ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is)) { 715 // s1 716 if (sptype[nestedProcedures] != stored_procedure_type.create_trigger) { 717 if ((sptype[0] == stored_procedure_type.package_spec) && (nestedProcedures > 0)) { 718 //when it's a package specification, only top level accept as/is 719 } else { 720 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 721 if (ast.searchToken("language", 1) != null) { 722 if (nestedProcedures == 0) { 723 gst = EFindSqlStateType.stsql; 724 } else { 725 procedure_status[nestedProcedures] = stored_procedure_status.body; 726 nestedProcedures--; 727 } 728 729 } 730 } 731 } 732 } else if (ast.tokencode == TBaseType.rrw_begin) { 733 // s4 734 if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) 735 waitingEnds[nestedProcedures]++; 736 737 if (nestedProcedures > 0) { 738 nestedProcedures--; 739 } 740 procedure_status[nestedProcedures] = stored_procedure_status.body; 741 waitingEnds[nestedProcedures] = 1; 742 } else if (ast.tokencode == TBaseType.rrw_end) { 743 //s10 744 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures - 1] == 1) 745 && ((sptype[nestedProcedures - 1] == stored_procedure_type.package_body) 746 || (sptype[nestedProcedures - 1] == stored_procedure_type.package_spec))) { 747 nestedProcedures--; 748 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 749 } 750 } else if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) { 751 //s3 752 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0) 753 && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) { 754 nestedProcedures--; 755 nestedProcedures++; 756 waitingEnds[nestedProcedures] = 0; 757 procedure_status[nestedProcedures] = stored_procedure_status.start; 758 } 759 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokencode == TBaseType.rrw_declare)) { 760 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 761 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 762 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 763 gst = EFindSqlStateType.stnormal; 764 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 765 766 //make / a sqlplus cmd 767 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 768 gcurrentsqlstatement.sourcetokenlist.add(ast); 769 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 770 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger)) { 771 if (ast.tokencode == TBaseType.rrw_trigger) { 772 TSourceToken compoundSt = ast.searchToken(TBaseType.rrw_oracle_compound, -1); 773 if (compoundSt != null) { 774 //it's trigger with compound trigger block 775 procedure_status[nestedProcedures] = stored_procedure_status.body; 776 waitingEnds[nestedProcedures]++; 777 } 778 } 779 } else if ((sptype[nestedProcedures] == stored_procedure_type.function) && (ast.tokencode == TBaseType.rrw_teradata_using)) { 780 if ((ast.searchToken("aggregate", -1) != null) || (ast.searchToken("pipelined", -1) != null)) { 781 if (nestedProcedures == 0) { 782 gst = EFindSqlStateType.stsql; 783 } else { 784 procedure_status[nestedProcedures] = stored_procedure_status.body; 785 nestedProcedures--; 786 } 787 } 788 789 } else { 790 //other tokens, do nothing 791 } 792 break; 793 case is_as: 794 if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) { 795 // s2 796 nestedProcedures++; 797 if (nestedProcedures > stored_procedure_nested_level - 1) { 798 gst = EFindSqlStateType.sterror; 799 nestedProcedures--; 800 } else { 801 waitingEnds[nestedProcedures] = 0; 802 procedure_status[nestedProcedures] = stored_procedure_status.start; 803 } 804 805 } else if (ast.tokencode == TBaseType.rrw_begin) { 806 // s5 807 if ((nestedProcedures == 0) && 808 ((sptype[nestedProcedures] == stored_procedure_type.package_body) 809 || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) { 810 //top level package or package body's BEGIN keyword already count, 811 // so don't increase waitingEnds[nestedProcedures] here 812 813 } else { 814 waitingEnds[nestedProcedures]++; 815 } 816 procedure_status[nestedProcedures] = stored_procedure_status.body; 817 } else if (ast.tokencode == TBaseType.rrw_end) { 818 // s6 819 if ((nestedProcedures == 0) && (waitingEnds[nestedProcedures] == 1) && 820 ((sptype[nestedProcedures] == stored_procedure_type.package_body) || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) { 821 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 822 waitingEnds[nestedProcedures]--; 823 } else { 824 waitingEnds[nestedProcedures]--; 825 } 826 } else if (ast.tokencode == TBaseType.rrw_case) { 827 if (ast.searchToken(';', 1) == null) { 828 //this is not case before ; 829 waitingEnds[nestedProcedures]++; 830 } 831 } else { 832 //other tokens, do nothing 833 } 834 break; 835 case body: 836 if ((ast.tokencode == TBaseType.rrw_begin)) { 837 waitingEnds[nestedProcedures]++; 838 } else if (ast.tokencode == TBaseType.rrw_if) { 839 840 if (ast.searchToken(';', 2) == null) { 841 //this is not if before ; 842 843 // 2015-02-27, change 1 to 2 make it able to detect label name after case 844 // like this: END CASE l1; 845 waitingEnds[nestedProcedures]++; 846 } 847 } else if (ast.tokencode == TBaseType.rrw_case) { 848 if (ast.searchToken(';', 2) == null) { 849 //this is not case before ; 850 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 851 waitingEnds[nestedProcedures]++; 852 } 853 } 854 } else if ((ast.tokencode == TBaseType.rrw_loop) 855 || (ast.tokencode == TBaseType.rrw_while) || (ast.tokencode == TBaseType.rrw_repeat) 856 || (ast.tokencode == TBaseType.rrw_for)) { 857 if (!((ast.searchToken(TBaseType.rrw_end, -1) != null) 858 && (ast.searchToken(';', 2) != null))) { 859 // exclude loop like this: 860 // end loop [labelname]; 861 waitingEnds[nestedProcedures]++; 862 } 863 864 } else if (ast.tokencode == TBaseType.rrw_end) { 865 //foundEnd = true; 866 waitingEnds[nestedProcedures]--; 867 //if (waitingEnd < 0) { waitingEnd = 0;} 868 if (waitingEnds[nestedProcedures] == 0) { 869 if (nestedProcedures == 0) { 870 // s7 871 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 872 } else { 873 // s71 874 nestedProcedures--; 875 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 876 } 877 } 878 } else if ((waitingEnds[nestedProcedures] == 0) && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 879 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 880 gst = EFindSqlStateType.stnormal; 881 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 882 883 //make / a sqlplus cmd 884 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 885 gcurrentsqlstatement.sourcetokenlist.add(ast); 886 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 887 } 888 break; 889 case bodyend: 890 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 891 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 892 gst = EFindSqlStateType.stnormal; 893 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 894 895 //make / a sqlplus cmd 896 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 897 gcurrentsqlstatement.sourcetokenlist.add(ast); 898 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 899 } else if ((ast.tokencode == ';')) { 900 gst = EFindSqlStateType.stnormal; 901 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 902 903 } else if ((ast.searchToken(TBaseType.rrw_package, 1) != null) && (!endBySlashOnly)) { 904 gcurrentsqlstatement.sourcetokenlist.add(ast); 905 gst = EFindSqlStateType.stnormal; 906 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 907 } else if ((ast.searchToken(TBaseType.rrw_procedure, 1) != null) && (!endBySlashOnly)) { 908 gcurrentsqlstatement.sourcetokenlist.add(ast); 909 gst = EFindSqlStateType.stnormal; 910 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 911 } else if ((ast.searchToken(TBaseType.rrw_function, 1) != null) && (!endBySlashOnly)) { 912 gcurrentsqlstatement.sourcetokenlist.add(ast); 913 gst = EFindSqlStateType.stnormal; 914 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 915 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_package, 4) != null) && (!endBySlashOnly)) { 916 gcurrentsqlstatement.sourcetokenlist.add(ast); 917 gst = EFindSqlStateType.stnormal; 918 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 919 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_library, 4) != null) && (!endBySlashOnly)) { 920 gcurrentsqlstatement.sourcetokenlist.add(ast); 921 gst = EFindSqlStateType.stnormal; 922 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 923 } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null) && (ast.searchToken(TBaseType.rrw_trigger, 2) != null) && (!endBySlashOnly)) { 924 gcurrentsqlstatement.sourcetokenlist.add(ast); 925 gst = EFindSqlStateType.stnormal; 926 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 927 } else if ((ast.searchToken(TBaseType.rrw_select, 1) != null) && (!endBySlashOnly)) { 928 gcurrentsqlstatement.sourcetokenlist.add(ast); 929 gst = EFindSqlStateType.stnormal; 930 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 931 } else if ((ast.searchToken(TBaseType.rrw_commit, 1) != null) && (!endBySlashOnly)) { 932 gcurrentsqlstatement.sourcetokenlist.add(ast); 933 gst = EFindSqlStateType.stnormal; 934 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 935 } else if ((ast.searchToken(TBaseType.rrw_grant, 1) != null) && 936 (ast.searchToken(TBaseType.rrw_execute, 2) != null) && (!endBySlashOnly)) { 937 gcurrentsqlstatement.sourcetokenlist.add(ast); 938 gst = EFindSqlStateType.stnormal; 939 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 940 } else { 941 gcurrentsqlstatement.sourcetokenlist.add(ast); 942 } 943 break; 944 case end: 945 break; 946 default: 947 break; 948 } 949 950 if (ast.tokencode == TBaseType.sqlpluscmd) { 951 //change tokencode back to keyword or TBaseType.ident, because sqlplus cmd 952 //in a sql statement(almost is plsql block) is not really a sqlplus cmd 953 int m = flexer.getkeywordvalue(ast.getAstext()); 954 if (m != 0) { 955 ast.tokencode = m; 956 } else if (ast.tokentype == ETokenType.ttslash) { 957 ast.tokencode = '/'; 958 } else { 959 ast.tokencode = TBaseType.ident; 960 } 961 } 962 963 final int wrapped_keyword_max_pos = 20; 964 if ((ast.tokencode == TBaseType.rrw_wrapped) && (ast.posinlist - gcurrentsqlstatement.sourcetokenlist.get(0).posinlist < wrapped_keyword_max_pos)) { 965 if (gcurrentsqlstatement instanceof TCommonStoredProcedureSqlStatement) { 966 ((TCommonStoredProcedureSqlStatement) gcurrentsqlstatement).setWrapped(true); 967 } 968 969 if (gcurrentsqlstatement instanceof TPlsqlCreatePackage) { 970 if (ast.prevSolidToken() != null) { 971 ((TPlsqlCreatePackage) gcurrentsqlstatement).setPackageName(fparser.getNf().createObjectNameWithPart(ast.prevSolidToken())); 972 } 973 } 974 } 975 976 break; 977 } //ststoredprocedure 978 979 } //case 980 } //for 981 982 //last statement 983 if (TBaseType.assigned(gcurrentsqlstatement) && 984 ((gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) 985 || (gst == EFindSqlStateType.stBigQueryIf) || (gst == EFindSqlStateType.sterror))) { 986 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 987 } 988 989 return errorcount; 990 } 991 992 // ========== Statement Parsing ========== 993 994 /** 995 * Parse all SQL statements after tokenization and raw extraction. 996 * <p> 997 * This method iterates through all statements, calls parsestatement on each, 998 * and handles error recovery for CREATE TABLE statements if enabled. 999 * <p> 1000 * Inherited performParsing pattern from AbstractSqlParser, specialized for BigQuery. 1001 */ 1002 @Override 1003 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 1004 TCustomParser secondaryParser, TSourceTokenList tokens, 1005 TStatementList rawStatements) { 1006 // Store references for error handling 1007 this.fparser = (TParserBigquery) parser; 1008 this.sourcetokenlist = tokens; 1009 this.parserContext = context; 1010 this.sqlstatements = rawStatements; 1011 1012 // Initialize sqlcmds for BigQuery 1013 if (this.sqlcmds == null) { 1014 this.sqlcmds = SqlCmdsFactory.get(vendor); 1015 } 1016 this.fparser.sqlcmds = this.sqlcmds; 1017 1018 // Initialize global context using inherited method 1019 initializeGlobalContext(); 1020 1021 // Parse each statement 1022 for (int i = 0; i < sqlstatements.size(); i++) { 1023 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 1024 1025 try { 1026 // Set frame stack for variable scope tracking 1027 stmt.setFrameStack(frameStack); 1028 1029 // Parse the statement 1030 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 1031 1032 // Vendor-specific post-processing (none needed for BigQuery currently) 1033 afterStatementParsed(stmt); 1034 1035 // Collect errors from the statement 1036 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 1037 copyErrorsFromStatement(stmt); 1038 } 1039 1040 } catch (Exception ex) { 1041 // Use inherited exception handler 1042 handleStatementParsingException(stmt, i, ex); 1043 continue; 1044 } 1045 } 1046 1047 // Clean up frame stack 1048 if (globalFrame != null) { 1049 globalFrame.popMeFromStack(frameStack); 1050 } 1051 1052 return sqlstatements; 1053 } 1054 1055 /** 1056 * Post-process statement after parsing (hook method). 1057 * <p> 1058 * BigQuery does not require special post-processing, so this is a no-op. 1059 * Override if BigQuery-specific validation is needed in the future. 1060 */ 1061 protected void afterStatementParsed(TCustomSqlStatement stmt) { 1062 // No special post-processing needed for BigQuery 1063 } 1064 1065 // ========== Semantic Analysis ========== 1066 1067 /** 1068 * Perform semantic analysis on parsed statements. 1069 * <p> 1070 * Runs TSQLResolver to build relationships between tables and columns, 1071 * resolve references, and perform type checking. 1072 */ 1073 @Override 1074 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 1075 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 1076 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 1077 resolver.resolve(); 1078 } 1079 } 1080 1081 /** 1082 * Perform interpretation/evaluation on statements. 1083 * <p> 1084 * Runs TASTEvaluator to execute constant expressions and compile-time 1085 * evaluations. 1086 */ 1087 @Override 1088 protected void performInterpreter(ParserContext context, TStatementList statements) { 1089 // BigQuery does not require interpretation currently 1090 } 1091 1092 @Override 1093 public String toString() { 1094 return "BigQuerySqlParser{vendor=" + vendor + "}"; 1095 } 1096}