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}