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}