001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.TBaseType; 005import gudusoft.gsqlparser.TGSqlParser; 006import gudusoft.gsqlparser.TCustomLexer; 007import gudusoft.gsqlparser.TCustomParser; 008import gudusoft.gsqlparser.TCustomSqlStatement; 009import gudusoft.gsqlparser.TLexerSqlanywhere; 010import gudusoft.gsqlparser.TParserSqlanywhere; 011import gudusoft.gsqlparser.TSourceToken; 012import gudusoft.gsqlparser.TSourceTokenList; 013import gudusoft.gsqlparser.TStatementList; 014import gudusoft.gsqlparser.TSyntaxError; 015import gudusoft.gsqlparser.EFindSqlStateType; 016import gudusoft.gsqlparser.ETokenType; 017import gudusoft.gsqlparser.ETokenStatus; 018import gudusoft.gsqlparser.ESqlStatementType; 019import gudusoft.gsqlparser.EErrorType; 020import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 021import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock; 022import gudusoft.gsqlparser.stmt.mssql.TMssqlExecute; 023import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 024import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 025import gudusoft.gsqlparser.compiler.TContext; 026import gudusoft.gsqlparser.sqlenv.TSQLEnv; 027import gudusoft.gsqlparser.compiler.TGlobalScope; 028import gudusoft.gsqlparser.compiler.TFrame; 029import gudusoft.gsqlparser.resolver.TSQLResolver; 030import gudusoft.gsqlparser.resolver2.TSQLResolver2; 031import gudusoft.gsqlparser.resolver2.TSQLResolverConfig; 032import gudusoft.gsqlparser.TLog; 033import gudusoft.gsqlparser.compiler.TASTEvaluator; 034 035import java.io.BufferedReader; 036import java.util.ArrayList; 037import java.util.BitSet; 038import java.util.List; 039import java.util.Stack; 040 041/** 042 * SQL Anywhere SQL parser implementation. 043 * 044 * <p>This parser handles SQL Anywhere-specific SQL syntax including: 045 * <ul> 046 * <li>SQL Anywhere stored procedures with GO statement boundaries</li> 047 * <li>SQL Anywhere BEGIN/END blocks</li> 048 * <li>SQL Anywhere TRY/CATCH blocks</li> 049 * <li>SQL Anywhere-specific statement types</li> 050 * </ul> 051 * 052 * <p><b>Design Notes:</b> 053 * <ul> 054 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 055 * <li>Uses {@link TLexerSqlanywhere} for tokenization</li> 056 * <li>Uses {@link TParserSqlanywhere} for parsing</li> 057 * <li>Tokenization and statement extraction similar to MSSQL</li> 058 * <li>Delimiter character: ';' for SQL Anywhere statements</li> 059 * </ul> 060 * 061 * @see SqlParser 062 * @see AbstractSqlParser 063 * @see TLexerSqlanywhere 064 * @see TParserSqlanywhere 065 * @since 3.2.0.0 066 */ 067public class SqlAnywhereSqlParser extends AbstractSqlParser { 068 069 /** 070 * Policy layer of the recognize-then-reject design (see 071 * docs/designs/sybase-tsql-vendor-decomposition.md). 072 * 073 * <p>The splitter registers the FULL family command set, so statement 074 * boundaries are always correct even in multi-statement input and in both 075 * splitter modes. Statements recognised as types this product does not 076 * have are then rejected HERE, with the statement type preserved and the 077 * error anchored at the statement's first token. 078 * 079 * <p>De-registration was the previous mechanism and is retired: it made 080 * the splitter mis-attach forbidden statements to their predecessor in 081 * multi-statement input (swallowing them with rc=0), and crashed outright 082 * where the grammar builds a dedicated node. 083 */ 084 private static final java.util.EnumSet<ESqlStatementType> FORBIDDEN_STATEMENTS = 085 java.util.EnumSet.of( 086 ESqlStatementType.sstsybasecreatejoinindex, 087 ESqlStatementType.sstsybasesynchronizejoinindex, 088 ESqlStatementType.sstsybaseiqutilities, 089 ESqlStatementType.sstsybasedumpTran, 090 ESqlStatementType.sstmssqlreadtext, 091 ESqlStatementType.sstsybaseupdateindexstatistics, 092 ESqlStatementType.sstsybaseDeleteStatistics); 093 094 private static final java.util.Map<ESqlStatementType, String> FORBIDDEN_MESSAGES = 095 new java.util.EnumMap<>(ESqlStatementType.class); 096 static { 097 FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybasecreatejoinindex, 098 "CREATE JOIN INDEX is Sybase IQ syntax and is not valid for SAP SQL Anywhere"); 099 FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybasesynchronizejoinindex, 100 "SYNCHRONIZE JOIN INDEX is Sybase IQ syntax and is not valid for SAP SQL Anywhere"); 101 FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybaseiqutilities, 102 "IQ UTILITIES is Sybase IQ syntax and is not valid for SAP SQL Anywhere"); 103 FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybasedumpTran, 104 "DUMP TRANSACTION is Sybase ASE syntax; use BACKUP and is not valid for SAP SQL Anywhere"); 105 FORBIDDEN_MESSAGES.put(ESqlStatementType.sstmssqlreadtext, 106 "READTEXT is Sybase ASE syntax and is not valid for SAP SQL Anywhere"); 107 FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybaseupdateindexstatistics, 108 "UPDATE INDEX/ALL/TABLE STATISTICS is Sybase ASE syntax and is not valid for SAP SQL Anywhere"); 109 FORBIDDEN_MESSAGES.put(ESqlStatementType.sstsybaseDeleteStatistics, 110 "DELETE STATISTICS is Sybase ASE syntax; use DROP STATISTICS and is not valid for SAP SQL Anywhere"); 111 } 112 113 /** 114 * Post-parse policy validation ("recognize then reject"). Called by 115 * TGSqlParser after every statement is parsed. Attaches a vendor error to 116 * each recognised-but-foreign statement and returns them. 117 * 118 * <p>Not hooked at raw-extraction time: parsing REPLACES some statement 119 * objects (the refined statement is rebuilt from the parse-tree node — e.g. 120 * DELETE STATISTICS and READTEXT), and an error attached to the raw object 121 * vanishes with it. Post-parse, the statement list is final. This was 122 * measured, not assumed: raw-attached errors survived for DUMP TRAN but 123 * disappeared for DELETE STATISTICS and READTEXT. 124 */ 125 @Override 126 public java.util.List<TCustomSqlStatement> validateRecognizedStatements(TStatementList stmts) { 127 java.util.List<TCustomSqlStatement> rejected = new java.util.ArrayList<>(); 128 if (stmts == null) return rejected; 129 for (int i = 0; i < stmts.size(); i++) { 130 TCustomSqlStatement st = stmts.get(i); 131 if (st == null || !FORBIDDEN_STATEMENTS.contains(st.sqlstatementtype)) continue; 132 rejected.add(st); 133 TSourceToken tok = (st.sourcetokenlist != null && st.sourcetokenlist.size() > 0) 134 ? st.sourcetokenlist.get(0) : null; 135 String msg = FORBIDDEN_MESSAGES.get(st.sqlstatementtype); 136 if (tok != null) { 137 st.parseerrormessagehandle(new TSyntaxError(tok.toString(), tok.lineNo, 138 tok.columnNo, msg, EErrorType.sperror, 10101, st, tok.posinlist)); 139 } else { 140 st.parseerrormessagehandle(new TSyntaxError(msg, 0, 0, msg, 141 EErrorType.sperror, 10101, st, -1)); 142 } 143 } 144 return rejected; 145 } 146 147 148 /** 149 * Construct SQL Anywhere SQL parser. 150 * <p> 151 * Configures the parser for SQL Anywhere with default delimiter (;). 152 */ 153 public SqlAnywhereSqlParser() { 154 super(EDbVendor.dbvsqlanywhere); 155 this.delimiterChar = ';'; 156 this.defaultDelimiterStr = ";"; 157 158 // Create lexer once - will be reused for all parsing operations 159 this.flexer = new TLexerSqlanywhere(); 160 this.flexer.delimiterchar = this.delimiterChar; 161 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 162 163 // Set parent's lexer reference for shared tokenization logic 164 this.lexer = this.flexer; 165 166 // Create parser once - will be reused for all parsing operations 167 this.fparser = new TParserSqlanywhere(null); 168 this.fparser.lexer = this.flexer; 169 } 170 171 // ========== Parser Components ========== 172 173 /** The SQL Anywhere lexer used for tokenization */ 174 public TLexerSqlanywhere flexer; 175 176 /** SQL Anywhere parser (for SQL Anywhere statements) */ 177 private TParserSqlanywhere fparser; 178 179 /** Current statement being built during extraction */ 180 private TCustomSqlStatement gcurrentsqlstatement; 181 182 // ========== AbstractSqlParser Abstract Methods Implementation ========== 183 184 /** 185 * Return the SQL Anywhere lexer instance. 186 */ 187 @Override 188 protected TCustomLexer getLexer(ParserContext context) { 189 return this.flexer; 190 } 191 192 /** 193 * Return the SQL Anywhere parser instance with updated token list. 194 */ 195 @Override 196 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 197 this.fparser.sourcetokenlist = tokens; 198 return this.fparser; 199 } 200 201 /** 202 * Call SQL Anywhere-specific tokenization logic. 203 * <p> 204 * SQL Anywhere uses MSSQL-like tokenization. 205 */ 206 @Override 207 protected void tokenizeVendorSql() { 208 dosybasesqltexttotokenlist(); 209 } 210 211 /** 212 * Setup SQL Anywhere parser for raw statement extraction. 213 */ 214 @Override 215 protected void setupVendorParsersForExtraction() { 216 this.fparser.sqlcmds = this.sqlcmds; 217 this.fparser.sourcetokenlist = this.sourcetokenlist; 218 } 219 220 /** 221 * Call SQL Anywhere-specific raw statement extraction logic. 222 */ 223 @Override 224 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 225 dosybasegetrawsqlstatements(builder); 226 } 227 228 /** 229 * Perform full parsing of statements with syntax checking. 230 */ 231 @Override 232 protected TStatementList performParsing(ParserContext context, 233 TCustomParser parser, 234 TCustomParser secondaryParser, 235 TSourceTokenList tokens, 236 TStatementList rawStatements) { 237 this.fparser = (TParserSqlanywhere) parser; 238 this.sourcetokenlist = tokens; 239 this.parserContext = context; 240 this.sqlstatements = rawStatements; 241 242 this.sqlcmds = SqlCmdsFactory.get(vendor); 243 this.fparser.sqlcmds = this.sqlcmds; 244 245 if (context != null && context.getGsqlparser() != null) { 246 TGSqlParser gsqlparser = (TGSqlParser) context.getGsqlparser(); 247 this.frameStack = gsqlparser.getFrameStack(); 248 this.fparser.getNf().setGsqlParser(gsqlparser); 249 this.globalContext = new TContext(); 250 this.sqlEnv = new TSQLEnv(this.vendor) { 251 @Override 252 public void initSQLEnv() { 253 } 254 }; 255 this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements); 256 } else { 257 initializeGlobalContext(); 258 } 259 260 for (int i = 0; i < sqlstatements.size(); i++) { 261 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 262 263 try { 264 stmt.setFrameStack(frameStack); 265 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 266 267 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 268 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 269 handleCreateTableErrorRecovery(stmt); 270 } 271 272 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 273 copyErrorsFromStatement(stmt); 274 } 275 276 } catch (Exception ex) { 277 handleStatementParsingException(stmt, i, ex); 278 continue; 279 } 280 } 281 282 if (globalFrame != null) { 283 globalFrame.popMeFromStack(frameStack); 284 } 285 286 return this.sqlstatements; 287 } 288 289 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 290 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 291 || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) 292 && (!TBaseType.c_createTableStrictParsing)) { 293 294 int nested = 0; 295 boolean isIgnore = false, isFoundIgnoreToken = false; 296 TSourceToken firstIgnoreToken = null; 297 298 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 299 TSourceToken st = stmt.sourcetokenlist.get(k); 300 if (isIgnore) { 301 if (st.issolidtoken() && (st.tokencode != ';')) { 302 isFoundIgnoreToken = true; 303 if (firstIgnoreToken == null) { 304 firstIgnoreToken = st; 305 } 306 } 307 if (st.tokencode != ';') { 308 st.tokencode = TBaseType.sqlpluscmd; 309 } 310 continue; 311 } 312 if (st.tokencode == (int) ')') { 313 nested--; 314 if (nested == 0) { 315 boolean isSelect = false; 316 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 317 if (st1 != null) { 318 TSourceToken st2 = st.searchToken((int) '(', 2); 319 if (st2 != null) { 320 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 321 isSelect = (st3 != null); 322 } 323 } 324 if (!isSelect) isIgnore = true; 325 } 326 } else if (st.tokencode == (int) '(') { 327 nested++; 328 } 329 } 330 331 if (isFoundIgnoreToken) { 332 stmt.clearError(); 333 stmt.parsestatement(null, false); 334 } 335 } 336 } 337 338 @Override 339 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 340 if (context != null && context.getGsqlparser() != null) { 341 return; 342 } 343 344 if (getSyntaxErrors().isEmpty()) { 345 if (TBaseType.isEnableResolver2()) { 346 TSQLResolverConfig config = new TSQLResolverConfig(); 347 config.setVendor(vendor); 348 TSQLResolver2 resolver2 = new TSQLResolver2(null, statements, config); 349 if (this.sqlEnv != null) { 350 resolver2.setSqlEnv(this.sqlEnv); 351 } 352 resolver2.resolve(); 353 } else if (TBaseType.isEnableResolver()) { 354 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 355 resolver.resolve(); 356 } 357 } 358 } 359 360 @Override 361 protected void performInterpreter(ParserContext context, TStatementList statements) { 362 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 363 TLog.clearLogs(); 364 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 365 TLog.enableInterpreterLogOnly(); 366 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 367 astEvaluator.eval(); 368 } 369 } 370 371 // ========== Helper Methods ========== 372 373 /** 374 * Add token to current statement with proper statement linkage. 375 * This replicates TCustomSqlStatement.addtokentolist() behavior 376 * which sets st.stmt = this before adding. 377 */ 378 private void addTokenToStatement(TSourceToken st) { 379 st.stmt = gcurrentsqlstatement; 380 gcurrentsqlstatement.sourcetokenlist.add(st); 381 } 382 383 // ========== SQL Anywhere-Specific Tokenization ========== 384 385 /** 386 * SQL Anywhere-specific tokenization logic. 387 * <p> 388 * SQL Anywhere uses MSSQL-like tokenization. 389 * Migrated from TGSqlParser.dosybasesqltexttotokenlist() 390 */ 391 private void dosybasesqltexttotokenlist() { 392 TSourceToken lcprevtoken = null; 393 int lcsteps = 0; 394 TSourceToken asourcetoken, lctoken, lctoken2, lctoken3; 395 int yychar; 396 boolean iskeywordgo; 397 398 asourcetoken = getanewsourcetoken(); 399 400 if (asourcetoken == null) return; 401 402 yychar = asourcetoken.tokencode; 403 404 boolean lcinopenrowset = false; 405 int lcnested = 0; 406 407 while (yychar > 0) { 408 409 if (asourcetoken.tokencode == TBaseType.rrw_openrowset) { 410 lcinopenrowset = true; 411 lcnested = 0; 412 } else if (asourcetoken.tokentype == ETokenType.ttleftparenthesis) { 413 if ((lcsteps > 0) && TBaseType.assigned(lcprevtoken)) { 414 if (lcprevtoken.tokencode == TBaseType.rrw_primary) { 415 lcprevtoken.tokencode = TBaseType.rrw_select - 2; 416 checkconstarinttoken(lcprevtoken); 417 } else if (lcprevtoken.tokencode == TBaseType.rrw_foreign) { 418 lcprevtoken.tokencode = TBaseType.rrw_select - 4; 419 checkconstarinttoken(lcprevtoken); 420 } else if (lcprevtoken.tokencode == TBaseType.rrw_unique) { 421 lcprevtoken.tokencode = TBaseType.rrw_select - 1; 422 checkconstarinttoken(lcprevtoken); 423 } 424 lcprevtoken = null; 425 lcsteps = 0; 426 } 427 428 if (lcinopenrowset) 429 lcnested++; 430 } else if (asourcetoken.tokentype == ETokenType.ttrightparenthesis) { 431 if (lcinopenrowset) { 432 if ((lcnested > 0)) 433 lcnested--; 434 if (lcnested == 0) 435 lcinopenrowset = false; 436 } 437 } else if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 438 if (lcinopenrowset) { 439 asourcetoken.tokentype = ETokenType.ttsemicolon2; 440 } else { 441 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_begin, -1); 442 if (lctoken2 != null) { 443 asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN; 444 asourcetoken.tokentype = ETokenType.ttsemicolon3; 445 } else { 446 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_try, -1); 447 if (lctoken2 == null) { 448 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_catch, -1); 449 } 450 if (lctoken2 != null) { 451 lctoken3 = asourcetoken.searchToken(TBaseType.rrw_begin, -2); 452 if (lctoken3 != null) { 453 asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN; 454 asourcetoken.tokentype = ETokenType.ttsemicolon3; 455 } 456 } 457 } 458 } 459 } else if (asourcetoken.tokentype == ETokenType.ttperiod) { 460 lctoken = getprevtoken(asourcetoken); 461 if (TBaseType.assigned(lctoken)) { 462 if (lctoken.tokencode == TBaseType.rrw_go) { 463 lctoken.tokencode = TBaseType.ident; 464 lctoken.tokentype = ETokenType.ttidentifier; 465 } 466 } 467 } else if (asourcetoken.tokencode == TBaseType.rrw_table) { 468 lctoken = getprevtoken(asourcetoken); 469 if (TBaseType.assigned(lctoken)) { 470 if (lctoken.tokencode == TBaseType.rrw_lock) { 471 lctoken.tokencode = TBaseType.rw_locktable; 472 } 473 } 474 } else if (asourcetoken.tokencode == TBaseType.rrw_sybase_isolation) { 475 // AT ISOLATION clause - change 'at' token code for grammar recognition 476 lctoken = getprevtoken(asourcetoken); 477 if (TBaseType.assigned(lctoken)) { 478 if (lctoken.tokencode == TBaseType.rrw_sybase_at) { 479 lctoken.tokencode = TBaseType.rw_sybase_at1; 480 } 481 } 482 } else if (asourcetoken.tokencode == TBaseType.rrw_update) { 483 // UPDATE in create trigger context 484 lctoken = getprevtoken(asourcetoken); 485 if (TBaseType.assigned(lctoken)) { 486 if ((lctoken.tokencode == TBaseType.rrw_not) 487 || (lctoken.tokencode == TBaseType.rrw_and) 488 || (lctoken.tokencode == TBaseType.rrw_or) 489 || (lctoken.tokencode == TBaseType.rrw_if) 490 ) { 491 // this is update(column) in create trigger 492 asourcetoken.tokencode = TBaseType.rw_sybase_update1; 493 } 494 } 495 } else if (asourcetoken.tokencode == TBaseType.rrw_go) { 496 iskeywordgo = true; 497 lctoken = getprevtoken(asourcetoken); 498 if (TBaseType.assigned(lctoken)) { 499 if ((lctoken.lineNo == asourcetoken.lineNo) && (lctoken.tokencode != ';')) { 500 iskeywordgo = false; 501 } 502 } 503 504 if (iskeywordgo) { 505 lcinopenrowset = false; 506 lcnested = 0; 507 lcprevtoken = asourcetoken; 508 } else { 509 asourcetoken.tokencode = TBaseType.ident; 510 asourcetoken.tokentype = ETokenType.ttidentifier; 511 } 512 } else if (asourcetoken.tokencode == TBaseType.rrw_primary) { 513 lcsteps = 2; 514 lcprevtoken = asourcetoken; 515 } else if (asourcetoken.tokencode == TBaseType.rrw_foreign) { 516 lcsteps = 2; 517 lcprevtoken = asourcetoken; 518 } else if (asourcetoken.tokencode == TBaseType.rrw_unique) { 519 lcsteps = 1; 520 lcprevtoken = asourcetoken; 521 } else if (asourcetoken.issolidtoken()) { 522 if (lcsteps > 0) { 523 if (!(TBaseType.mysametext("clustered", asourcetoken.toString()) 524 || TBaseType.mysametext("nonclustered", asourcetoken.toString()) 525 || TBaseType.mysametext("hash", asourcetoken.toString()) 526 )) 527 lcsteps--; 528 } 529 } 530 531 sourcetokenlist.add(asourcetoken); 532 533 asourcetoken = getanewsourcetoken(); 534 if (asourcetoken == null) break; 535 yychar = asourcetoken.tokencode; 536 } 537 } 538 539 private TSourceToken getprevtoken(TSourceToken ptoken) { 540 TSourceTokenList lcstlist = ptoken.container; 541 if (TBaseType.assigned(lcstlist)) { 542 if ((ptoken.posinlist > 0) && (lcstlist.size() > ptoken.posinlist - 1)) { 543 if (!((lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace) 544 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn) 545 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment) 546 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment) 547 )) { 548 return lcstlist.get(ptoken.posinlist - 1); 549 } else { 550 return lcstlist.nextsolidtoken(ptoken.posinlist - 1, -1, false); 551 } 552 } 553 } 554 return null; 555 } 556 557 private void checkconstarinttoken(TSourceToken token) { 558 TSourceTokenList lcStList = token.container; 559 if (TBaseType.assigned(lcStList)) { 560 TSourceToken lcPPToken = lcStList.nextsolidtoken(token.posinlist, -2, false); 561 if (TBaseType.assigned(lcPPToken)) { 562 if (lcPPToken.tokencode == flexer.getkeywordvalue("constraint")) { 563 lcPPToken.tokencode = TBaseType.rw_constraint2; 564 } 565 } 566 } 567 } 568 569 // ========== SQL Anywhere-Specific Raw Statement Extraction ========== 570 571 /** 572 * SQL Anywhere-specific raw statement extraction logic. 573 * <p> 574 * Migrated from TGSqlParser.dosybasegetrawsqlstatements() 575 */ 576 private void dosybasegetrawsqlstatements(SqlParseResult.Builder builder) { 577 int errorcount = 0; 578 int case_end_nest = 0; 579 // Watcom / SAP IQ / SQL Anywhere `IF cond THEN ... END IF` tracking. 580 // `lcwatcomIfNest` counts open IF...THEN blocks awaiting END IF or 581 // ENDIF inside a BEGIN/END block. `lcwhenPending` counts WHEN tokens 582 // awaiting their matching THEN so that CASE WHEN ... THEN, MERGE 583 // WHEN ... THEN and EXCEPTION WHEN ... THEN do not look like Watcom 584 // IF openers. The counters are only used in the stblock state — at 585 // top level we still rely on the standard statement-splitter path. 586 int lcwatcomIfNest = 0; 587 int lcwhenPending = 0; 588 // Mantis 4717: the block levels of the stblock state that a CASE opened, and the last END that 589 // closed a CASE (in stblock or stsql). The CASE of `END CASE` follows exactly that END; a CASE after 590 // the END of a BEGIN block starts a new CASE, even with no semicolon between them. 591 BitSet lccaseopenedlevels = new BitSet(); 592 TSourceToken lcendofcase = null; 593 594 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 595 if (!TBaseType.assigned(sourcetokenlist)) { 596 builder.errorCode(-1); 597 builder.errorMessage("Source token list is null"); 598 return; 599 } 600 601 gcurrentsqlstatement = null; 602 EFindSqlStateType gst = EFindSqlStateType.stnormal; 603 int lcblocklevel = 0; 604 int lctrycatchlevel = 0; 605 TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken; 606 TSourceToken ast = null; 607 int i; 608 boolean lcisendconversation, lcstillinsql; 609 610 for (i = 0; i < sourcetokenlist.size(); i++) { 611 612 if ((ast != null) && (ast.issolidtoken())) 613 lcprevsolidtoken = ast; 614 615 ast = sourcetokenlist.get(i); 616 sourcetokenlist.curpos = i; 617 if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) { 618 addTokenToStatement(ast); 619 continue; 620 } 621 622 if (gst == EFindSqlStateType.ststoredprocedurebody) { 623 if (!((ast.tokencode == TBaseType.rrw_go) 624 || (ast.tokencode == TBaseType.rrw_create) 625 || (ast.tokencode == TBaseType.rrw_alter))) { 626 addTokenToStatement(ast); 627 continue; 628 } 629 } 630 631 TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 632 633 switch (gst) { 634 case sterror: { 635 if (TBaseType.assigned(lcnextsqlstatement)) { 636 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 637 gcurrentsqlstatement = lcnextsqlstatement; 638 addTokenToStatement(ast); 639 gst = EFindSqlStateType.stsql; 640 } else if ((ast.tokentype == ETokenType.ttsemicolon)) { 641 addTokenToStatement(ast); 642 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 643 gst = EFindSqlStateType.stnormal; 644 } else { 645 addTokenToStatement(ast); 646 } 647 break; 648 } 649 case stnormal: { 650 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 651 || (ast.tokencode == TBaseType.cmtslashstar) 652 || (ast.tokencode == TBaseType.lexspace) 653 || (ast.tokencode == TBaseType.lexnewline) 654 || (ast.tokentype == ETokenType.ttsemicolon)) { 655 if (TBaseType.assigned(gcurrentsqlstatement)) { 656 addTokenToStatement(ast); 657 } 658 659 if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) { 660 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 661 ast.tokentype = ETokenType.ttsimplecomment; 662 ast.tokencode = TBaseType.cmtdoublehyphen; 663 } 664 } 665 666 continue; 667 } 668 669 gcurrentsqlstatement = lcnextsqlstatement; 670 671 if (TBaseType.assigned(gcurrentsqlstatement)) { 672 switch (gcurrentsqlstatement.sqlstatementtype) { 673 case sstmssqlcreateprocedure: 674 case sstmssqlcreatefunction: 675 case sstcreatetrigger: 676 case sstmssqlalterprocedure: 677 case sstmssqlalterfunction: 678 case sstmssqlaltertrigger: { 679 addTokenToStatement(ast); 680 gst = EFindSqlStateType.ststoredprocedure; 681 break; 682 } 683 case sstmssqlbegintry: 684 case sstmssqlbegincatch: { 685 addTokenToStatement(ast); 686 gst = EFindSqlStateType.sttrycatch; 687 lctrycatchlevel = 0; 688 break; 689 } 690 case sstmssqlgo: { 691 addTokenToStatement(ast); 692 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 693 gst = EFindSqlStateType.stnormal; 694 break; 695 } 696 default: { 697 addTokenToStatement(ast); 698 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqllabel) { 699 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 700 gst = EFindSqlStateType.stnormal; 701 } else { 702 gst = EFindSqlStateType.stsql; 703 } 704 break; 705 } 706 } 707 } else { 708 if (ast.tokencode == TBaseType.rrw_begin) { 709 gcurrentsqlstatement = new TMssqlBlock(vendor); 710 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 711 addTokenToStatement(ast); 712 gst = EFindSqlStateType.stblock; 713 } else { 714 if (sqlstatements.size() == 0) { 715 gst = EFindSqlStateType.stsql; 716 gcurrentsqlstatement = new TMssqlExecute(vendor); 717 ast.tokencode = TBaseType.rrw_sybase_exce_proc_name; 718 addTokenToStatement(ast); 719 } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) { 720 gst = EFindSqlStateType.stsql; 721 gcurrentsqlstatement = new TMssqlExecute(vendor); 722 ast.tokencode = TBaseType.rrw_sybase_exce_proc_name; 723 addTokenToStatement(ast); 724 } 725 } 726 } 727 728 if (!TBaseType.assigned(gcurrentsqlstatement)) { 729 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 730 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 731 732 ast.tokentype = ETokenType.tttokenlizererrortoken; 733 gst = EFindSqlStateType.sterror; 734 735 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 736 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 737 addTokenToStatement(ast); 738 } 739 break; 740 } 741 case stblock: { 742 if (TBaseType.assigned(lcnextsqlstatement)) { 743 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 744 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 745 gcurrentsqlstatement = lcnextsqlstatement; 746 addTokenToStatement(ast); 747 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 748 gst = EFindSqlStateType.stnormal; 749 } else { 750 lcnextsqlstatement = null; 751 } 752 } 753 754 if (gst == EFindSqlStateType.stblock) { 755 addTokenToStatement(ast); 756 if (ast.tokencode == TBaseType.rrw_begin) { 757 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 758 if (TBaseType.assigned(lcnextsolidtoken)) { 759 if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran") 760 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction") 761 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "distributed") 762 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "dialog") 763 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "conversation") 764 )) 765 lcblocklevel++; 766 } else 767 lcblocklevel++; 768 769 } else if (ast.tokencode == TBaseType.rrw_case) { 770 // The CASE of `END CASE` ends a Watcom CASE statement (Mantis 4717): its END already 771 // closed the level that statement's CASE opened, so it opens nothing. Only an END 772 // that closed a CASE counts; after the END of a BEGIN block, CASE opens a level. 773 if (!(TBaseType.assigned(lcprevsolidtoken) && lcprevsolidtoken == lcendofcase)) { 774 lcblocklevel++; 775 lccaseopenedlevels.set(lcblocklevel); 776 } 777 } else if (ast.tokencode == TBaseType.rrw_when) { 778 // Each WHEN expects a matching THEN (CASE, MERGE, 779 // EXCEPTION). Track so that THEN does not get 780 // confused with the Watcom IF...THEN opener. 781 lcwhenPending++; 782 } else if (ast.tokencode == TBaseType.rrw_then) { 783 if (lcwhenPending > 0) 784 lcwhenPending--; 785 else 786 lcwatcomIfNest++; 787 } else if (ast.tokencode == flexer.getkeywordvalue("ENDIF")) { 788 // One-word ENDIF closes a Watcom IF if one is open. 789 if (lcwatcomIfNest > 0) 790 lcwatcomIfNest--; 791 } else if (ast.tokencode == TBaseType.rrw_end) { 792 793 lcisendconversation = false; 794 // Watcom/SQL Anywhere/SQL Anywhere IQ: `END IF` closes a 795 // Watcom IF statement only when there is an open 796 // IF...THEN to match. Without that match, `END` 797 // followed by `IF` is just a block-close followed 798 // by a new T-SQL `IF` statement (semicolons are 799 // optional in SQL Anywhere/T-SQL), so we must still 800 // decrement the block level normally. 801 boolean lcisendif = false; 802 803 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 804 if (TBaseType.assigned(lcnextsolidtoken)) { 805 if (lcnextsolidtoken.tokencode == flexer.getkeywordvalue("CONVERSATION")) 806 lcisendconversation = true; 807 else if (lcnextsolidtoken.tokencode == TBaseType.rrw_if 808 && lcwatcomIfNest > 0) { 809 lcisendif = true; 810 lcwatcomIfNest--; 811 } 812 } 813 814 if (!lcisendconversation && !lcisendif) { 815 816 if (lcblocklevel == 0) { 817 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 818 if (TBaseType.assigned(lcnextsolidtoken)) { 819 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 820 gst = EFindSqlStateType.stsql; 821 } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) { 822 lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false); 823 if (TBaseType.assigned(lcnnextsolidtoken)) { 824 if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) { 825 gst = EFindSqlStateType.stsql; 826 } 827 } 828 } 829 } 830 } 831 832 if (gst != EFindSqlStateType.stsql) { 833 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 834 gst = EFindSqlStateType.stnormal; 835 } 836 837 } else { 838 if (lccaseopenedlevels.get(lcblocklevel)) { 839 lccaseopenedlevels.clear(lcblocklevel); 840 lcendofcase = ast; 841 } 842 lcblocklevel--; 843 } 844 845 } 846 } 847 } 848 break; 849 } 850 case sttrycatch: { 851 852 if (TBaseType.assigned(lcnextsqlstatement)) { 853 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 854 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 855 gcurrentsqlstatement = lcnextsqlstatement; 856 addTokenToStatement(ast); 857 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 858 gst = EFindSqlStateType.stnormal; 859 } else { 860 if ( 861 (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry) 862 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 863 ) 864 lctrycatchlevel++; 865 lcnextsqlstatement = null; 866 } 867 } 868 869 if (gst == EFindSqlStateType.sttrycatch) { 870 addTokenToStatement(ast); 871 if ((ast.tokencode == TBaseType.rrw_try) || 872 (ast.tokencode == TBaseType.rrw_catch)) { 873 if (TBaseType.assigned(lcprevsolidtoken)) { 874 if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) { 875 if (lctrycatchlevel == 0) { 876 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 877 gst = EFindSqlStateType.stnormal; 878 } else 879 lctrycatchlevel--; 880 } 881 } 882 } 883 } 884 break; 885 } 886 case stsql: { 887 if ((ast.tokentype == ETokenType.ttsemicolon)) { 888 lcstillinsql = false; 889 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 890 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 891 if (TBaseType.assigned(lcnextsolidtoken)) { 892 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 893 addTokenToStatement(ast); 894 lcstillinsql = true; 895 } 896 897 } 898 } 899 900 if (!lcstillinsql) { 901 gst = EFindSqlStateType.stnormal; 902 addTokenToStatement(ast); 903 gcurrentsqlstatement.semicolonended = ast; 904 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 905 } 906 907 } else if (TBaseType.assigned(lcnextsqlstatement)) { 908 909 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 910 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 911 gcurrentsqlstatement = lcnextsqlstatement; 912 addTokenToStatement(ast); 913 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 914 gst = EFindSqlStateType.stnormal; 915 continue; 916 } 917 918 switch (gcurrentsqlstatement.sqlstatementtype) { 919 case sstmssqlif: 920 case sstmssqlwhile: { 921 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 922 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) { 923 addTokenToStatement(ast); 924 gst = EFindSqlStateType.stblock; 925 lcblocklevel = 1; 926 lccaseopenedlevels.clear(); 927 lcnextsqlstatement = null; 928 continue; 929 930 } else if (gcurrentsqlstatement.dummytag == 1) { 931 addTokenToStatement(ast); 932 933 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 934 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) 935 gcurrentsqlstatement.dummytag = 1; 936 else 937 gcurrentsqlstatement.dummytag = 0; 938 939 940 lcnextsqlstatement = null; 941 continue; 942 } 943 break; 944 } 945 case sstmssqlalterqueue: { 946 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) { 947 addTokenToStatement(ast); 948 949 lcnextsqlstatement = null; 950 continue; 951 952 } 953 break; 954 } 955 case sstmssqlcreateschema: { 956 addTokenToStatement(ast); 957 lcnextsqlstatement = null; 958 continue; 959 } 960 } 961 962 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 963 gcurrentsqlstatement = lcnextsqlstatement; 964 addTokenToStatement(ast); 965 966 switch (gcurrentsqlstatement.sqlstatementtype) { 967 case sstmssqlcreateprocedure: 968 case sstmssqlcreatefunction: 969 case sstcreatetrigger: 970 case sstmssqlalterprocedure: 971 case sstmssqlalterfunction: 972 case sstmssqlaltertrigger: { 973 gst = EFindSqlStateType.ststoredprocedure; 974 break; 975 } 976 case sstmssqlbegintry: 977 case sstmssqlbegincatch: { 978 gst = EFindSqlStateType.sttrycatch; 979 lctrycatchlevel = 0; 980 break; 981 } 982 case sstmssqlgo: { 983 gst = EFindSqlStateType.stnormal; 984 break; 985 } 986 default: { 987 gst = EFindSqlStateType.stsql; 988 break; 989 } 990 } 991 992 } else if ((ast.tokencode == TBaseType.rrw_begin)) { 993 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 994 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) { 995 gst = EFindSqlStateType.stblock; 996 lcblocklevel = 0; 997 lccaseopenedlevels.clear(); 998 addTokenToStatement(ast); 999 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqldeclare) { 1000 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1001 gcurrentsqlstatement = new TMssqlBlock(vendor); 1002 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 1003 addTokenToStatement(ast); 1004 gst = EFindSqlStateType.stblock; 1005 } else { 1006 addTokenToStatement(ast); 1007 } 1008 } else if ((ast.tokencode == TBaseType.rrw_case)) { 1009 // The CASE of `END CASE` ends a Watcom CASE statement (Mantis 4717); it opens nothing. 1010 if (!(TBaseType.assigned(lcprevsolidtoken) && lcprevsolidtoken == lcendofcase)) 1011 case_end_nest++; 1012 addTokenToStatement(ast); 1013 } else if ((ast.tokencode == TBaseType.rrw_end)) { 1014 if (case_end_nest > 0) { 1015 case_end_nest--; 1016 lcendofcase = ast; 1017 } 1018 addTokenToStatement(ast); 1019 } else if ((ast.tokencode == TBaseType.rrw_else)) { 1020 addTokenToStatement(ast); 1021 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 1022 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 1023 ) && (case_end_nest == 0)) 1024 gcurrentsqlstatement.dummytag = 1; 1025 } else { 1026 addTokenToStatement(ast); 1027 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlset) { 1028 if (ast.tokencode == TBaseType.rrw_on) { 1029 gst = EFindSqlStateType.stnormal; 1030 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1031 } 1032 } 1033 } 1034 break; 1035 } 1036 case ststoredprocedure: { 1037 if (TBaseType.assigned(lcnextsqlstatement)) { 1038 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1039 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1040 gcurrentsqlstatement = lcnextsqlstatement; 1041 addTokenToStatement(ast); 1042 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1043 gst = EFindSqlStateType.stnormal; 1044 } else { 1045 gst = EFindSqlStateType.ststoredprocedurebody; 1046 addTokenToStatement(ast); 1047 1048 lcnextsqlstatement = null; 1049 } 1050 } 1051 1052 if (gst == EFindSqlStateType.ststoredprocedure) { 1053 addTokenToStatement(ast); 1054 if (ast.tokencode == TBaseType.rrw_begin) { 1055 gst = EFindSqlStateType.stblock; 1056 } 1057 } 1058 break; 1059 } 1060 case ststoredprocedurebody: { 1061 if (TBaseType.assigned(lcnextsqlstatement)) { 1062 switch (lcnextsqlstatement.sqlstatementtype) { 1063 case sstmssqlgo: { 1064 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1065 gcurrentsqlstatement = lcnextsqlstatement; 1066 addTokenToStatement(ast); 1067 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1068 gst = EFindSqlStateType.stnormal; 1069 break; 1070 } 1071 case sstmssqlcreateprocedure: 1072 case sstmssqlcreatefunction: 1073 case sstcreatetrigger: 1074 case sstmssqlalterprocedure: 1075 case sstmssqlalterfunction: 1076 case sstmssqlaltertrigger: { 1077 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1078 gcurrentsqlstatement = lcnextsqlstatement; 1079 addTokenToStatement(ast); 1080 gst = EFindSqlStateType.ststoredprocedure; 1081 break; 1082 } 1083 default: { 1084 lcnextsqlstatement = null; 1085 break; 1086 } 1087 } 1088 } 1089 1090 if (gst == EFindSqlStateType.ststoredprocedurebody) 1091 addTokenToStatement(ast); 1092 } 1093 break; 1094 } 1095 } 1096 1097 // last statement 1098 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 1099 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 1100 } 1101 1102 builder.sqlStatements(this.sqlstatements); 1103 builder.errorCode(errorcount); 1104 builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount)); 1105 } 1106}