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.TLexerDuckdb; 009import gudusoft.gsqlparser.TParserDuckdb; 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.stmt.TCommonBlock; 024import gudusoft.gsqlparser.stmt.TRoutine; 025import gudusoft.gsqlparser.compiler.TContext; 026import gudusoft.gsqlparser.sqlenv.TSQLEnv; 027import gudusoft.gsqlparser.compiler.TGlobalScope; 028import gudusoft.gsqlparser.compiler.TFrame; 029 030import java.io.BufferedReader; 031import java.util.ArrayList; 032import java.util.List; 033import java.util.Stack; 034 035/** 036 * DuckDB database SQL parser implementation. 037 * 038 * <p>This parser handles DuckDB-specific SQL syntax based on PostgreSQL. 039 * DuckDB is an in-process analytical database that uses PostgreSQL-compatible syntax. 040 * 041 * @see SqlParser 042 * @see AbstractSqlParser 043 * @see TLexerDuckdb 044 * @see TParserDuckdb 045 * @since 4.1.0.9 046 */ 047public class DuckdbSqlParser extends AbstractSqlParser { 048 049 /** The DuckDB lexer used for tokenization (public for TGSqlParser.getFlexer()) */ 050 public TLexerDuckdb flexer; 051 private TParserDuckdb fparser; 052 053 // ========== State Variables for Tokenization ========== 054 private boolean insqlpluscmd; 055 private boolean isvalidplace; 056 private boolean waitingreturnforsemicolon; 057 private boolean waitingreturnforfloatdiv; 058 private boolean continuesqlplusatnewline; 059 060 /** 061 * Construct DuckDB SQL parser. 062 */ 063 public DuckdbSqlParser() { 064 super(EDbVendor.dbvduckdb); 065 066 // Set delimiter character 067 this.delimiterChar = ';'; 068 this.defaultDelimiterStr = ";"; 069 070 // Create lexer once - will be reused for all parsing operations 071 this.flexer = new TLexerDuckdb(); 072 this.flexer.delimiterchar = this.delimiterChar; 073 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 074 075 // CRITICAL: Set lexer for inherited getanewsourcetoken() method 076 this.lexer = this.flexer; 077 078 // Create parser once - will be reused for all parsing operations 079 this.fparser = new TParserDuckdb(null); 080 this.fparser.lexer = this.flexer; 081 } 082 083 @Override 084 protected TCustomLexer getLexer(ParserContext context) { 085 return this.flexer; 086 } 087 088 @Override 089 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 090 this.fparser.sourcetokenlist = tokens; 091 return this.fparser; 092 } 093 094 @Override 095 protected void tokenizeVendorSql() { 096 doduckdbtexttotokenlist(); 097 } 098 099 @Override 100 protected void setupVendorParsersForExtraction() { 101 this.fparser.sqlcmds = this.sqlcmds; 102 this.fparser.sourcetokenlist = this.sourcetokenlist; 103 } 104 105 @Override 106 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 107 doduckdbgetrawsqlstatements(builder); 108 } 109 110 @Override 111 protected TStatementList performParsing(ParserContext context, 112 TCustomParser parser, 113 TCustomParser secondaryParser, 114 TSourceTokenList tokens, 115 TStatementList rawStatements) { 116 this.sourcetokenlist = tokens; 117 this.parserContext = context; 118 this.sqlstatements = rawStatements; 119 120 initializeGlobalContext(); 121 122 for (int i = 0; i < sqlstatements.size(); i++) { 123 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 124 stmt.setFrameStack(frameStack); 125 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 126 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 127 copyErrorsFromStatement(stmt); 128 } 129 } 130 131 if (globalFrame != null) { 132 globalFrame.popMeFromStack(frameStack); 133 } 134 135 return this.sqlstatements; 136 } 137 138 // ========== DuckDB-Specific Tokenization ========== 139 140 private void doduckdbtexttotokenlist() { 141 insqlpluscmd = false; 142 isvalidplace = true; 143 waitingreturnforfloatdiv = false; 144 waitingreturnforsemicolon = false; 145 continuesqlplusatnewline = false; 146 147 TSourceToken lct = null, prevst = null; 148 TSourceToken asourcetoken, lcprevst; 149 int yychar; 150 151 asourcetoken = getanewsourcetoken(); 152 if (asourcetoken == null) return; 153 yychar = asourcetoken.tokencode; 154 155 while (yychar > 0) { 156 sourcetokenlist.add(asourcetoken); 157 158 switch (yychar) { 159 case TBaseType.cmtdoublehyphen: 160 case TBaseType.cmtslashstar: 161 case TBaseType.lexspace: { 162 if (insqlpluscmd) { 163 asourcetoken.insqlpluscmd = true; 164 } 165 break; 166 } 167 168 case TBaseType.lexnewline: { 169 if (insqlpluscmd) { 170 insqlpluscmd = false; 171 isvalidplace = true; 172 173 if (continuesqlplusatnewline) { 174 insqlpluscmd = true; 175 isvalidplace = false; 176 asourcetoken.insqlpluscmd = true; 177 } 178 } 179 180 if (waitingreturnforsemicolon) { 181 isvalidplace = true; 182 } 183 if (waitingreturnforfloatdiv) { 184 isvalidplace = true; 185 lct.tokencode = TBaseType.sqlpluscmd; 186 if (lct.tokentype != ETokenType.ttslash) { 187 lct.tokentype = ETokenType.ttsqlpluscmd; 188 } 189 } 190 flexer.insqlpluscmd = insqlpluscmd; 191 break; 192 } 193 194 default: { 195 continuesqlplusatnewline = false; 196 waitingreturnforsemicolon = false; 197 waitingreturnforfloatdiv = false; 198 199 if (insqlpluscmd) { 200 asourcetoken.insqlpluscmd = true; 201 if (asourcetoken.toString().equalsIgnoreCase("-")) { 202 continuesqlplusatnewline = true; 203 } 204 } else { 205 if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 206 waitingreturnforsemicolon = true; 207 } 208 if ((asourcetoken.tokentype == ETokenType.ttslash) 209 && (isvalidplace || (isValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) { 210 lct = asourcetoken; 211 waitingreturnforfloatdiv = true; 212 } 213 } 214 isvalidplace = false; 215 216 // Keyword handling (same as PostgreSQL) 217 if (prevst != null) { 218 if (prevst.tokencode == TBaseType.rrw_inner) { 219 if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) { 220 prevst.tokencode = TBaseType.ident; 221 } 222 } 223 224 if ((prevst.tokencode == TBaseType.rrw_not) 225 && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) { 226 prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE"); 227 } 228 } 229 230 if (asourcetoken.tokencode == TBaseType.rrw_inner) { 231 prevst = asourcetoken; 232 } else if (asourcetoken.tokencode == TBaseType.rrw_not) { 233 prevst = asourcetoken; 234 } else { 235 prevst = null; 236 } 237 238 // Additional transformations 239 if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD")) 240 || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) { 241 lcprevst = getprevsolidtoken(asourcetoken); 242 if (lcprevst != null) { 243 if (lcprevst.tokencode == TBaseType.rrw_for) 244 lcprevst.tokencode = TBaseType.rw_for1; 245 } 246 } 247 248 if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) { 249 TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2); 250 if (stKeep != null) { 251 stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank; 252 } 253 } 254 255 if ((asourcetoken.tokencode == TBaseType.rrw_postgresql_rowtype) 256 || (asourcetoken.tokencode == TBaseType.rrw_postgresql_type)) { 257 TSourceToken stPercent = asourcetoken.searchToken('%', -1); 258 if (stPercent != null) { 259 stPercent.tokencode = TBaseType.rowtype_operator; 260 } 261 } 262 263 if (asourcetoken.tokencode == TBaseType.JSON_EXIST) { 264 TSourceToken stPercent = asourcetoken.searchToken('=', -1); 265 if (stPercent != null) { 266 asourcetoken.tokencode = TBaseType.ident; 267 } 268 } 269 270 if (asourcetoken.tokencode == TBaseType.rrw_update) { 271 TSourceToken stDo = asourcetoken.searchToken(TBaseType.rrw_do, -1); 272 if (stDo != null) { 273 asourcetoken.tokencode = TBaseType.rrw_postgresql_do_update; 274 } 275 } 276 277 break; 278 } 279 } 280 281 asourcetoken = getanewsourcetoken(); 282 if (asourcetoken != null) { 283 yychar = asourcetoken.tokencode; 284 } else { 285 yychar = 0; 286 287 if (waitingreturnforfloatdiv) { 288 lct.tokencode = TBaseType.sqlpluscmd; 289 if (lct.tokentype != ETokenType.ttslash) { 290 lct.tokentype = ETokenType.ttsqlpluscmd; 291 } 292 } 293 } 294 295 if ((yychar == 0) && (prevst != null)) { 296 if (prevst.tokencode == TBaseType.rrw_inner) { 297 prevst.tokencode = TBaseType.ident; 298 } 299 } 300 } 301 } 302 303 private boolean isValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) { 304 boolean ret = false; 305 if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret; 306 TSourceToken lcst = pstlist.get(pPos - 1); 307 if (lcst.tokentype != ETokenType.ttreturn) { 308 return ret; 309 } 310 if (!(lcst.getAstext().charAt(lcst.getAstext().length() - 1) == ' ')) { 311 ret = true; 312 } 313 return ret; 314 } 315 316 private TSourceToken getprevsolidtoken(TSourceToken ptoken) { 317 TSourceToken ret = null; 318 TSourceTokenList lctokenlist = ptoken.container; 319 320 if (lctokenlist != null) { 321 if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) { 322 if (!( 323 (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace) 324 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn) 325 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment) 326 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment) 327 )) { 328 ret = lctokenlist.get(ptoken.posinlist - 1); 329 } else { 330 ret = lctokenlist.nextsolidtoken(ptoken.posinlist - 1, -1, false); 331 } 332 } 333 } 334 return ret; 335 } 336 337 // ========== DuckDB-Specific Raw Statement Extraction ========== 338 339 private void doduckdbgetrawsqlstatements(SqlParseResult.Builder builder) { 340 int waitingEnd = 0; 341 boolean foundEnd = false, enterDeclare = false; 342 boolean isSinglePLBlock = false; 343 344 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 345 if (!TBaseType.assigned(sourcetokenlist)) { 346 builder.sqlStatements(this.sqlstatements); 347 builder.errorCode(1); 348 builder.errorMessage("No source token list available"); 349 return; 350 } 351 352 TCustomSqlStatement gcurrentsqlstatement = null; 353 EFindSqlStateType gst = EFindSqlStateType.stnormal; 354 TSourceToken lcprevsolidtoken = null, ast = null; 355 356 if (isSinglePLBlock) { 357 gcurrentsqlstatement = new TCommonBlock(EDbVendor.dbvduckdb); 358 } 359 360 for (int i = 0; i < sourcetokenlist.size(); i++) { 361 362 if ((ast != null) && (ast.issolidtoken())) 363 lcprevsolidtoken = ast; 364 365 ast = sourcetokenlist.get(i); 366 sourcetokenlist.curpos = i; 367 368 if (isSinglePLBlock) { 369 gcurrentsqlstatement.sourcetokenlist.add(ast); 370 continue; 371 } 372 373 performRawStatementTokenTransformations(ast); 374 375 switch (gst) { 376 case sterror: { 377 if (ast.tokentype == ETokenType.ttsemicolon) { 378 appendToken(gcurrentsqlstatement, ast); 379 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 380 gst = EFindSqlStateType.stnormal; 381 } else { 382 appendToken(gcurrentsqlstatement, ast); 383 } 384 break; 385 } 386 387 case stnormal: { 388 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 389 || (ast.tokencode == TBaseType.cmtslashstar) 390 || (ast.tokencode == TBaseType.lexspace) 391 || (ast.tokencode == TBaseType.lexnewline) 392 || (ast.tokentype == ETokenType.ttsemicolon)) { 393 if (gcurrentsqlstatement != null) { 394 appendToken(gcurrentsqlstatement, ast); 395 } 396 397 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 398 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 399 ast.tokentype = ETokenType.ttsimplecomment; 400 ast.tokencode = TBaseType.cmtdoublehyphen; 401 } 402 } 403 404 continue; 405 } 406 407 if (ast.tokencode == TBaseType.sqlpluscmd) { 408 gst = EFindSqlStateType.stsqlplus; 409 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 410 appendToken(gcurrentsqlstatement, ast); 411 continue; 412 } 413 414 // Find a token to start sql or plsql mode 415 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 416 417 if (gcurrentsqlstatement != null) { 418 enterDeclare = false; 419 if (gcurrentsqlstatement.ispgplsql()) { 420 gst = EFindSqlStateType.ststoredprocedure; 421 appendToken(gcurrentsqlstatement, ast); 422 foundEnd = false; 423 if ((ast.tokencode == TBaseType.rrw_begin) 424 || (ast.tokencode == TBaseType.rrw_package) 425 || (ast.searchToken(TBaseType.rrw_package, 4) != null)) { 426 waitingEnd = 1; 427 } else if (ast.tokencode == TBaseType.rrw_declare) { 428 enterDeclare = true; 429 } 430 } else { 431 gst = EFindSqlStateType.stsql; 432 appendToken(gcurrentsqlstatement, ast); 433 } 434 } else { 435 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, 436 (ast.columnNo < 0 ? 0 : ast.columnNo), 437 "Error when tokenize", EErrorType.spwarning, 438 TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 439 440 ast.tokentype = ETokenType.tttokenlizererrortoken; 441 gst = EFindSqlStateType.sterror; 442 443 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 444 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 445 appendToken(gcurrentsqlstatement, ast); 446 } 447 448 break; 449 } 450 451 case stsqlplus: { 452 if (ast.insqlpluscmd) { 453 appendToken(gcurrentsqlstatement, ast); 454 } else { 455 gst = EFindSqlStateType.stnormal; 456 appendToken(gcurrentsqlstatement, ast); 457 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 458 } 459 460 break; 461 } 462 463 case stsql: { 464 if (ast.tokentype == ETokenType.ttsemicolon) { 465 gst = EFindSqlStateType.stnormal; 466 appendToken(gcurrentsqlstatement, ast); 467 gcurrentsqlstatement.semicolonended = ast; 468 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 469 continue; 470 } 471 472 if (sourcetokenlist.sqlplusaftercurtoken()) { 473 gst = EFindSqlStateType.stnormal; 474 appendToken(gcurrentsqlstatement, ast); 475 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 476 continue; 477 } 478 479 if (ast.tokencode == TBaseType.cmtdoublehyphen) { 480 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { 481 gst = EFindSqlStateType.stnormal; 482 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 483 continue; 484 } 485 } 486 487 appendToken(gcurrentsqlstatement, ast); 488 break; 489 } 490 491 case ststoredprocedure: { 492 if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) { 493 appendToken(gcurrentsqlstatement, ast); 494 gst = EFindSqlStateType.ststoredprocedurePgStartBody; 495 continue; 496 } 497 498 if (ast.tokencode == TBaseType.rrw_postgresql_language) { 499 TSourceToken nextSt = ast.nextSolidToken(); 500 if (nextSt != null) { 501 if (gcurrentsqlstatement instanceof TRoutine) { 502 TRoutine p = (TRoutine) gcurrentsqlstatement; 503 p.setRoutineLanguage(nextSt.toString()); 504 } 505 } 506 } 507 508 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (!enterDeclare)) { 509 gst = EFindSqlStateType.stnormal; 510 appendToken(gcurrentsqlstatement, ast); 511 gcurrentsqlstatement.semicolonended = ast; 512 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 513 continue; 514 } 515 516 if (ast.tokencode == TBaseType.rrw_begin) { 517 waitingEnd++; 518 enterDeclare = false; 519 } else if (ast.tokencode == TBaseType.rrw_declare) { 520 enterDeclare = true; 521 } else if (ast.tokencode == TBaseType.rrw_if) { 522 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 523 waitingEnd++; 524 } 525 } else if (ast.tokencode == TBaseType.rrw_case) { 526 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 527 waitingEnd++; 528 } 529 } else if (ast.tokencode == TBaseType.rrw_loop) { 530 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 531 waitingEnd++; 532 } 533 } else if (ast.tokencode == TBaseType.rrw_end) { 534 foundEnd = true; 535 waitingEnd--; 536 if (waitingEnd < 0) { 537 waitingEnd = 0; 538 } 539 } 540 541 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 542 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 543 gst = EFindSqlStateType.stnormal; 544 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 545 546 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 547 appendToken(gcurrentsqlstatement, ast); 548 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 549 } else if ((ast.tokentype == ETokenType.ttperiod) 550 && (sourcetokenlist.returnaftercurtoken(false)) 551 && (sourcetokenlist.returnbeforecurtoken(false))) { 552 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 553 gst = EFindSqlStateType.stnormal; 554 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 555 556 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 557 appendToken(gcurrentsqlstatement, ast); 558 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 559 } else { 560 appendToken(gcurrentsqlstatement, ast); 561 if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (foundEnd)) { 562 gst = EFindSqlStateType.stnormal; 563 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 564 } 565 } 566 567 if (ast.tokencode == TBaseType.sqlpluscmd) { 568 int m = flexer.getkeywordvalue(ast.getAstext()); 569 if (m != 0) { 570 ast.tokencode = m; 571 } else { 572 ast.tokencode = TBaseType.ident; 573 } 574 } 575 576 if ((gst == EFindSqlStateType.ststoredprocedure) && (ast.tokencode == TBaseType.cmtdoublehyphen)) { 577 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { 578 gst = EFindSqlStateType.stnormal; 579 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 580 } 581 } 582 583 break; 584 } 585 586 case ststoredprocedurePgStartBody: { 587 appendToken(gcurrentsqlstatement, ast); 588 589 if (ast.tokencode == TBaseType.rrw_postgresql_function_delimiter) { 590 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstDoExecuteBlock) { 591 TSourceToken nextSolid = ast.nextSolidToken(); 592 if (nextSolid != null && nextSolid.tokencode == TBaseType.rrw_postgresql_language) { 593 gst = EFindSqlStateType.ststoredprocedurePgEndBody; 594 } else { 595 gst = EFindSqlStateType.stnormal; 596 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 597 } 598 continue; 599 } else { 600 gst = EFindSqlStateType.ststoredprocedurePgEndBody; 601 continue; 602 } 603 } 604 605 break; 606 } 607 608 case ststoredprocedurePgEndBody: { 609 if (ast.tokentype == ETokenType.ttsemicolon) { 610 gst = EFindSqlStateType.stnormal; 611 appendToken(gcurrentsqlstatement, ast); 612 gcurrentsqlstatement.semicolonended = ast; 613 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 614 continue; 615 } else if (ast.tokencode == TBaseType.cmtdoublehyphen) { 616 if (ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) { 617 gst = EFindSqlStateType.stnormal; 618 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 619 continue; 620 } 621 } 622 623 appendToken(gcurrentsqlstatement, ast); 624 625 if (ast.tokencode == TBaseType.rrw_postgresql_language) { 626 TSourceToken nextSt = ast.nextSolidToken(); 627 if (nextSt != null) { 628 if (gcurrentsqlstatement instanceof TRoutine) { 629 TRoutine p = (TRoutine) gcurrentsqlstatement; 630 p.setRoutineLanguage(nextSt.toString()); 631 } 632 } 633 } 634 635 break; 636 } 637 } 638 } 639 640 // Last statement 641 if ((gcurrentsqlstatement != null) && 642 ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) 643 || (gst == EFindSqlStateType.ststoredprocedure) 644 || (gst == EFindSqlStateType.ststoredprocedurePgEndBody) 645 || (gst == EFindSqlStateType.sterror) || (isSinglePLBlock))) { 646 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder); 647 } 648 649 builder.sqlStatements(this.sqlstatements); 650 builder.syntaxErrors(syntaxErrors instanceof ArrayList ? 651 (ArrayList<TSyntaxError>) syntaxErrors : new ArrayList<>(syntaxErrors)); 652 builder.errorCode(syntaxErrors.isEmpty() ? 0 : syntaxErrors.size()); 653 } 654 655 private void performRawStatementTokenTransformations(TSourceToken ast) { 656 if (ast.tokencode == TBaseType.JSON_EXIST) { 657 TSourceToken stConstant = ast.searchToken(TBaseType.sconst, 1); 658 if (stConstant == null) { 659 ast.tokencode = TBaseType.ident; 660 } 661 } else if (ast.tokencode == TBaseType.rrw_postgresql_POSITION) { 662 TSourceToken st1 = ast.nextSolidToken(); 663 if (st1 != null) { 664 if (st1.tokencode == '(') { 665 ast.tokencode = TBaseType.rrw_postgresql_POSITION_FUNCTION; 666 } 667 } 668 } else if (ast.tokencode == TBaseType.rrw_postgresql_ordinality) { 669 TSourceToken lcprevst = getprevsolidtoken(ast); 670 671 if (lcprevst != null) { 672 if (lcprevst.tokencode == TBaseType.rrw_with) { 673 TSourceToken lcbeforewith = getprevsolidtoken(lcprevst); 674 if (lcbeforewith != null && lcbeforewith.tokencode == ')') { 675 lcprevst.tokencode = TBaseType.rrw_postgresql_with_lookahead; 676 } 677 } 678 } 679 } else if (ast.tokencode == TBaseType.rrw_postgresql_filter) { 680 TSourceToken st1 = ast.nextSolidToken(); 681 if (st1 != null) { 682 if (st1.tokencode != '(') { 683 ast.tokencode = TBaseType.ident; 684 } 685 } 686 } else if (ast.tokencode == TBaseType.rrw_postgresql_jsonb) { 687 TSourceToken st1 = ast.nextSolidToken(); 688 if (st1 != null) { 689 if (st1.tokencode == '?') { 690 st1.tokencode = TBaseType.OP_JSONB_QUESTION; 691 } 692 } 693 } else if (ast.tokencode == TBaseType.rrw_values) { 694 TSourceToken stParen = ast.searchToken('(', 1); 695 if (stParen != null) { 696 TSourceToken stInsert = ast.searchToken(TBaseType.rrw_insert, -ast.posinlist); 697 if (stInsert != null) { 698 TSourceToken stSemiColon = ast.searchToken(';', -ast.posinlist); 699 if ((stSemiColon != null) && (stSemiColon.posinlist > stInsert.posinlist)) { 700 // Don't treat values(1) as insert values 701 } else { 702 TSourceToken stFrom = ast.searchToken(TBaseType.rrw_from, -ast.posinlist); 703 if ((stFrom != null) && (stFrom.posinlist > stInsert.posinlist)) { 704 // Don't treat values after from keyword as an insert values 705 } else { 706 ast.tokencode = TBaseType.rrw_postgresql_insert_values; 707 } 708 } 709 } 710 } 711 } 712 } 713 714 private void appendToken(TCustomSqlStatement statement, TSourceToken token) { 715 if (statement == null || token == null) { 716 return; 717 } 718 token.stmt = statement; 719 statement.sourcetokenlist.add(token); 720 } 721 722 @Override 723 protected void onRawStatementComplete(ParserContext context, 724 TCustomSqlStatement statement, 725 TCustomParser mainParser, 726 TCustomParser secondaryParser, 727 TStatementList statementList, 728 boolean isLastStatement, 729 SqlParseResult.Builder builder) { 730 super.onRawStatementComplete(context, statement, mainParser, secondaryParser, statementList, isLastStatement, builder); 731 732 if (statement instanceof TRoutine) { 733 TRoutine routine = (TRoutine) statement; 734 if (!routine.isBodyInSQL()) { 735 processNonSqlRoutineBody(routine); 736 } 737 } 738 } 739 740 private void processNonSqlRoutineBody(TRoutine routine) { 741 if (routine.sourcetokenlist == null || routine.sourcetokenlist.size() == 0) { 742 return; 743 } 744 745 TSourceToken st; 746 boolean inBody = false; 747 StringBuilder routineBodyBuilder = new StringBuilder(); 748 749 for (int i = 0; i < routine.sourcetokenlist.size(); i++) { 750 st = routine.sourcetokenlist.get(i); 751 752 if (isDollarFunctionDelimiter(st.tokencode, this.vendor)) { 753 if (!inBody) { 754 inBody = true; 755 routineBodyBuilder.append(st.toString()); 756 } else { 757 inBody = false; 758 routineBodyBuilder.append(st.toString()); 759 break; 760 } 761 continue; 762 } 763 764 if (inBody) { 765 st.tokencode = TBaseType.sqlpluscmd; 766 routineBodyBuilder.append(st.toString()); 767 } 768 } 769 770 routine.setRoutineBody(routineBodyBuilder.toString()); 771 } 772 773 @Override 774 public String toString() { 775 return "DuckdbSqlParser{vendor=" + vendor + "}"; 776 } 777}