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