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.TLexerCouchbase; 009import gudusoft.gsqlparser.TParserCouchbase; 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.oracle.TSqlplusCmdStatement; 020import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 021import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 022import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 023import gudusoft.gsqlparser.compiler.TContext; 024import gudusoft.gsqlparser.sqlenv.TSQLEnv; 025import gudusoft.gsqlparser.compiler.TGlobalScope; 026import gudusoft.gsqlparser.compiler.TFrame; 027 028import java.io.BufferedReader; 029import java.util.ArrayList; 030import java.util.List; 031import java.util.Stack; 032 033/** 034 * Couchbase database SQL parser implementation. 035 * 036 * <p>This parser handles Couchbase-specific SQL syntax including: 037 * <ul> 038 * <li>N1QL queries (SELECT, INSERT, UPDATE, DELETE)</li> 039 * <li>Couchbase-specific functions and operators</li> 040 * <li>Document-oriented query features</li> 041 * </ul> 042 * 043 * <p><b>Design Notes:</b> 044 * <ul> 045 * <li>Extends {@link AbstractSqlParser}</li> 046 * <li>Can directly instantiate: {@link TLexerCouchbase}, {@link TParserCouchbase}</li> 047 * <li>Uses single parser (no secondary parser)</li> 048 * <li>Uses PostgreSQL-style tokenization</li> 049 * <li>Delimiter character: ';' for SQL statements</li> 050 * </ul> 051 * 052 * <p><b>Usage Example:</b> 053 * <pre> 054 * // Get Couchbase parser from factory 055 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvcouchbase); 056 * 057 * // Build context 058 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvcouchbase) 059 * .sqlText("SELECT * FROM bucket WHERE type = 'user'") 060 * .build(); 061 * 062 * // Parse 063 * SqlParseResult result = parser.parse(context); 064 * 065 * // Access statements 066 * TStatementList statements = result.getSqlStatements(); 067 * </pre> 068 * 069 * @see SqlParser 070 * @see AbstractSqlParser 071 * @see TLexerCouchbase 072 * @see TParserCouchbase 073 * @since 3.2.0.0 074 */ 075public class CouchbaseSqlParser extends AbstractSqlParser { 076 077 // ========== Lexer and Parser Instances ========== 078 // Created once in constructor, reused for all parsing operations 079 080 /** The Couchbase lexer used for tokenization - public for TGSqlParser compatibility */ 081 public TLexerCouchbase flexer; 082 private TParserCouchbase fparser; 083 084 // ========== State Variables ========== 085 // NOTE: The following fields moved to AbstractSqlParser (inherited): 086 // - sourcetokenlist (TSourceTokenList) 087 // - sqlstatements (TStatementList) 088 // - parserContext (ParserContext) 089 // - sqlcmds (ISqlCmds) 090 // - globalContext (TContext) 091 // - sqlEnv (TSQLEnv) 092 // - frameStack (Stack<TFrame>) 093 // - globalFrame (TFrame) 094 095 // ========== State Variables for Tokenization ========== 096 private boolean insqlpluscmd; 097 private boolean isvalidplace; 098 private boolean waitingreturnforsemicolon; 099 private boolean waitingreturnforfloatdiv; 100 private boolean continuesqlplusatnewline; 101 102 // ========== Constructor ========== 103 104 /** 105 * Construct Couchbase SQL parser. 106 * <p> 107 * Configures the parser for Couchbase database with default delimiter: semicolon (;) 108 * <p> 109 * Following the original TGSqlParser pattern, the lexer and parser are 110 * created once in the constructor and reused for all parsing operations. 111 */ 112 public CouchbaseSqlParser() { 113 super(EDbVendor.dbvcouchbase); 114 115 // Set delimiter character 116 this.delimiterChar = ';'; 117 this.defaultDelimiterStr = ";"; 118 119 // Create lexer once - will be reused for all parsing operations 120 this.flexer = new TLexerCouchbase(); 121 this.flexer.delimiterchar = this.delimiterChar; 122 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 123 124 // CRITICAL: Set lexer for inherited getanewsourcetoken() method 125 this.lexer = this.flexer; 126 127 // Create parser once - will be reused for all parsing operations 128 this.fparser = new TParserCouchbase(null); 129 this.fparser.lexer = this.flexer; 130 131 // NOTE: sourcetokenlist and sqlstatements are initialized in AbstractSqlParser constructor 132 } 133 134 // ========== AbstractSqlParser Abstract Methods Implementation ========== 135 136 /** 137 * Return the Couchbase lexer instance. 138 * <p> 139 * The lexer is created once in the constructor and reused for all 140 * parsing operations. This method simply returns the existing instance, 141 * matching the original TGSqlParser pattern where the lexer is created 142 * once and reset before each use. 143 * 144 * @param context parser context (not used, lexer already created) 145 * @return the Couchbase lexer instance created in constructor 146 */ 147 @Override 148 protected TCustomLexer getLexer(ParserContext context) { 149 // Return existing lexer instance (created in constructor) 150 return this.flexer; 151 } 152 153 /** 154 * Return the Couchbase SQL parser instance with updated token list. 155 * <p> 156 * The parser is created once in the constructor and reused for all 157 * parsing operations. This method updates the token list and returns 158 * the existing instance, matching the original TGSqlParser pattern. 159 * 160 * @param context parser context (not used, parser already created) 161 * @param tokens source token list to parse 162 * @return the Couchbase SQL parser instance created in constructor 163 */ 164 @Override 165 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 166 // Update token list for reused parser instance 167 this.fparser.sourcetokenlist = tokens; 168 return this.fparser; 169 } 170 171 /** 172 * Call Couchbase-specific tokenization logic. 173 * <p> 174 * Couchbase uses PostgreSQL-style tokenization, so delegates to 175 * docouchbasesqltexttotokenlist which in turn calls dopostgresqltexttotokenlist. 176 */ 177 @Override 178 protected void tokenizeVendorSql() { 179 docouchbasesqltexttotokenlist(); 180 } 181 182 /** 183 * Setup Couchbase parser for raw statement extraction. 184 * <p> 185 * Couchbase uses a single parser, so we inject sqlcmds and update 186 * the token list for the main parser only. 187 */ 188 @Override 189 protected void setupVendorParsersForExtraction() { 190 this.fparser.sqlcmds = this.sqlcmds; 191 this.fparser.sourcetokenlist = this.sourcetokenlist; 192 } 193 194 /** 195 * Call Couchbase-specific raw statement extraction logic. 196 * <p> 197 * Delegates to docouchbasegetrawsqlstatements which handles Couchbase's 198 * statement delimiters and BEGIN/END block processing. 199 */ 200 @Override 201 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 202 docouchbasegetrawsqlstatements(builder); 203 } 204 205 /** 206 * Perform full parsing of statements with syntax checking. 207 * <p> 208 * This method orchestrates the parsing of all statements. 209 * 210 * <p><b>Important:</b> This method does NOT extract raw statements - they are 211 * passed in as a parameter already extracted by {@link #extractRawStatements}. 212 * 213 * @param context parser context 214 * @param parser main SQL parser (TParserCouchbase) 215 * @param secondaryParser not used for Couchbase 216 * @param tokens source token list 217 * @param rawStatements raw statements already extracted (never null) 218 * @return list of fully parsed statements with AST built 219 */ 220 @Override 221 protected TStatementList performParsing(ParserContext context, 222 TCustomParser parser, 223 TCustomParser secondaryParser, 224 TSourceTokenList tokens, 225 TStatementList rawStatements) { 226 // Store references 227 this.sourcetokenlist = tokens; 228 this.parserContext = context; 229 230 // Use the raw statements passed from AbstractSqlParser.parse() 231 // (already extracted - DO NOT re-extract to avoid duplication) 232 this.sqlstatements = rawStatements; 233 234 // If no raw statements, return empty list 235 if (this.sqlstatements == null || this.sqlstatements.size() == 0) { 236 return this.sqlstatements; 237 } 238 239 // Initialize global context for statement parsing 240 initializeGlobalContext(); 241 242 // Parse each statement 243 for (int i = 0; i < sqlstatements.size(); i++) { 244 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 245 246 // Set frame stack for the statement (needed for parsing) 247 stmt.setFrameStack(frameStack); 248 249 // Parse the statement 250 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 251 252 // Collect syntax errors 253 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 254 copyErrorsFromStatement(stmt); 255 } 256 } 257 258 // Clean up frame stack 259 if (globalFrame != null) { 260 globalFrame.popMeFromStack(frameStack); 261 } 262 263 return this.sqlstatements; 264 } 265 266 // ========== Couchbase-Specific Tokenization ========== 267 268 /** 269 * Perform Couchbase-specific tokenization. 270 * <p> 271 * Couchbase uses PostgreSQL-style tokenization (from TGSqlParser line 3445-3447). 272 * Delegates to dopostgresqltexttotokenlist(). 273 */ 274 private void docouchbasesqltexttotokenlist() { 275 dopostgresqltexttotokenlist(); 276 } 277 278 /** 279 * Perform PostgreSQL-style tokenization (used by Couchbase). 280 * <p> 281 * Copied from PostgreSqlParser.dopostgresqltexttotokenlist() to ensure identical behavior. 282 */ 283 private void dopostgresqltexttotokenlist() { 284 // Initialize state machine 285 insqlpluscmd = false; 286 isvalidplace = true; 287 waitingreturnforfloatdiv = false; 288 waitingreturnforsemicolon = false; 289 continuesqlplusatnewline = false; 290 291 TSourceToken lct = null, prevst = null; 292 TSourceToken asourcetoken, lcprevst; 293 int yychar; 294 295 asourcetoken = getanewsourcetoken(); 296 if (asourcetoken == null) return; 297 yychar = asourcetoken.tokencode; 298 299 while (yychar > 0) { 300 sourcetokenlist.add(asourcetoken); 301 302 switch (yychar) { 303 case TBaseType.cmtdoublehyphen: 304 case TBaseType.cmtslashstar: 305 case TBaseType.lexspace: { 306 if (insqlpluscmd) { 307 asourcetoken.insqlpluscmd = true; 308 } 309 break; 310 } 311 312 case TBaseType.lexnewline: { 313 if (insqlpluscmd) { 314 insqlpluscmd = false; 315 isvalidplace = true; 316 317 if (continuesqlplusatnewline) { 318 insqlpluscmd = true; 319 isvalidplace = false; 320 asourcetoken.insqlpluscmd = true; 321 } 322 } 323 324 if (waitingreturnforsemicolon) { 325 isvalidplace = true; 326 } 327 if (waitingreturnforfloatdiv) { 328 isvalidplace = true; 329 lct.tokencode = TBaseType.sqlpluscmd; 330 if (lct.tokentype != ETokenType.ttslash) { 331 lct.tokentype = ETokenType.ttsqlpluscmd; 332 } 333 } 334 flexer.insqlpluscmd = insqlpluscmd; 335 break; 336 } 337 338 default: { 339 // Solid token 340 continuesqlplusatnewline = false; 341 waitingreturnforsemicolon = false; 342 waitingreturnforfloatdiv = false; 343 344 if (insqlpluscmd) { 345 asourcetoken.insqlpluscmd = true; 346 if (asourcetoken.toString().equalsIgnoreCase("-")) { 347 continuesqlplusatnewline = true; 348 } 349 } else { 350 if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 351 waitingreturnforsemicolon = true; 352 } 353 if ((asourcetoken.tokentype == ETokenType.ttslash) 354 && (isvalidplace || (isValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) { 355 lct = asourcetoken; 356 waitingreturnforfloatdiv = true; 357 } 358 if ((isvalidplace) && isvalidsqlpluscmdInPostgresql(asourcetoken.toString())) { 359 asourcetoken.tokencode = TBaseType.sqlpluscmd; 360 if (asourcetoken.tokentype != ETokenType.ttslash) { 361 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 362 } 363 insqlpluscmd = true; 364 flexer.insqlpluscmd = insqlpluscmd; 365 } 366 } 367 isvalidplace = false; 368 369 // PostgreSQL-specific keyword handling 370 if (prevst != null) { 371 if (prevst.tokencode == TBaseType.rrw_inner) { 372 if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) { 373 prevst.tokencode = TBaseType.ident; 374 } 375 } 376 377 if ((prevst.tokencode == TBaseType.rrw_not) 378 && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) { 379 prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE"); 380 } 381 } 382 383 if (asourcetoken.tokencode == TBaseType.rrw_inner) { 384 prevst = asourcetoken; 385 } else if (asourcetoken.tokencode == TBaseType.rrw_not) { 386 prevst = asourcetoken; 387 } else { 388 prevst = null; 389 } 390 391 // Additional PostgreSQL transformations 392 if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD")) 393 || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) { 394 lcprevst = getprevsolidtoken(asourcetoken); 395 if (lcprevst != null) { 396 if (lcprevst.tokencode == TBaseType.rrw_for) 397 lcprevst.tokencode = TBaseType.rw_for1; 398 } 399 } 400 401 if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) { 402 TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2); 403 if (stKeep != null) { 404 stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank; 405 } 406 } 407 408 if ((asourcetoken.tokencode == TBaseType.rrw_postgresql_rowtype) 409 || (asourcetoken.tokencode == TBaseType.rrw_postgresql_type)) { 410 TSourceToken stPercent = asourcetoken.searchToken('%', -1); 411 if (stPercent != null) { 412 stPercent.tokencode = TBaseType.rowtype_operator; 413 } 414 } 415 416 if (asourcetoken.tokencode == TBaseType.JSON_EXIST) { 417 TSourceToken stPercent = asourcetoken.searchToken('=', -1); 418 if (stPercent != null) { 419 asourcetoken.tokencode = TBaseType.ident; 420 } 421 } 422 423 if (asourcetoken.tokencode == TBaseType.rrw_update) { 424 TSourceToken stDo = asourcetoken.searchToken(TBaseType.rrw_do, -1); 425 if (stDo != null) { 426 asourcetoken.tokencode = TBaseType.rrw_postgresql_do_update; 427 } 428 } 429 430 break; 431 } 432 } 433 434 // Get next token 435 asourcetoken = getanewsourcetoken(); 436 if (asourcetoken != null) { 437 yychar = asourcetoken.tokencode; 438 } else { 439 yychar = 0; 440 441 if (waitingreturnforfloatdiv) { 442 lct.tokencode = TBaseType.sqlpluscmd; 443 if (lct.tokentype != ETokenType.ttslash) { 444 lct.tokentype = ETokenType.ttsqlpluscmd; 445 } 446 } 447 } 448 449 if ((yychar == 0) && (prevst != null)) { 450 if (prevst.tokencode == TBaseType.rrw_inner) { 451 prevst.tokencode = TBaseType.ident; 452 } 453 } 454 } 455 } 456 457 /** 458 * Check if token represents a valid SQL*Plus-like command in PostgreSQL. 459 * 460 * @param tokenText token text to check 461 * @return true if valid SQL*Plus command 462 */ 463 private boolean isvalidsqlpluscmdInPostgresql(String tokenText) { 464 // PostgreSQL/Couchbase supports psql meta-commands like \d, \dt, etc. 465 // For now, keep compatible with original implementation 466 return false; 467 } 468 469 /** 470 * Determine if forward slash should be treated as SQL*Plus command delimiter. 471 * 472 * @param pstlist token list 473 * @param pPos position of '/' token 474 * @return true if '/' should be SQL*Plus command 475 */ 476 private boolean isValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) { 477 boolean ret = false; 478 479 if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret; 480 481 TSourceToken lcst = pstlist.get(pPos - 1); 482 if (lcst.tokentype != ETokenType.ttreturn) { 483 return ret; 484 } 485 486 if (!(lcst.getAstext().charAt(lcst.getAstext().length() - 1) == ' ')) { 487 ret = true; 488 } 489 490 return ret; 491 } 492 493 /** 494 * Get previous non-whitespace token. 495 * 496 * @param ptoken current token 497 * @return previous solid token, or null 498 */ 499 private TSourceToken getprevsolidtoken(TSourceToken ptoken) { 500 TSourceToken ret = null; 501 TSourceTokenList lctokenlist = ptoken.container; 502 503 if (lctokenlist != null) { 504 if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) { 505 if (!((lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace) 506 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn) 507 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment) 508 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment))) { 509 ret = lctokenlist.get(ptoken.posinlist - 1); 510 } else { 511 ret = lctokenlist.nextsolidtoken(ptoken.posinlist - 1, -1, false); 512 } 513 } 514 } 515 return ret; 516 } 517 518 /** 519 * Helper method to append a token to a statement. 520 * <p> 521 * This method sets the stmt reference on the token and adds it to the statement's token list. 522 * 523 * @param statement the statement to append to 524 * @param token the token to append 525 */ 526 private void appendToken(TCustomSqlStatement statement, TSourceToken token) { 527 if (statement == null || token == null) { 528 return; 529 } 530 token.stmt = statement; 531 statement.sourcetokenlist.add(token); 532 } 533 534 // ========== Couchbase-Specific Raw Statement Extraction ========== 535 536 /** 537 * Extract raw Couchbase SQL statements from tokenized source. 538 * <p> 539 * Extracted from TGSqlParser.docouchbasegetrawsqlstatements() (lines 6493-6723) 540 * 541 * @param builder the result builder to populate with raw statements 542 */ 543 private void docouchbasegetrawsqlstatements(SqlParseResult.Builder builder) { 544 int waitingEnd = 0; 545 boolean foundEnd = false; 546 547 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 548 if (!TBaseType.assigned(sourcetokenlist)) { 549 // No tokens available - populate builder with empty results and return 550 builder.sqlStatements(this.sqlstatements); 551 builder.errorCode(1); 552 builder.errorMessage("No source token list available"); 553 return; 554 } 555 556 TCustomSqlStatement gcurrentsqlstatement = null; 557 EFindSqlStateType gst = EFindSqlStateType.stnormal; 558 TSourceToken lcprevsolidtoken = null, ast = null; 559 560 for (int i = 0; i < sourcetokenlist.size(); i++) { 561 562 if ((ast != null) && (ast.issolidtoken())) 563 lcprevsolidtoken = ast; 564 565 ast = sourcetokenlist.get(i); 566 sourcetokenlist.curpos = i; 567 568 switch (gst) { 569 case sterror: { 570 if (ast.tokentype == ETokenType.ttsemicolon) { 571 appendToken(gcurrentsqlstatement, ast); 572 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 573 gst = EFindSqlStateType.stnormal; 574 } else { 575 appendToken(gcurrentsqlstatement, ast); 576 } 577 break; 578 } 579 580 case stnormal: { 581 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 582 || (ast.tokencode == TBaseType.cmtslashstar) 583 || (ast.tokencode == TBaseType.lexspace) 584 || (ast.tokencode == TBaseType.lexnewline) 585 || (ast.tokentype == ETokenType.ttsemicolon)) { 586 if (gcurrentsqlstatement != null) { 587 appendToken(gcurrentsqlstatement, ast); 588 } 589 590 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 591 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 592 // Continuous semicolons, treat it as comment 593 ast.tokentype = ETokenType.ttsimplecomment; 594 ast.tokencode = TBaseType.cmtdoublehyphen; 595 } 596 } 597 598 continue; 599 } 600 601 if (ast.tokencode == TBaseType.sqlpluscmd) { 602 gst = EFindSqlStateType.stsqlplus; 603 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 604 appendToken(gcurrentsqlstatement, ast); 605 continue; 606 } 607 608 // Find a token text to start sql or plsql mode 609 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 610 611 if (gcurrentsqlstatement != null) { 612 gst = EFindSqlStateType.stsql; 613 appendToken(gcurrentsqlstatement, ast); 614 } else { 615 // Error token found 616 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, 617 (ast.columnNo < 0 ? 0 : ast.columnNo), 618 "Error when tokenlize", EErrorType.spwarning, 619 TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 620 621 ast.tokentype = ETokenType.tttokenlizererrortoken; 622 gst = EFindSqlStateType.sterror; 623 624 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 625 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 626 appendToken(gcurrentsqlstatement, ast); 627 } 628 629 break; 630 } 631 632 case stsqlplus: { 633 if (ast.insqlpluscmd) { 634 appendToken(gcurrentsqlstatement, ast); 635 } else { 636 gst = EFindSqlStateType.stnormal; 637 appendToken(gcurrentsqlstatement, ast); 638 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 639 } 640 641 break; 642 } 643 644 case stsql: { 645 if (ast.tokentype == ETokenType.ttsemicolon) { 646 gst = EFindSqlStateType.stnormal; 647 appendToken(gcurrentsqlstatement, ast); 648 gcurrentsqlstatement.semicolonended = ast; 649 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 650 continue; 651 } 652 653 if (sourcetokenlist.sqlplusaftercurtoken()) { 654 gst = EFindSqlStateType.stnormal; 655 appendToken(gcurrentsqlstatement, ast); 656 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 657 continue; 658 } 659 appendToken(gcurrentsqlstatement, ast); 660 break; 661 } 662 663 case ststoredprocedure: { 664 if ((ast.tokencode == TBaseType.rrw_begin)) { 665 waitingEnd++; 666 foundEnd = false; 667 } else if (ast.tokencode == TBaseType.rrw_if) { 668 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 669 // This is not if after END 670 waitingEnd++; 671 } 672 } else if (ast.tokencode == TBaseType.rrw_case) { 673 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 674 // This is not case after END 675 waitingEnd++; 676 } 677 } else if (ast.tokencode == TBaseType.rrw_loop) { 678 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 679 // This is not loop after END 680 waitingEnd++; 681 } 682 } else if (ast.tokencode == TBaseType.rrw_end) { 683 foundEnd = true; 684 waitingEnd--; 685 if (waitingEnd < 0) { 686 waitingEnd = 0; 687 } 688 } 689 690 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 691 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 692 gst = EFindSqlStateType.stnormal; 693 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 694 695 // Make / a sqlplus cmd 696 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 697 appendToken(gcurrentsqlstatement, ast); 698 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 699 } else if ((ast.tokentype == ETokenType.ttperiod) 700 && (sourcetokenlist.returnaftercurtoken(false)) 701 && (sourcetokenlist.returnbeforecurtoken(false))) { 702 // Single dot at a separate line 703 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 704 gst = EFindSqlStateType.stnormal; 705 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 706 707 // Make ttperiod a sqlplus cmd 708 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 709 appendToken(gcurrentsqlstatement, ast); 710 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 711 } else { 712 appendToken(gcurrentsqlstatement, ast); 713 if ((ast.tokentype == ETokenType.ttsemicolon) 714 && (waitingEnd == 0) 715 && (foundEnd) 716 && (gcurrentsqlstatement.VerticaStatementCanBeSeparatedByBeginEndPair())) { 717 gst = EFindSqlStateType.stnormal; 718 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 719 } 720 } 721 722 if (ast.tokencode == TBaseType.sqlpluscmd) { 723 // Change tokencode back to keyword or ident, because sqlplus cmd 724 // in a sql statement (almost is plsql block) is not really a sqlplus cmd 725 int m = flexer.getkeywordvalue(ast.getAstext()); 726 if (m != 0) { 727 ast.tokencode = m; 728 } else { 729 ast.tokencode = TBaseType.ident; 730 } 731 } 732 733 break; 734 } 735 } 736 } 737 738 // Last statement 739 if ((gcurrentsqlstatement != null) 740 && ((gst == EFindSqlStateType.stsqlplus) 741 || (gst == EFindSqlStateType.stsql) 742 || (gst == EFindSqlStateType.ststoredprocedure) 743 || (gst == EFindSqlStateType.sterror))) { 744 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder); 745 } 746 747 // Populate builder with results 748 builder.sqlStatements(this.sqlstatements); 749 builder.syntaxErrors(syntaxErrors instanceof ArrayList ? 750 (ArrayList<TSyntaxError>) syntaxErrors : new ArrayList<>(syntaxErrors)); 751 builder.errorCode(syntaxErrors.isEmpty() ? 0 : syntaxErrors.size()); 752 } 753 754 @Override 755 public String toString() { 756 return "CouchbaseSqlParser{vendor=" + vendor + "}"; 757 } 758}