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.TLexerOracle;
009import gudusoft.gsqlparser.TParserOracleSql;
010import gudusoft.gsqlparser.TParserOraclePLSql;
011import gudusoft.gsqlparser.TSourceToken;
012import gudusoft.gsqlparser.TSourceTokenList;
013import gudusoft.gsqlparser.TStatementList;
014import gudusoft.gsqlparser.TSyntaxError;
015import gudusoft.gsqlparser.EFindSqlStateType;
016import gudusoft.gsqlparser.ESqlPlusCmd;
017import gudusoft.gsqlparser.ETokenType;
018import gudusoft.gsqlparser.ETokenStatus;
019import gudusoft.gsqlparser.ESqlStatementType;
020import gudusoft.gsqlparser.EErrorType;
021import gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement;
022import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
023import gudusoft.gsqlparser.sqlcmds.ISqlCmds;
024import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
025import gudusoft.gsqlparser.compiler.TContext;
026import gudusoft.gsqlparser.sqlenv.TSQLEnv;
027import gudusoft.gsqlparser.compiler.TGlobalScope;
028import gudusoft.gsqlparser.compiler.TFrame;
029import gudusoft.gsqlparser.resolver.TSQLResolver;
030import gudusoft.gsqlparser.TLog;
031import gudusoft.gsqlparser.TGSqlParser;
032import gudusoft.gsqlparser.compiler.TASTEvaluator;
033import java.util.Stack;
034
035import java.io.BufferedReader;
036import java.util.ArrayList;
037import java.util.List;
038
039/**
040 * Oracle database SQL parser implementation.
041 *
042 * <p>This parser handles Oracle-specific SQL syntax including:
043 * <ul>
044 *   <li>PL/SQL blocks (procedures, functions, packages, triggers)</li>
045 *   <li>SQL*Plus commands (spool, set, show, etc.)</li>
046 *   <li>Oracle-specific DML/DDL (MERGE, flashback, etc.)</li>
047 *   <li>Oracle analytical functions and extensions</li>
048 *   <li>Special token handling (INNER, NOT DEFERRABLE, etc.)</li>
049 * </ul>
050 *
051 * <p><b>Implementation Status:</b> PHASE 3 - IN PROGRESS
052 * <ul>
053 *   <li><b>Completed:</b> Oracle classes (TLexerOracle, TParserOracleSql, TParserOraclePLSql) are now PUBLIC</li>
054 *   <li><b>Current:</b> Skeleton implementation delegates to legacy TGSqlParser</li>
055 *   <li><b>Next:</b> Extract vendor-specific logic from TGSqlParser into this class</li>
056 *   <li><b>Goal:</b> Fully self-contained Oracle parser using AbstractSqlParser template</li>
057 * </ul>
058 *
059 * <p><b>Design Notes:</b>
060 * <ul>
061 *   <li>Implements {@link SqlParser} directly (will extend {@link AbstractSqlParser} in Phase 4)</li>
062 *   <li>Can now directly instantiate: {@link TLexerOracle}, {@link TParserOracleSql}, {@link TParserOraclePLSql}</li>
063 *   <li>Uses two parsers: TParserOracleSql (SQL) + TParserOraclePLSql (PL/SQL blocks)</li>
064 *   <li>Handles SQL*Plus commands via special tokenization logic</li>
065 *   <li>Delimiter character: '/' for PL/SQL blocks, ';' for SQL statements</li>
066 * </ul>
067 *
068 * <p><b>Usage Example:</b>
069 * <pre>
070 * // Get Oracle parser from factory
071 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvoracle);
072 *
073 * // Build context
074 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvoracle)
075 *     .sqlText("SELECT * FROM emp WHERE deptno = 10")
076 *     .build();
077 *
078 * // Parse
079 * SqlParseResult result = parser.parse(context);
080 *
081 * // Access statements
082 * TStatementList statements = result.getSqlStatements();
083 * </pre>
084 *
085 * <p><b>Phase 3 Extraction Roadmap:</b>
086 * <ol>
087 *   <li>✅ DONE: Make TLexerOracle, TParserOracleSql, TParserOraclePLSql public</li>
088 *   <li>⏳ TODO: Extract tokenization logic (~367 lines from TGSqlParser.dooraclesqltexttotokenlist())</li>
089 *   <li>⏳ TODO: Extract raw statement logic (~200 lines from TGSqlParser.dooraclegetrawsqlstatements())</li>
090 *   <li>⏳ TODO: Extract parsing orchestration (SQL vs PL/SQL parser selection)</li>
091 *   <li>⏳ TODO: Extract helper methods (getanewsourcetoken, getprevsolidtoken, etc.)</li>
092 *   <li>⏳ TODO: Extend AbstractSqlParser and use template method pattern fully</li>
093 *   <li>⏳ TODO: Remove all delegation to TGSqlParser</li>
094 * </ol>
095 *
096 * <p><b>Key Methods to Extract from TGSqlParser:</b>
097 * <ul>
098 *   <li>{@code dooraclesqltexttotokenlist()} - Oracle tokenization with SQL*Plus command detection</li>
099 *   <li>{@code dooraclegetrawsqlstatements()} - Oracle raw statement boundaries (handles PL/SQL blocks)</li>
100 *   <li>{@code getanewsourcetoken()} - Token iterator from lexer</li>
101 *   <li>{@code getprevsolidtoken()} - Navigate token list backwards</li>
102 *   <li>{@code IsValidPlaceForDivToSqlplusCmd()} - Slash vs divide operator disambiguation</li>
103 *   <li>{@code countLines()} - Multi-line token handling</li>
104 *   <li>{@code spaceAtTheEndOfReturnToken()} - SQL*Plus command validation</li>
105 * </ul>
106 *
107 * @see SqlParser
108 * @see AbstractSqlParser
109 * @see TLexerOracle
110 * @see TParserOracleSql
111 * @see TParserOraclePLSql
112 * @since 3.2.0.0
113 */
114public class OracleSqlParser extends AbstractSqlParser {
115
116    /**
117     * Construct Oracle SQL parser.
118     * <p>
119     * Configures the parser for Oracle database with default delimiters:
120     * <ul>
121     *   <li>SQL statements: semicolon (;)</li>
122     *   <li>PL/SQL blocks: forward slash (/)</li>
123     * </ul>
124     * <p>
125     * Following the original TGSqlParser pattern, the lexer and parsers are
126     * created once in the constructor and reused for all parsing operations.
127     * This avoids unnecessary object allocation overhead since the parser
128     * is not thread-safe and designed for single-use per instance.
129     */
130    public OracleSqlParser() {
131        super(EDbVendor.dbvoracle);
132        this.delimiterChar = '/';  // PL/SQL delimiter
133        this.defaultDelimiterStr = ";";  // SQL delimiter
134
135        // Create lexer once - will be reused for all parsing operations
136        // (matches original TGSqlParser constructor pattern at line 1033)
137        this.flexer = new TLexerOracle();
138        this.flexer.delimiterchar = this.delimiterChar;
139        this.flexer.defaultDelimiterStr = this.defaultDelimiterStr;
140
141        // Set parent's lexer reference for shared tokenization logic
142        this.lexer = this.flexer;
143
144        // Create parsers once - will be reused for all parsing operations
145        // Token list will be set/updated when parsing begins
146        // (matches original TGSqlParser constructor pattern at lines 1036-1040)
147        this.fparser = new TParserOracleSql(null);
148        this.fplsqlparser = new TParserOraclePLSql(null);
149        this.fparser.lexer = this.flexer;
150        this.fplsqlparser.lexer = this.flexer;
151
152        // NOTE: sourcetokenlist and sqlstatements are initialized in AbstractSqlParser constructor
153    }
154
155    // ========== Tokenization State (used during tokenization) ==========
156    // These instance variables are used during the tokenization process
157    // and are set up at the beginning of tokenization
158
159    /** The Oracle lexer used for tokenization */
160    public TLexerOracle flexer;  // Package-accessible for TGSqlParser integration
161
162    // NOTE: sourcetokenlist moved to AbstractSqlParser (inherited)
163
164    /** Optional callback for token processing (can be null) */
165    private Object tokenHandle;  // TTokenCallback interface - keeping as Object for now
166
167    // State variables for tokenization (set during dooraclesqltexttotokenlist())
168    private boolean continuesqlplusatnewline;
169    private boolean waitingreturnforsemicolon;
170    private boolean waitingreturnforfloatdiv;
171    private boolean isvalidplace;
172    private boolean insqlpluscmd;
173
174    // ========== Statement Parsing State (used during statement parsing) ==========
175    // These instance variables are used during the statement parsing process
176
177    // NOTE: The following fields moved to AbstractSqlParser (inherited):
178    //   - sqlcmds (ISqlCmds)
179    //   - sqlstatements (TStatementList)
180    //   - parserContext (ParserContext)
181
182    /** Current statement being built */
183    private TCustomSqlStatement gcurrentsqlstatement;
184
185    /** SQL parser (for regular SQL statements) */
186    private TParserOracleSql fparser;
187
188    /** PL/SQL parser (for PL/SQL blocks) */
189    private TParserOraclePLSql fplsqlparser;
190
191    // Note: Global context and frame stack fields inherited from AbstractSqlParser:
192    // - protected TContext globalContext
193    // - protected TSQLEnv sqlEnv
194    // - protected Stack<TFrame> frameStack
195    // - protected TFrame globalFrame
196
197    // ========== Enums for State Machine ==========
198    // These enums are used by the dooraclegetrawsqlstatements state machine
199
200    enum stored_procedure_status {start,is_as,body,bodyend,end, cursor_declare};
201    enum stored_procedure_type {function,procedure,package_spec,package_body, block_with_begin,block_with_declare,
202        create_trigger,create_library,cursor_in_package_spec,others};
203
204    static final int stored_procedure_nested_level = 1024;
205
206    // ========== AbstractSqlParser Abstract Methods Implementation ==========
207
208    /**
209     * Return the Oracle lexer instance.
210     * <p>
211     * The lexer is created once in the constructor and reused for all
212     * parsing operations. This method simply returns the existing instance,
213     * matching the original TGSqlParser pattern where the lexer is created
214     * once and reset before each use.
215     *
216     * @param context parser context (not used, lexer already created)
217     * @return the Oracle lexer instance created in constructor
218     */
219    @Override
220    protected TCustomLexer getLexer(ParserContext context) {
221        // Return existing lexer instance (created in constructor)
222        // No need to create new instance - matches original TGSqlParser pattern
223        return this.flexer;
224    }
225
226    /**
227     * Return the Oracle SQL parser instance with updated token list.
228     * <p>
229     * The parser is created once in the constructor and reused for all
230     * parsing operations. This method updates the token list and returns
231     * the existing instance, matching the original TGSqlParser pattern.
232     *
233     * @param context parser context (not used, parser already created)
234     * @param tokens source token list to parse
235     * @return the Oracle SQL parser instance created in constructor
236     */
237    @Override
238    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
239        // Update token list for reused parser instance
240        this.fparser.sourcetokenlist = tokens;
241        return this.fparser;
242    }
243
244    /**
245     * Return the Oracle PL/SQL parser instance with updated token list.
246     * <p>
247     * Oracle needs a secondary parser (TParserOraclePLSql) for PL/SQL blocks
248     * (procedures, functions, packages, triggers, anonymous blocks).
249     * <p>
250     * The parser is created once in the constructor and reused for all
251     * parsing operations. This method updates the token list and returns
252     * the existing instance, matching the original TGSqlParser pattern.
253     *
254     * @param context parser context (not used, parser already created)
255     * @param tokens source token list to parse
256     * @return the Oracle PL/SQL parser instance created in constructor
257     */
258    @Override
259    protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) {
260        // Update token list for reused parser instance
261        this.fplsqlparser.sourcetokenlist = tokens;
262        return this.fplsqlparser;
263    }
264
265    /**
266     * Call Oracle-specific tokenization logic.
267     * <p>
268     * Delegates to dooraclesqltexttotokenlist which handles Oracle's
269     * specific keyword recognition, SQL*Plus commands, forward slash
270     * disambiguation, and token generation.
271     */
272    @Override
273    protected void tokenizeVendorSql() {
274        dooraclesqltexttotokenlist();
275    }
276
277    /**
278     * Setup Oracle parsers for raw statement extraction.
279     * <p>
280     * Oracle uses dual parsers (SQL + PL/SQL), so we inject sqlcmds and
281     * update token lists for both parsers.
282     */
283    @Override
284    protected void setupVendorParsersForExtraction() {
285        // Inject sqlcmds into BOTH parsers (SQL + PL/SQL)
286        this.fparser.sqlcmds = this.sqlcmds;
287        this.fplsqlparser.sqlcmds = this.sqlcmds;
288
289        // Update token list for BOTH parsers
290        this.fparser.sourcetokenlist = this.sourcetokenlist;
291        this.fplsqlparser.sourcetokenlist = this.sourcetokenlist;
292    }
293
294    /**
295     * Call Oracle-specific raw statement extraction logic.
296     * <p>
297     * Delegates to dooraclegetrawsqlstatements which handles Oracle's
298     * statement delimiters (semicolon and forward slash).
299     */
300    @Override
301    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
302        dooraclegetrawsqlstatements(builder);
303    }
304
305    /**
306     * Perform full parsing of statements with syntax checking.
307     * <p>
308     * This method orchestrates the parsing of all statements by:
309     * <ul>
310     *   <li>Using the raw statements passed from AbstractSqlParser.parse()</li>
311     *   <li>Initializing SQL and PL/SQL parsers</li>
312     *   <li>Creating global context and frame stack</li>
313     *   <li>Looping through each raw statement</li>
314     *   <li>Calling parsestatement() on each to build AST</li>
315     *   <li>Handling error recovery for CREATE TABLE/INDEX</li>
316     *   <li>Collecting syntax errors</li>
317     * </ul>
318     *
319     * <p><b>Important:</b> This method does NOT extract raw statements - they are
320     * passed in as a parameter already extracted by {@link #extractRawStatements}.
321     * This eliminates duplicate extraction that was occurring in the old design.
322     *
323     * <p>Extracted from: TGSqlParser.doparse() lines 16903-17026
324     *
325     * @param context parser context
326     * @param parser main SQL parser (TParserOracleSql)
327     * @param secondaryParser PL/SQL parser (TParserOraclePLSql)
328     * @param tokens source token list
329     * @param rawStatements raw statements already extracted (never null)
330     * @return list of fully parsed statements with AST built
331     */
332    @Override
333    protected TStatementList performParsing(ParserContext context,
334                                           TCustomParser parser,
335                                           TCustomParser secondaryParser,
336                                           TSourceTokenList tokens,
337                                           TStatementList rawStatements) {
338        // Store references
339        this.fparser = (TParserOracleSql) parser;
340        this.fplsqlparser = (TParserOraclePLSql) secondaryParser;
341        this.sourcetokenlist = tokens;
342        this.parserContext = context;
343
344        // Use the raw statements passed from AbstractSqlParser.parse()
345        // (already extracted - DO NOT re-extract to avoid duplication)
346        this.sqlstatements = rawStatements;
347
348        // Initialize statement parsing infrastructure
349        this.sqlcmds = SqlCmdsFactory.get(vendor);
350
351        // Inject sqlcmds into parsers (required for make_stmt and other methods)
352        this.fparser.sqlcmds = this.sqlcmds;
353        this.fplsqlparser.sqlcmds = this.sqlcmds;
354
355        // Initialize global context for semantic analysis
356        // CRITICAL: When delegated from TGSqlParser, use TGSqlParser's frameStack
357        // so that variables set in statements can be found by other statements
358        if (context != null && context.getGsqlparser() != null) {
359            TGSqlParser gsqlparser = (TGSqlParser) context.getGsqlparser();
360            this.frameStack = gsqlparser.getFrameStack();
361
362            // CRITICAL: Set gsqlparser on the NodeFactory - matches TGSqlParser behavior
363            // This is needed for proper AST node creation during parsing
364            // Without this, expression traversal order may differ, causing
365            // dataflow constant ordering issues
366            this.fparser.getNf().setGsqlParser(gsqlparser);
367            this.fplsqlparser.getNf().setGsqlParser(gsqlparser);
368
369            // Create global context if needed
370            this.globalContext = new TContext();
371            this.sqlEnv = new TSQLEnv(this.vendor) {
372                @Override
373                public void initSQLEnv() {
374                }
375            };
376            this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements);
377        } else {
378            initializeGlobalContext();
379        }
380
381        // Parse each statement with exception handling for robustness
382        for (int i = 0; i < sqlstatements.size(); i++) {
383            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
384
385            try {
386                stmt.setFrameStack(frameStack);
387
388                // Parse the statement
389                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
390
391                // Oracle-specific post-processing (overridden hook method)
392                afterStatementParsed(stmt);
393
394                // Handle error recovery for CREATE TABLE/INDEX
395                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
396                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
397                    handleCreateTableErrorRecovery(stmt);
398                }
399
400                // Collect syntax errors
401                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
402                    copyErrorsFromStatement(stmt);
403                }
404
405            } catch (Exception ex) {
406                // Use inherited exception handler from AbstractSqlParser
407                // This provides consistent error handling across all database parsers
408                handleStatementParsingException(stmt, i, ex);
409                continue;
410            }
411        }
412
413        // Clean up frame stack
414        if (globalFrame != null) {
415            globalFrame.popMeFromStack(frameStack);
416        }
417
418        return this.sqlstatements;
419    }
420
421    // Note: initializeGlobalContext() inherited from AbstractSqlParser
422
423    /**
424     * Override to provide Oracle-specific post-processing after statement parsing.
425     * <p>
426     * For Oracle, we check if the statement is PL/SQL and recursively find syntax
427     * errors in nested PL/SQL statements.
428     */
429    @Override
430    protected void afterStatementParsed(TCustomSqlStatement stmt) {
431        if (stmt.isoracleplsql()) {
432            findAllSyntaxErrorsInPlsql(stmt);
433        }
434    }
435
436    /**
437     * Perform Oracle-specific semantic analysis using TSQLResolver.
438     *
439     * <p>This includes:
440     * <ul>
441     *   <li>Column-to-table resolution</li>
442     *   <li>Dataflow analysis</li>
443     *   <li>Reference resolution</li>
444     *   <li>Scope resolution</li>
445     * </ul>
446     *
447     * @param context the parser context
448     * @param statements the parsed statements
449     */
450    @Override
451    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
452        if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) {
453            TSQLResolver resolver = new TSQLResolver(globalContext, statements);
454            resolver.resolve();
455        }
456    }
457
458    /**
459     * Perform Oracle-specific AST interpretation/evaluation using TASTEvaluator.
460     *
461     * <p>This executes simple SQL statements and evaluates expressions
462     * for static analysis and constant folding.
463     *
464     * @param context the parser context
465     * @param statements the parsed statements
466     */
467    @Override
468    protected void performInterpreter(ParserContext context, TStatementList statements) {
469        if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) {
470            TLog.clearLogs();
471            TGlobalScope interpreterScope = new TGlobalScope(sqlEnv);
472            TLog.enableInterpreterLogOnly();
473            TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope);
474            astEvaluator.eval();
475        }
476    }
477
478    // ========== Raw Statement Extraction ==========
479    // These methods extract raw SQL statements from tokens without full parsing
480    // Extracted from TGSqlParser.dooraclegetrawsqlstatements() and related methods
481
482    /**
483     * Extract raw Oracle SQL statements from tokenized source.
484     * <p>
485     * This is the main Oracle statement extraction state machine that:
486     * <ul>
487     *   <li>Groups tokens into statement boundaries</li>
488     *   <li>Identifies statement types (SQL vs PL/SQL, SQL*Plus commands)</li>
489     *   <li>Handles nested PL/SQL blocks (procedures, functions, packages, triggers)</li>
490     *   <li>Tracks BEGIN/END pairs and other block delimiters</li>
491     *   <li>Detects statement terminators (semicolon, forward slash, period)</li>
492     * </ul>
493     *
494     * <p><b>State Machine:</b> Uses 4 main states:
495     * <ul>
496     *   <li>{@code stnormal} - Between statements, looking for start of next statement</li>
497     *   <li>{@code stsql} - Inside a SQL statement</li>
498     *   <li>{@code stsqlplus} - Inside a SQL*Plus command</li>
499     *   <li>{@code ststoredprocedure} - Inside a PL/SQL block (procedure/function/package/trigger)</li>
500     *   <li>{@code sterror} - Error recovery mode</li>
501     * </ul>
502     *
503     * <p><b>Extracted from:</b> TGSqlParser.dooraclegetrawsqlstatements() (lines 10071-10859)
504     *
505     * <p><b>Design Note:</b> This method now receives a builder to populate with results,
506     * following Option A design where the vendor-specific method focuses on parsing logic
507     * while extractRawStatements() handles result construction.
508     *
509     * @param builder the result builder to populate with statements and error information
510     */
511    private void dooraclegetrawsqlstatements(SqlParseResult.Builder builder) {
512        int waitingEnds[] = new int[stored_procedure_nested_level];
513        stored_procedure_type sptype[] = new stored_procedure_type[stored_procedure_nested_level];
514        stored_procedure_status procedure_status[] = new stored_procedure_status[stored_procedure_nested_level];
515        boolean endBySlashOnly = true;
516        int nestedProcedures = 0, nestedParenthesis = 0;
517        // Flag for CREATE MLE MODULE with AS clause - terminates with / not ;
518        boolean mleModuleWithAs = false;
519        // Flag for WITH FUNCTION/PROCEDURE - track BEGIN/END nesting to handle embedded semicolons
520        boolean withPlsqlDefinition = false;
521        int withPlsqlBeginEndNesting = 0;
522        boolean withPlsqlFoundSelect = false;  // True when SELECT has been found after WITH FUNCTION
523
524        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
525        if (!TBaseType.assigned(sourcetokenlist)) {
526            // No tokens available - populate builder with error and return
527            builder.errorCode(1);
528            builder.errorMessage("No source token list available");
529            builder.sqlStatements(new TStatementList());
530            return;
531        }
532
533        gcurrentsqlstatement = null;
534        EFindSqlStateType gst = EFindSqlStateType.stnormal;
535        TSourceToken lcprevsolidtoken = null, ast = null;
536
537        // Main tokenization loop
538        for (int i = 0; i < sourcetokenlist.size(); i++) {
539
540            if ((ast != null) && (ast.issolidtoken()))
541                lcprevsolidtoken = ast;
542
543            ast = sourcetokenlist.get(i);
544            sourcetokenlist.curpos = i;
545
546            // Token-specific keyword transformations for Oracle
547            performRawStatementTokenTransformations(ast);
548
549            // State machine processing
550            switch (gst) {
551                case sterror: {
552                    if (ast.tokentype == ETokenType.ttsemicolon) {
553                        appendToken(gcurrentsqlstatement, ast);
554                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
555                        gst = EFindSqlStateType.stnormal;
556                    } else {
557                        appendToken(gcurrentsqlstatement, ast);
558                    }
559                    break;
560                } //sterror
561
562                case stnormal: {
563                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
564                            || (ast.tokencode == TBaseType.cmtslashstar)
565                            || (ast.tokencode == TBaseType.lexspace)
566                            || (ast.tokencode == TBaseType.lexnewline)
567                            || (ast.tokentype == ETokenType.ttsemicolon)) {
568                        if (gcurrentsqlstatement != null) {
569                            appendToken(gcurrentsqlstatement, ast);
570                        }
571
572                        if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
573                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
574                                // ;;;; continuous semicolon, treat it as comment
575                                ast.tokentype = ETokenType.ttsimplecomment;
576                                ast.tokencode = TBaseType.cmtdoublehyphen;
577                            }
578                        }
579
580                        continue;
581                    }
582
583                    if (ast.tokencode == TBaseType.sqlpluscmd) {
584                        gst = EFindSqlStateType.stsqlplus;
585                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
586                        appendToken(gcurrentsqlstatement, ast);
587                        continue;
588                    }
589
590                    // find a token to start sql or plsql mode
591                    gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
592
593                    if (gcurrentsqlstatement != null) {
594                        if (gcurrentsqlstatement.isoracleplsql()) {
595                            nestedProcedures = 0;
596                            gst = EFindSqlStateType.ststoredprocedure;
597                            appendToken(gcurrentsqlstatement, ast);
598
599                            switch (gcurrentsqlstatement.sqlstatementtype) {
600                                case sstplsql_createprocedure:
601                                    sptype[nestedProcedures] = stored_procedure_type.procedure;
602                                    break;
603                                case sstplsql_createfunction:
604                                    sptype[nestedProcedures] = stored_procedure_type.function;
605                                    break;
606                                case sstplsql_createpackage:
607                                    sptype[nestedProcedures] = stored_procedure_type.package_spec;
608                                    if (ast.searchToken(TBaseType.rrw_body, 5) != null) {
609                                        sptype[nestedProcedures] = stored_procedure_type.package_body;
610                                    }
611                                    break;
612                                case sst_plsql_block:
613                                    sptype[nestedProcedures] = stored_procedure_type.block_with_declare;
614                                    if (ast.tokencode == TBaseType.rrw_begin) {
615                                        sptype[nestedProcedures] = stored_procedure_type.block_with_begin;
616                                    }
617                                    break;
618                                case sstplsql_createtrigger:
619                                    sptype[nestedProcedures] = stored_procedure_type.create_trigger;
620                                    break;
621                                case sstoraclecreatelibrary:
622                                    sptype[nestedProcedures] = stored_procedure_type.create_library;
623                                    break;
624                                case sstplsql_createtype_placeholder:
625                                    gst = EFindSqlStateType.stsql;
626                                    break;
627                                default:
628                                    sptype[nestedProcedures] = stored_procedure_type.others;
629                                    break;
630                            }
631
632                            if (sptype[0] == stored_procedure_type.block_with_declare) {
633                                endBySlashOnly = false;
634                                procedure_status[0] = stored_procedure_status.is_as;
635                            } else if (sptype[0] == stored_procedure_type.block_with_begin) {
636                                endBySlashOnly = false;
637                                procedure_status[0] = stored_procedure_status.body;
638                            } else if (sptype[0] == stored_procedure_type.procedure) {
639                                endBySlashOnly = false;
640                                procedure_status[0] = stored_procedure_status.start;
641                            } else if (sptype[0] == stored_procedure_type.function) {
642                                endBySlashOnly = false;
643                                procedure_status[0] = stored_procedure_status.start;
644                            } else if (sptype[0] == stored_procedure_type.package_spec) {
645                                endBySlashOnly = false;
646                                procedure_status[0] = stored_procedure_status.start;
647                            } else if (sptype[0] == stored_procedure_type.package_body) {
648                                endBySlashOnly = false;
649                                procedure_status[0] = stored_procedure_status.start;
650                            } else if (sptype[0] == stored_procedure_type.create_trigger) {
651                                endBySlashOnly = false;
652                                procedure_status[0] = stored_procedure_status.start;
653                            } else if (sptype[0] == stored_procedure_type.create_library) {
654                                endBySlashOnly = false;
655                                procedure_status[0] = stored_procedure_status.bodyend;
656                            } else {
657                                endBySlashOnly = true;
658                                procedure_status[0] = stored_procedure_status.bodyend;
659                            }
660
661                            if ((ast.tokencode == TBaseType.rrw_begin)
662                                    || (ast.tokencode == TBaseType.rrw_package)
663                                    || (ast.searchToken(TBaseType.rrw_package, 4) != null)) {
664                                waitingEnds[nestedProcedures] = 1;
665                            }
666                        } else {
667                            gst = EFindSqlStateType.stsql;
668                            appendToken(gcurrentsqlstatement, ast);
669                            nestedParenthesis = 0;
670                            // Check if this is CREATE MLE MODULE with AS clause (JavaScript code)
671                            // If AS is found after LANGUAGE JAVASCRIPT, it terminates with / not ;
672                            if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstoraclecreatemlemodule) {
673                                // Look ahead to see if there's an AS keyword
674                                TSourceToken asToken = ast.searchToken(TBaseType.rrw_as, 10);
675                                mleModuleWithAs = (asToken != null);
676                            } else {
677                                mleModuleWithAs = false;
678                            }
679
680                            // Check if this is WITH FUNCTION/PROCEDURE (Oracle 12c inline PL/SQL)
681                            // Need to track BEGIN/END nesting to handle embedded semicolons
682                            if (ast.tokencode == TBaseType.rrw_with && gcurrentsqlstatement.isctequery) {
683                                // Look ahead for FUNCTION or PROCEDURE keyword
684                                TSourceToken nextSolid = ast.nextSolidToken();
685                                if (nextSolid != null && (nextSolid.tokencode == TBaseType.rrw_function
686                                        || nextSolid.tokencode == TBaseType.rrw_procedure)) {
687                                    withPlsqlDefinition = true;
688                                    withPlsqlBeginEndNesting = 0;
689                                }
690                            }
691                        }
692                    } else {
693                        //error token found
694                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
695                                , "Error when tokenize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
696
697                        ast.tokentype = ETokenType.tttokenlizererrortoken;
698                        gst = EFindSqlStateType.sterror;
699
700                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
701                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
702                        appendToken(gcurrentsqlstatement, ast);
703                    }
704
705                    break;
706                } // stnormal
707
708                case stsqlplus: {
709                    if (ast.insqlpluscmd) {
710                        appendToken(gcurrentsqlstatement, ast);
711                    } else {
712                        gst = EFindSqlStateType.stnormal; //this token must be newline,
713                        appendToken(gcurrentsqlstatement, ast); // so add it here
714                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
715                    }
716
717                    break;
718                }//case stsqlplus
719
720                case stsql: {
721                    // For WITH FUNCTION/PROCEDURE, track BEGIN/END nesting and when SELECT is found
722                    if (withPlsqlDefinition) {
723                        if (ast.tokencode == TBaseType.rrw_begin) {
724                            withPlsqlBeginEndNesting++;
725                        } else if (ast.tokencode == TBaseType.rrw_end) {
726                            withPlsqlBeginEndNesting--;
727                            if (withPlsqlBeginEndNesting < 0) withPlsqlBeginEndNesting = 0;
728                        } else if (ast.tokencode == TBaseType.rrw_select && withPlsqlBeginEndNesting == 0) {
729                            // Found SELECT after all function definitions are done
730                            withPlsqlFoundSelect = true;
731                        }
732                    }
733
734                    // For CREATE MLE MODULE with AS clause, don't terminate on semicolon
735                    // The JavaScript code may contain semicolons; wait for / to terminate
736                    // For WITH FUNCTION/PROCEDURE, don't terminate on semicolon until SELECT is found
737                    // (the semicolons in function body and after END are part of the function definition)
738                    boolean skipSemicolonTermination = mleModuleWithAs || (withPlsqlDefinition && !withPlsqlFoundSelect);
739                    if (ast.tokentype == ETokenType.ttsemicolon && !skipSemicolonTermination) {
740                        gst = EFindSqlStateType.stnormal;
741                        appendToken(gcurrentsqlstatement, ast);
742                        gcurrentsqlstatement.semicolonended = ast;
743                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
744                        mleModuleWithAs = false; // Reset flag
745                        withPlsqlDefinition = false; // Reset WITH FUNCTION flag
746                        withPlsqlBeginEndNesting = 0;
747                        withPlsqlFoundSelect = false;
748                        continue;
749                    }
750
751                    if (sourcetokenlist.sqlplusaftercurtoken()) //most probably is / cmd
752                    {
753                        gst = EFindSqlStateType.stnormal;
754                        appendToken(gcurrentsqlstatement, ast);
755                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
756                        mleModuleWithAs = false; // Reset flag
757                        continue;
758                    }
759
760                    if (ast.tokencode == '(') nestedParenthesis++;
761                    if (ast.tokencode == ')') {
762                        nestedParenthesis--;
763                        if (nestedParenthesis < 0) nestedParenthesis = 0;
764                    }
765
766                    Boolean findNewStmt = false;
767                    TCustomSqlStatement lcStmt = null;
768                    if ((nestedParenthesis == 0) && (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetable)) {
769                        lcStmt = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
770                        if (lcStmt != null) {
771                            findNewStmt = true;
772                            if (lcStmt.sqlstatementtype == ESqlStatementType.sstselect) {
773                                TSourceToken prevst = ast.prevSolidToken();
774                                if ((prevst.tokencode == TBaseType.rrw_as) || (prevst.tokencode == '(') || (prevst.tokencode == ')')) {
775                                    findNewStmt = false;
776                                }
777                            }
778                        }
779                    }
780
781                    if (findNewStmt) {
782                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
783                        gcurrentsqlstatement = lcStmt;
784                        appendToken(gcurrentsqlstatement, ast);
785                        continue;
786                    } else
787                        appendToken(gcurrentsqlstatement, ast);
788
789                    break;
790                }//case stsql
791
792                case ststoredprocedure: {
793
794                    if (procedure_status[nestedProcedures] != stored_procedure_status.bodyend) {
795                        appendToken(gcurrentsqlstatement, ast);
796                    }
797
798                    switch (procedure_status[nestedProcedures]) {
799                        case cursor_declare:
800                            if (ast.tokencode == ';') {
801                                nestedProcedures--;
802                                if (nestedProcedures < 0) {
803                                    nestedProcedures = 0;
804                                }
805                            }
806                            break;
807                        case start:
808                            if ((ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is)) {
809                                if (sptype[nestedProcedures] != stored_procedure_type.create_trigger) {
810                                    if ((sptype[0] == stored_procedure_type.package_spec) && (nestedProcedures > 0)) {
811                                        // when it's a package specification, only top level accept as/is
812                                    } else {
813                                        procedure_status[nestedProcedures] = stored_procedure_status.is_as;
814                                        if (ast.searchToken("language", 1) != null) {
815                                            if (nestedProcedures == 0) {
816                                                gst = EFindSqlStateType.stsql;
817                                            } else {
818                                                procedure_status[nestedProcedures] = stored_procedure_status.body;
819                                                nestedProcedures--;
820                                            }
821                                        }
822                                    }
823                                }
824                            } else if (ast.tokencode == TBaseType.rrw_begin) {
825                                if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) {
826                                    waitingEnds[nestedProcedures]++;
827                                }
828                                if (nestedProcedures > 0) {
829                                    nestedProcedures--;
830                                }
831                                procedure_status[nestedProcedures] = stored_procedure_status.body;
832                            } else if (ast.tokencode == TBaseType.rrw_end) {
833                                if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures - 1] == 1)
834                                        && ((sptype[nestedProcedures - 1] == stored_procedure_type.package_body)
835                                        || (sptype[nestedProcedures - 1] == stored_procedure_type.package_spec))) {
836                                    nestedProcedures--;
837                                    procedure_status[nestedProcedures] = stored_procedure_status.bodyend;
838                                }
839                            } else if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) {
840                                if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0)
841                                        && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) {
842                                    nestedProcedures--;
843                                    nestedProcedures++;
844                                    waitingEnds[nestedProcedures] = 0;
845                                    procedure_status[nestedProcedures] = stored_procedure_status.start;
846                                }
847                            } else if (ast.tokencode == TBaseType.rrw_oracle_cursor) {
848                                if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0)
849                                        && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) {
850                                    nestedProcedures--;
851                                    nestedProcedures++;
852                                    waitingEnds[nestedProcedures] = 0;
853                                    procedure_status[nestedProcedures] = stored_procedure_status.cursor_declare;
854                                }
855                            } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokencode == TBaseType.rrw_declare)) {
856                                procedure_status[nestedProcedures] = stored_procedure_status.is_as;
857                            } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger)
858                                    && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) {
859                                ast.tokenstatus = ETokenStatus.tsignorebyyacc;
860                                gst = EFindSqlStateType.stnormal;
861                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
862
863                                gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
864                                appendToken(gcurrentsqlstatement, ast);
865                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
866                            } else if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) {
867                                if (ast.tokencode == TBaseType.rrw_trigger) {
868                                    TSourceToken compoundSt = ast.searchToken(TBaseType.rrw_oracle_compound, -1);
869                                    if (compoundSt != null) {
870                                        procedure_status[nestedProcedures] = stored_procedure_status.body;
871                                        waitingEnds[nestedProcedures]++;
872                                    }
873                                }
874                            } else if ((sptype[nestedProcedures] == stored_procedure_type.function)
875                                    && (ast.tokencode == TBaseType.rrw_teradata_using)) {
876                                if ((ast.searchToken("aggregate", -1) != null) || (ast.searchToken("pipelined", -1) != null)) {
877                                    if (nestedProcedures == 0) {
878                                        gst = EFindSqlStateType.stsql;
879                                    } else {
880                                        procedure_status[nestedProcedures] = stored_procedure_status.body;
881                                        nestedProcedures--;
882                                    }
883                                }
884                            }
885                            break;
886                        case is_as:
887                            if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) {
888                                nestedProcedures++;
889                                waitingEnds[nestedProcedures] = 0;
890                                procedure_status[nestedProcedures] = stored_procedure_status.start;
891
892                                if (nestedProcedures > stored_procedure_nested_level - 1) {
893                                    gst = EFindSqlStateType.sterror;
894                                    nestedProcedures--;
895                                }
896                            } else if (ast.tokencode == TBaseType.rrw_begin) {
897                                if ((nestedProcedures == 0) &&
898                                        ((sptype[nestedProcedures] == stored_procedure_type.package_body)
899                                                || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) {
900                                    // top level package begin already counted
901                                } else {
902                                    waitingEnds[nestedProcedures]++;
903                                }
904                                procedure_status[nestedProcedures] = stored_procedure_status.body;
905                            } else if (ast.tokencode == TBaseType.rrw_end) {
906                                if ((nestedProcedures == 0) && (waitingEnds[nestedProcedures] == 1)
907                                        && ((sptype[nestedProcedures] == stored_procedure_type.package_body)
908                                        || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) {
909                                    procedure_status[nestedProcedures] = stored_procedure_status.bodyend;
910                                    waitingEnds[nestedProcedures]--;
911                                } else {
912                                    waitingEnds[nestedProcedures]--;
913                                }
914                            } else if (ast.tokencode == TBaseType.rrw_case) {
915                                if (ast.searchToken(';', 1) == null) {
916                                    waitingEnds[nestedProcedures]++;
917                                }
918                            }
919                            break;
920                        case body:
921                            if (ast.tokencode == TBaseType.rrw_begin) {
922                                waitingEnds[nestedProcedures]++;
923                            } else if (ast.tokencode == TBaseType.rrw_if) {
924                                if (ast.searchToken(';', 2) == null) {
925                                    waitingEnds[nestedProcedures]++;
926                                }
927                            } else if (ast.tokencode == TBaseType.rrw_case) {
928                                if (ast.searchToken(';', 2) == null) {
929                                    if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
930                                        waitingEnds[nestedProcedures]++;
931                                    }
932                                }
933                            } else if (ast.tokencode == TBaseType.rrw_loop) {
934                                if (!((ast.searchToken(TBaseType.rrw_end, -1) != null)
935                                        && (ast.searchToken(';', 2) != null))) {
936                                    waitingEnds[nestedProcedures]++;
937                                }
938                            } else if (ast.tokencode == TBaseType.rrw_end) {
939                                waitingEnds[nestedProcedures]--;
940                                if (waitingEnds[nestedProcedures] == 0) {
941                                    if (nestedProcedures == 0) {
942                                        procedure_status[nestedProcedures] = stored_procedure_status.bodyend;
943                                    } else {
944                                        nestedProcedures--;
945                                        procedure_status[nestedProcedures] = stored_procedure_status.is_as;
946                                    }
947                                }
948                            } else if ((waitingEnds[nestedProcedures] == 0)
949                                    && (ast.tokentype == ETokenType.ttslash)
950                                    && (ast.tokencode == TBaseType.sqlpluscmd)) {
951                                ast.tokenstatus = ETokenStatus.tsignorebyyacc;
952                                gst = EFindSqlStateType.stnormal;
953                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
954
955                                gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
956                                appendToken(gcurrentsqlstatement, ast);
957                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
958                            }
959                            break;
960                        case bodyend:
961                            if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) {
962                                // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast;
963                                ast.tokenstatus = ETokenStatus.tsignorebyyacc;
964                                gst = EFindSqlStateType.stnormal;
965                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
966
967                                //make / a sqlplus cmd
968                                gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
969                                appendToken(gcurrentsqlstatement, ast);
970                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
971                            } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) {
972                                // single dot at a seperate line
973                                ast.tokenstatus = ETokenStatus.tsignorebyyacc;
974                                gst = EFindSqlStateType.stnormal;
975                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
976
977                                //make ttperiod a sqlplus cmd
978                                gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
979                                appendToken(gcurrentsqlstatement, ast);
980                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
981                            } else if ((ast.searchToken(TBaseType.rrw_package, 1) != null) && (!endBySlashOnly)) {
982                                appendToken(gcurrentsqlstatement, ast);
983                                gst = EFindSqlStateType.stnormal;
984                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
985                            } else if ((ast.searchToken(TBaseType.rrw_procedure, 1) != null) && (!endBySlashOnly)) {
986                                appendToken(gcurrentsqlstatement, ast);
987                                gst = EFindSqlStateType.stnormal;
988                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
989                            } else if ((ast.searchToken(TBaseType.rrw_function, 1) != null) && (!endBySlashOnly)) {
990                                appendToken(gcurrentsqlstatement, ast);
991                                gst = EFindSqlStateType.stnormal;
992                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
993                            } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null)
994                                    && ((ast.searchToken(TBaseType.rrw_package, 4) != null) || (ast.searchToken(TBaseType.rrw_package, 5) != null))
995                                    && (!endBySlashOnly)) {
996                                appendToken(gcurrentsqlstatement, ast);
997                                gst = EFindSqlStateType.stnormal;
998                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
999                            } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null)
1000                                    && ((ast.searchToken(TBaseType.rrw_procedure, 4) != null)
1001                                            || (ast.searchToken(TBaseType.rrw_function, 4) != null)
1002                                            || (ast.searchToken(TBaseType.rrw_view, 4) != null)
1003                                            || (ast.searchToken(TBaseType.rrw_oracle_synonym, 4) != null)
1004                                            || (ast.searchToken(TBaseType.rrw_trigger, 4) != null))
1005                                    && (!endBySlashOnly)) {
1006                                appendToken(gcurrentsqlstatement, ast);
1007                                gst = EFindSqlStateType.stnormal;
1008                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1009                            } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_library, 4) != null) && (!endBySlashOnly)) {
1010                                appendToken(gcurrentsqlstatement, ast);
1011                                gst = EFindSqlStateType.stnormal;
1012                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1013                            } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null) && (ast.searchToken(TBaseType.rrw_trigger, 2) != null) && (!endBySlashOnly)) {
1014                                appendToken(gcurrentsqlstatement, ast);
1015                                gst = EFindSqlStateType.stnormal;
1016                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1017                            } else if ((ast.searchToken(TBaseType.rrw_select, 1) != null) && (!endBySlashOnly)) {
1018                                appendToken(gcurrentsqlstatement, ast);
1019                                gst = EFindSqlStateType.stnormal;
1020                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1021                            } else if ((ast.searchToken(TBaseType.rrw_call, 1) != null) && (!endBySlashOnly)) {
1022                                appendToken(gcurrentsqlstatement, ast);
1023                                gst = EFindSqlStateType.stnormal;
1024                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1025                            } else if ((ast.searchToken(TBaseType.rrw_commit, 1) != null) && (!endBySlashOnly)) {
1026                                appendToken(gcurrentsqlstatement, ast);
1027                                gst = EFindSqlStateType.stnormal;
1028                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1029                            } else if ((ast.searchToken(TBaseType.rrw_declare, 1) != null) && (!endBySlashOnly)) {
1030                                appendToken(gcurrentsqlstatement, ast);
1031                                gst = EFindSqlStateType.stnormal;
1032                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1033                            } else if ((ast.searchToken(TBaseType.rrw_grant, 1) != null)
1034                                    && (ast.searchToken(TBaseType.rrw_execute, 2) != null) && (!endBySlashOnly)) {
1035                                appendToken(gcurrentsqlstatement, ast);
1036                                gst = EFindSqlStateType.stnormal;
1037                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1038                            } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null)
1039                                    && (ast.searchToken(TBaseType.rrw_table, 2) != null) && (!endBySlashOnly)) {
1040                                appendToken(gcurrentsqlstatement, ast);
1041                                gst = EFindSqlStateType.stnormal;
1042                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
1043                            } else {
1044                                appendToken(gcurrentsqlstatement, ast);
1045                            }
1046                            break;
1047                        case end:
1048                            break;
1049                        default:
1050                            break;
1051                    }
1052
1053                    if (ast.tokencode == TBaseType.sqlpluscmd) {
1054                        int m = flexer.getkeywordvalue(ast.getAstext());
1055                        if (m != 0) {
1056                            ast.tokencode = m;
1057                        } else if (ast.tokentype == ETokenType.ttslash) {
1058                            ast.tokencode = '/';
1059                        } else {
1060                            ast.tokencode = TBaseType.ident;
1061                        }
1062                    }
1063
1064                    final int wrapped_keyword_max_pos = 20;
1065                    if ((ast.tokencode == TBaseType.rrw_wrapped)
1066                            && (ast.posinlist - gcurrentsqlstatement.sourcetokenlist.get(0).posinlist < wrapped_keyword_max_pos)) {
1067                        if (gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.TCommonStoredProcedureSqlStatement) {
1068                            ((gudusoft.gsqlparser.stmt.TCommonStoredProcedureSqlStatement) gcurrentsqlstatement).setWrapped(true);
1069                        }
1070
1071                        if (gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage) {
1072                            if (ast.prevSolidToken() != null) {
1073                                ((gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage) gcurrentsqlstatement)
1074                                        .setPackageName(fparser.getNf().createObjectNameWithPart(ast.prevSolidToken()));
1075                            }
1076                        }
1077                    }
1078
1079                    break;
1080                } //ststoredprocedure
1081
1082            } //switch
1083        }//for
1084
1085        //last statement
1086        if ((gcurrentsqlstatement != null) &&
1087                ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) ||
1088                        (gst == EFindSqlStateType.sterror))) {
1089            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, true, builder);
1090        }
1091
1092        // Populate builder with results
1093        builder.sqlStatements(this.sqlstatements);
1094        builder.syntaxErrors(syntaxErrors instanceof ArrayList ?
1095                (ArrayList<TSyntaxError>) syntaxErrors : new ArrayList<>(syntaxErrors));
1096        builder.errorCode(syntaxErrors.isEmpty() ? 0 : syntaxErrors.size());
1097        builder.errorMessage(syntaxErrors.isEmpty() ? "" :
1098                String.format("Raw extraction completed with %d error(s)", syntaxErrors.size()));
1099    }
1100
1101    /**
1102     * Handle token transformations during raw statement extraction.
1103     * <p>
1104     * This performs Oracle-specific keyword disambiguation that must happen
1105     * before statement boundary detection. Examples:
1106     * <ul>
1107     *   <li>RETURN after WHERE → treat as identifier</li>
1108     *   <li>VALUE after BY → mark as value_after_by</li>
1109     *   <li>NEW → treat as identifier or constructor based on context</li>
1110     *   <li>And many more Oracle-specific cases</li>
1111     * </ul>
1112     *
1113     * @param ast current token being processed
1114     */
1115    private void performRawStatementTokenTransformations(TSourceToken ast) {
1116        // This method contains the keyword transformation logic from dooraclegetrawsqlstatements
1117        // It's been extracted to keep the main method more readable
1118
1119        if (ast.tokencode == TBaseType.rrw_return) {
1120            TSourceToken stMatch = ast.searchToken(TBaseType.rrw_where, 1);
1121            if (stMatch != null) {
1122                ast.tokencode = TBaseType.ident;
1123            }
1124        } else if (ast.tokencode == TBaseType.rrw_value_oracle) {
1125            TSourceToken stBy = ast.searchToken(TBaseType.rrw_by, -1);
1126            if (stBy != null) {
1127                ast.tokencode = TBaseType.rrw_value_after_by;
1128            }
1129        } else if (ast.tokencode == TBaseType.rrw_new_oracle) {
1130            TSourceToken stRightParen = ast.searchToken(')', -1);
1131            if (stRightParen != null) {
1132                ast.tokencode = TBaseType.ident;
1133            }
1134            TSourceToken stDot = ast.searchToken('.', 1);
1135            if (stDot != null) {
1136                ast.tokencode = TBaseType.ident;
1137            }
1138
1139            TSourceToken stNext = ast.searchTokenAfterObjectName();
1140            stDot = ast.searchToken('.', 1);
1141            if ((stDot == null) && (stNext != null) && (stNext.tokencode == '(')) {
1142                ast.tokencode = TBaseType.rrw_oracle_new_constructor;
1143            }
1144        } else if (ast.tokencode == TBaseType.rrw_chr_oracle) {
1145            TSourceToken stLeftParen = ast.searchToken('(', 1);
1146            if (stLeftParen == null) {
1147                ast.tokencode = TBaseType.ident;
1148            }
1149        } else if (ast.tokencode == TBaseType.rrw_log_oracle) {
1150            TSourceToken stNext = ast.searchToken(TBaseType.rrw_errors_oracle, 1);
1151            TSourceToken stPrev = ast.searchToken(TBaseType.rrw_view, -1);
1152            if (stPrev == null) {
1153                stPrev = ast.searchToken(TBaseType.rrw_oracle_supplemental, -1);
1154            }
1155            if ((stNext == null) && (stPrev == null)) {
1156                ast.tokencode = TBaseType.ident;
1157            }
1158        } else if (ast.tokencode == TBaseType.rrw_delete) {
1159            TSourceToken stPrev = ast.searchToken('.', -1);
1160            if (stPrev != null) {
1161                ast.tokencode = TBaseType.ident;
1162            }
1163        } else if (ast.tokencode == TBaseType.rrw_partition) {
1164            TSourceToken stPrev = ast.searchToken(TBaseType.rrw_add, -1);
1165            if (stPrev != null) {
1166                stPrev.tokencode = TBaseType.rrw_add_p;
1167            }
1168        } else if (ast.tokencode == TBaseType.rrw_oracle_column) {
1169            TSourceToken stPrev = ast.searchToken(TBaseType.rrw_oracle_modify, -1);
1170            if (stPrev != null) {
1171                ast.tokencode = TBaseType.rrw_oracle_column_after_modify;
1172            }
1173        } else if (ast.tokencode == TBaseType.rrw_oracle_apply) {
1174            TSourceToken stPrev = ast.searchToken(TBaseType.rrw_outer, -1);
1175            if (stPrev != null) {
1176                stPrev.tokencode = TBaseType.ORACLE_OUTER2;
1177            }
1178        } else if (ast.tokencode == TBaseType.rrw_oracle_subpartition) {
1179            TSourceToken stNext = ast.searchToken("(", 2);
1180            if (stNext != null) {
1181                TSourceToken st1 = ast.nextSolidToken();
1182                if (st1.toString().equalsIgnoreCase("template")) {
1183                    // don't change, keep as RW_SUBPARTITION
1184                } else {
1185                    ast.tokencode = TBaseType.rrw_oracle_subpartition_tablesample;
1186                }
1187            }
1188        } else if (ast.tokencode == TBaseType.rrw_primary) {
1189            TSourceToken stNext = ast.searchToken("key", 1);
1190            if (stNext == null) {
1191                ast.tokencode = TBaseType.ident;
1192            }
1193        } else if (ast.tokencode == TBaseType.rrw_oracle_offset) {
1194            TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_row, 2);
1195            if (stNext == null) {
1196                stNext = ast.searchToken(TBaseType.rrw_oracle_rows, 2);
1197            }
1198            if (stNext != null) {
1199                ast.tokencode = TBaseType.rrw_oracle_offset_row;
1200            }
1201        } else if (ast.tokencode == TBaseType.rrw_translate) {
1202            TSourceToken stNext = ast.searchToken("(", 2);
1203            if (stNext == null) {
1204                ast.tokencode = TBaseType.ident;
1205            }
1206        } else if (ast.tokencode == TBaseType.rrw_constraint) {
1207            TSourceToken stNext = ast.nextSolidToken();
1208            if (stNext == null) {
1209                ast.tokencode = TBaseType.ident;
1210            } else {
1211                if (stNext.tokencode != TBaseType.ident) {
1212                    ast.tokencode = TBaseType.ident;
1213                }
1214            }
1215        } else if (ast.tokencode == TBaseType.rrw_oracle_without) {
1216            TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_count, 1);
1217            if (stNext != null) {
1218                ast.tokencode = TBaseType.rrw_oracle_without_before_count;
1219            }
1220        } else if (ast.tokencode == TBaseType.rrw_bulk) {
1221            TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_collect, 1);
1222            if (stNext == null) {
1223                ast.tokencode = TBaseType.ident;
1224            }
1225        } else if (ast.tokencode == TBaseType.rrw_oracle_model) {
1226            TSourceToken stNext = ast.nextSolidToken();
1227            if (stNext != null) {
1228                switch (stNext.toString().toUpperCase()) {
1229                    case "RETURN":
1230                    case "REFERENCE":
1231                    case "IGNORE":
1232                    case "KEEP":
1233                    case "UNIQUE":
1234                    case "PARTITION":
1235                    case "DIMENSION":
1236                    case "MEASURES":
1237                    case "RULES":
1238                        ast.tokencode = TBaseType.rrw_oracle_model_in_model_clause;
1239                        break;
1240                    default:
1241                        ;
1242                }
1243            }
1244        }
1245    }
1246
1247    private void appendToken(TCustomSqlStatement statement, TSourceToken token) {
1248        if (statement == null || token == null) {
1249            return;
1250        }
1251        token.stmt = statement;
1252        statement.sourcetokenlist.add(token);
1253    }
1254
1255    // ========== Error Handling and Recovery ==========
1256
1257    /**
1258     * Find all syntax errors in PL/SQL statements recursively.
1259     * Extracted from TGSqlParser.findAllSyntaxErrorsInPlsql().
1260     */
1261    private void findAllSyntaxErrorsInPlsql(TCustomSqlStatement psql) {
1262        if (psql.getErrorCount() > 0) {
1263            copyErrorsFromStatement(psql);
1264        }
1265
1266        for (int k = 0; k < psql.getStatements().size(); k++) {
1267            findAllSyntaxErrorsInPlsql(psql.getStatements().get(k));
1268        }
1269    }
1270
1271    /**
1272     * Handle error recovery for CREATE TABLE/INDEX statements.
1273     * Oracle allows table properties that may not be fully parsed.
1274     * This method marks unparseable properties as SQL*Plus commands to skip them.
1275     *
1276     * <p>Extracted from TGSqlParser.doparse() lines 16916-16971
1277     */
1278    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
1279        if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) ||
1280             (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) &&
1281            (!TBaseType.c_createTableStrictParsing)) {
1282
1283            // Find the closing parenthesis of table definition
1284            int nested = 0;
1285            boolean isIgnore = false, isFoundIgnoreToken = false;
1286            TSourceToken firstIgnoreToken = null;
1287
1288            for (int k = 0; k < stmt.sourcetokenlist.size(); k++) {
1289                TSourceToken st = stmt.sourcetokenlist.get(k);
1290
1291                if (isIgnore) {
1292                    if (st.issolidtoken() && (st.tokencode != ';')) {
1293                        isFoundIgnoreToken = true;
1294                        if (firstIgnoreToken == null) {
1295                            firstIgnoreToken = st;
1296                        }
1297                    }
1298                    if (st.tokencode != ';') {
1299                        st.tokencode = TBaseType.sqlpluscmd;
1300                    }
1301                    continue;
1302                }
1303
1304                if (st.tokencode == (int) ')') {
1305                    nested--;
1306                    if (nested == 0) {
1307                        // Check if next token is "AS ( SELECT"
1308                        boolean isSelect = false;
1309                        TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
1310                        if (st1 != null) {
1311                            TSourceToken st2 = st.searchToken((int) '(', 2);
1312                            if (st2 != null) {
1313                                TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
1314                                isSelect = (st3 != null);
1315                            }
1316                        }
1317                        if (!isSelect) isIgnore = true;
1318                    }
1319                }
1320
1321                if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
1322                    nested++;
1323                }
1324            }
1325
1326            // Verify it's a valid Oracle table property
1327            if ((firstIgnoreToken != null) &&
1328                (!TBaseType.searchOracleTablePros(firstIgnoreToken.toString()))) {
1329                // Not a valid property, keep the error
1330                isFoundIgnoreToken = false;
1331            }
1332
1333            // Retry parsing if we found ignoreable properties
1334            if (isFoundIgnoreToken) {
1335                stmt.clearError();
1336                stmt.parsestatement(null, false);
1337            }
1338        }
1339    }
1340
1341    /**
1342     * Copy syntax errors from a statement to our error list.
1343     * Extracted from TGSqlParser.copyerrormsg().
1344     */
1345
1346    @Override
1347    public String toString() {
1348        return "OracleSqlParser{vendor=" + vendor + "}";
1349    }
1350
1351    // ========== Main Oracle Tokenization ==========
1352    // Core tokenization logic extracted from TGSqlParser.dooraclesqltexttotokenlist()
1353
1354    /**
1355     * Perform Oracle-specific tokenization with SQL*Plus command detection.
1356     * <p>
1357     * This method implements Oracle's complex tokenization rules including:
1358     * <ul>
1359     *   <li>SQL*Plus command detection (SPOOL, SET, START, etc.)</li>
1360     *   <li>Forward slash disambiguation (division vs PL/SQL delimiter)</li>
1361     *   <li>Oracle-specific keyword transformations (INNER, TYPE, FULL, etc.)</li>
1362     *   <li>Context-dependent token code modifications</li>
1363     * </ul>
1364     *
1365     * <p><b>State Machine:</b> Uses 5 boolean flags to track tokenization state:
1366     * <ul>
1367     *   <li>{@code insqlpluscmd} - Currently inside SQL*Plus command</li>
1368     *   <li>{@code isvalidplace} - Valid place to start SQL*Plus command</li>
1369     *   <li>{@code waitingreturnforfloatdiv} - Slash seen, waiting for newline</li>
1370     *   <li>{@code waitingreturnforsemicolon} - Semicolon seen, waiting for newline</li>
1371     *   <li>{@code continuesqlplusatnewline} - SQL*Plus command continues to next line</li>
1372     * </ul>
1373     *
1374     * <p><b>Extracted from:</b> TGSqlParser.dooraclesqltexttotokenlist() (lines 3931-4298)
1375     *
1376     * @throws RuntimeException if tokenization fails
1377     */
1378    private void dooraclesqltexttotokenlist() {
1379        // Initialize state machine for SQL*Plus command detection
1380        insqlpluscmd = false;
1381        isvalidplace = true;
1382        waitingreturnforfloatdiv = false;
1383        waitingreturnforsemicolon = false;
1384        continuesqlplusatnewline = false;
1385
1386        ESqlPlusCmd currentCmdType = ESqlPlusCmd.spcUnknown;
1387
1388        TSourceToken lct = null, prevst = null;
1389
1390        TSourceToken asourcetoken, lcprevst;
1391        int yychar;
1392
1393        asourcetoken = getanewsourcetoken();
1394        if (asourcetoken == null) return;
1395        yychar = asourcetoken.tokencode;
1396
1397        while (yychar > 0) {
1398            sourcetokenlist.add(asourcetoken);
1399
1400            switch (yychar) {
1401                case TBaseType.cmtdoublehyphen:
1402                case TBaseType.cmtslashstar:
1403                case TBaseType.lexspace: {
1404                    if (insqlpluscmd) {
1405                        asourcetoken.insqlpluscmd = true;
1406                    }
1407                    break;
1408                }
1409
1410                case TBaseType.lexnewline: {
1411                    if (insqlpluscmd) {
1412                        insqlpluscmd = false;
1413                        isvalidplace = true;
1414
1415                        if (continuesqlplusatnewline) {
1416                            insqlpluscmd = true;
1417                            isvalidplace = false;
1418                            asourcetoken.insqlpluscmd = true;
1419                        }
1420
1421                        if (!insqlpluscmd) {
1422                            currentCmdType = ESqlPlusCmd.spcUnknown;
1423                        }
1424                    }
1425
1426                    if (waitingreturnforsemicolon) {
1427                        isvalidplace = true;
1428                    }
1429
1430                    if (waitingreturnforfloatdiv) {
1431                        isvalidplace = true;
1432                        lct.tokencode = TBaseType.sqlpluscmd;
1433                        if (lct.tokentype != ETokenType.ttslash) {
1434                            lct.tokentype = ETokenType.ttsqlpluscmd;
1435                        }
1436                    }
1437
1438                    if (countLines(asourcetoken.toString()) > 1) {
1439                        // There is a line after select, so spool is the right place to start a sqlplus command
1440                        isvalidplace = true;
1441                    }
1442
1443                    flexer.insqlpluscmd = insqlpluscmd;
1444                    break;
1445                }
1446
1447                default: {
1448                    // Solid token
1449                    continuesqlplusatnewline = false;
1450                    waitingreturnforsemicolon = false;
1451                    waitingreturnforfloatdiv = false;
1452
1453                    if (insqlpluscmd) {
1454                        asourcetoken.insqlpluscmd = true;
1455                        if (asourcetoken.toString().equalsIgnoreCase("-")) {
1456                            continuesqlplusatnewline = true;
1457                        }
1458                    } else {
1459                        if (asourcetoken.tokentype == ETokenType.ttsemicolon) {
1460                            waitingreturnforsemicolon = true;
1461                        }
1462
1463                        if ((asourcetoken.tokentype == ETokenType.ttslash)
1464                                && (isvalidplace || (isValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) {
1465                            lct = asourcetoken;
1466                            waitingreturnforfloatdiv = true;
1467                        }
1468
1469                        currentCmdType = TSqlplusCmdStatement.searchCmd(asourcetoken.toString(), asourcetoken.nextToken());
1470                        if (currentCmdType != ESqlPlusCmd.spcUnknown) {
1471                            if (isvalidplace) {
1472                                TSourceToken lnbreak = null;
1473                                boolean aRealSqlplusCmd = true;
1474                                if (sourcetokenlist.curpos > 0) {
1475                                    lnbreak = sourcetokenlist.get(sourcetokenlist.curpos - 1);
1476                                    aRealSqlplusCmd = !spaceAtTheEndOfReturnToken(lnbreak.toString());
1477                                }
1478
1479                                if (aRealSqlplusCmd) {
1480                                    asourcetoken.prevTokenCode = asourcetoken.tokencode;
1481                                    asourcetoken.tokencode = TBaseType.sqlpluscmd;
1482                                    if (asourcetoken.tokentype != ETokenType.ttslash) {
1483                                        asourcetoken.tokentype = ETokenType.ttsqlpluscmd;
1484                                    }
1485                                    insqlpluscmd = true;
1486                                    flexer.insqlpluscmd = insqlpluscmd;
1487                                }
1488                            } else if ((asourcetoken.tokencode == TBaseType.rrw_connect) && (sourcetokenlist.returnbeforecurtoken(true))) {
1489                                asourcetoken.tokencode = TBaseType.sqlpluscmd;
1490                                if (asourcetoken.tokentype != ETokenType.ttslash) {
1491                                    asourcetoken.tokentype = ETokenType.ttsqlpluscmd;
1492                                }
1493                                insqlpluscmd = true;
1494                                flexer.insqlpluscmd = insqlpluscmd;
1495                            } else if (sourcetokenlist.returnbeforecurtoken(true)) {
1496                                TSourceToken lnbreak = sourcetokenlist.get(sourcetokenlist.curpos - 1);
1497
1498                                if ((countLines(lnbreak.toString()) > 1) && (!spaceAtTheEndOfReturnToken(lnbreak.toString()))) {
1499                                    asourcetoken.tokencode = TBaseType.sqlpluscmd;
1500                                    if (asourcetoken.tokentype != ETokenType.ttslash) {
1501                                        asourcetoken.tokentype = ETokenType.ttsqlpluscmd;
1502                                    }
1503                                    insqlpluscmd = true;
1504                                    flexer.insqlpluscmd = insqlpluscmd;
1505                                }
1506                            }
1507                        }
1508                    }
1509
1510                    isvalidplace = false;
1511
1512                    // Oracle-specific keyword handling (inline to match legacy behavior)
1513                    if (prevst != null) {
1514                        if (prevst.tokencode == TBaseType.rrw_inner) {
1515                            if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) {
1516                                prevst.tokencode = TBaseType.ident;
1517                            }
1518                        } else if ((prevst.tokencode == TBaseType.rrw_not)
1519                                && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) {
1520                            prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE");
1521                        }
1522                    }
1523
1524                    if (asourcetoken.tokencode == TBaseType.rrw_inner) {
1525                        prevst = asourcetoken;
1526                    } else if (asourcetoken.tokencode == TBaseType.rrw_not) {
1527                        prevst = asourcetoken;
1528                    } else {
1529                        prevst = null;
1530                    }
1531
1532                    // Oracle keyword transformations that rely on prev token state
1533                    if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD"))
1534                            || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) {
1535                        lcprevst = getprevsolidtoken(asourcetoken);
1536                        if (lcprevst != null) {
1537                            if (lcprevst.tokencode == TBaseType.rrw_for)
1538                                lcprevst.tokencode = TBaseType.rw_for1;
1539                        }
1540                    } else if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) {
1541                        TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2);
1542                        if (stKeep != null) {
1543                            stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank;
1544                        }
1545                    } else if (asourcetoken.tokencode == TBaseType.rrw_full) {
1546                        TSourceToken stMatch = asourcetoken.searchToken(TBaseType.rrw_match, -1);
1547                        if (stMatch != null) {
1548                            asourcetoken.tokencode = TBaseType.RW_FULL2;
1549                        }
1550                    } else if (asourcetoken.tokencode == TBaseType.rrw_join) {
1551                        TSourceToken stFull = asourcetoken.searchToken(TBaseType.rrw_full, -1);
1552                        if (stFull != null) {
1553                            stFull.tokencode = TBaseType.RW_FULL2;
1554                        } else {
1555                            TSourceToken stNatural = asourcetoken.searchToken(TBaseType.rrw_natural, -4);
1556                            if (stNatural != null) {
1557                                stNatural.tokencode = TBaseType.RW_NATURAL2;
1558                            }
1559                        }
1560                    } else if (asourcetoken.tokencode == TBaseType.rrw_outer) {
1561                        TSourceToken stFull = asourcetoken.searchToken(TBaseType.rrw_full, -1);
1562                        if (stFull != null) {
1563                            stFull.tokencode = TBaseType.RW_FULL2;
1564                        }
1565                    } else if (asourcetoken.tokencode == TBaseType.rrw_is) {
1566                        TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2);
1567                        if (stType != null) {
1568                            stType.tokencode = TBaseType.rrw_type2;
1569                        }
1570                    } else if (asourcetoken.tokencode == TBaseType.rrw_as) {
1571                        TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2);
1572                        if (stType != null) {
1573                            stType.tokencode = TBaseType.rrw_type2;
1574                        }
1575                    } else if (asourcetoken.tokencode == TBaseType.rrw_oid) {
1576                        TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2);
1577                        if (stType != null) {
1578                            stType.tokencode = TBaseType.rrw_type2;
1579                        }
1580                    } else if (asourcetoken.tokencode == TBaseType.rrw_type) {
1581                        TSourceToken stPrev;
1582                        stPrev = asourcetoken.searchToken(TBaseType.rrw_drop, -1);
1583                        if (stPrev != null) {
1584                            asourcetoken.tokencode = TBaseType.rrw_type2;
1585                        }
1586                        if (asourcetoken.tokencode == TBaseType.rrw_type) {
1587                            stPrev = asourcetoken.searchToken(TBaseType.rrw_of, -1);
1588                            if (stPrev != null) {
1589                                asourcetoken.tokencode = TBaseType.rrw_type2;
1590                            }
1591                        }
1592                        if (asourcetoken.tokencode == TBaseType.rrw_type) {
1593                            stPrev = asourcetoken.searchToken(TBaseType.rrw_create, -1);
1594                            if (stPrev != null) {
1595                                asourcetoken.tokencode = TBaseType.rrw_type2;
1596                            }
1597                        }
1598                        if (asourcetoken.tokencode == TBaseType.rrw_type) {
1599                            stPrev = asourcetoken.searchToken(TBaseType.rrw_replace, -1);
1600                            if (stPrev != null) {
1601                                asourcetoken.tokencode = TBaseType.rrw_type2;
1602                            }
1603                        }
1604                        if (asourcetoken.tokencode == TBaseType.rrw_type) {
1605                            stPrev = asourcetoken.searchToken('%', -1);
1606                            if (stPrev != null) {
1607                                asourcetoken.tokencode = TBaseType.rrw_type2;
1608                            }
1609                        }
1610                    } else if ((asourcetoken.tokencode == TBaseType.rrw_by) || (asourcetoken.tokencode == TBaseType.rrw_to)) {
1611                        lcprevst = getprevsolidtoken(asourcetoken);
1612                        if (lcprevst != null) {
1613                            if ((lcprevst.tokencode == TBaseType.sqlpluscmd) && (lcprevst.toString().equalsIgnoreCase("connect"))) {
1614                                lcprevst.tokencode = TBaseType.rrw_connect;
1615                                lcprevst.tokentype = ETokenType.ttkeyword;
1616                                flexer.insqlpluscmd = false;
1617
1618                                continuesqlplusatnewline = false;
1619                                waitingreturnforsemicolon = false;
1620                                waitingreturnforfloatdiv = false;
1621                                isvalidplace = false;
1622                                insqlpluscmd = false;
1623                            }
1624                        }
1625                    } else if (asourcetoken.tokencode == TBaseType.rrw_with) {
1626                        lcprevst = getprevsolidtoken(asourcetoken);
1627                        if (lcprevst != null) {
1628                            if ((lcprevst.tokencode == TBaseType.sqlpluscmd) && (lcprevst.toString().equalsIgnoreCase("start"))) {
1629                                lcprevst.tokencode = TBaseType.rrw_start;
1630                                lcprevst.tokentype = ETokenType.ttkeyword;
1631                                flexer.insqlpluscmd = false;
1632
1633                                continuesqlplusatnewline = false;
1634                                waitingreturnforsemicolon = false;
1635                                waitingreturnforfloatdiv = false;
1636                                isvalidplace = false;
1637                                insqlpluscmd = false;
1638                            }
1639                        }
1640                    } else if (asourcetoken.tokencode == TBaseType.rrw_set) {
1641                        lcprevst = getprevsolidtoken(asourcetoken);
1642                        if (lcprevst != null) {
1643                            if (lcprevst.getAstext().equalsIgnoreCase("a")) {
1644                                TSourceToken lcpp = getprevsolidtoken(lcprevst);
1645                                if (lcpp != null) {
1646                                    if ((lcpp.tokencode == TBaseType.rrw_not) || (lcpp.tokencode == TBaseType.rrw_is)) {
1647                                        lcprevst.tokencode = TBaseType.rrw_oracle_a_in_aset;
1648                                        asourcetoken.tokencode = TBaseType.rrw_oracle_set_in_aset;
1649                                    }
1650                                }
1651                            }
1652                        }
1653                    }
1654
1655                    break;
1656                }
1657            }
1658
1659            // Get next token
1660            asourcetoken = getanewsourcetoken();
1661            if (asourcetoken != null) {
1662                yychar = asourcetoken.tokencode;
1663
1664                // Handle special case: dot after SQL*Plus commands
1665                if ((asourcetoken.tokencode == '.') && (getprevsolidtoken(asourcetoken) != null)
1666                        && ((currentCmdType == ESqlPlusCmd.spcAppend)
1667                        || (currentCmdType == ESqlPlusCmd.spcChange) || (currentCmdType == ESqlPlusCmd.spcInput)
1668                        || (currentCmdType == ESqlPlusCmd.spcList) || (currentCmdType == ESqlPlusCmd.spcRun))) {
1669                    // a.ent_rp_usr_id is not a real sqlplus command
1670                    TSourceToken lcprevst2 = getprevsolidtoken(asourcetoken);
1671                    lcprevst2.insqlpluscmd = false;
1672                    if (lcprevst2.prevTokenCode != 0) {
1673                        lcprevst2.tokencode = lcprevst2.prevTokenCode;
1674                    } else {
1675                        lcprevst2.tokencode = TBaseType.ident;
1676                    }
1677
1678                    flexer.insqlpluscmd = false;
1679                    continuesqlplusatnewline = false;
1680                    waitingreturnforsemicolon = false;
1681                    waitingreturnforfloatdiv = false;
1682                    isvalidplace = false;
1683                    insqlpluscmd = false;
1684                }
1685            } else {
1686                yychar = 0;
1687
1688                if (waitingreturnforfloatdiv) {
1689                    // / at the end of line treat as sqlplus command
1690                    lct.tokencode = TBaseType.sqlpluscmd;
1691                    if (lct.tokentype != ETokenType.ttslash) {
1692                        lct.tokentype = ETokenType.ttsqlpluscmd;
1693                    }
1694                }
1695            }
1696
1697            if ((yychar == 0) && (prevst != null)) {
1698                if (prevst.tokencode == TBaseType.rrw_inner) {
1699                    prevst.tokencode = TBaseType.ident;
1700                }
1701            }
1702        }
1703    }
1704
1705    // ========== Helper Methods for Tokenization ==========
1706    // These methods support Oracle-specific tokenization logic
1707
1708    /**
1709     * Count number of newlines in a string.
1710     *
1711     * @param s string to analyze
1712     * @return number of line breaks (LF or CR)
1713     */
1714    private int countLines(String s) {
1715        int pos = 0, lf = 0, cr = 0;
1716
1717        while (pos < s.length()) {
1718            if (s.charAt(pos) == '\r') {
1719                cr++;
1720                pos++;
1721                continue;
1722            }
1723            if (s.charAt(pos) == '\n') {
1724                lf++;
1725                pos++;
1726                continue;
1727            }
1728
1729            if (s.charAt(pos) == ' ') {
1730                pos++;
1731                continue;
1732            }
1733            break;
1734        }
1735
1736        if (lf >= cr) return lf;
1737        else return cr;
1738    }
1739
1740    /**
1741     * Check if return token ends with space or tab.
1742     *
1743     * @param s token text
1744     * @return true if ends with space/tab
1745     */
1746    private boolean spaceAtTheEndOfReturnToken(String s) {
1747        if (s == null) return false;
1748        if (s.length() == 0) return false;
1749
1750        return ((s.charAt(s.length() - 1) == ' ') || (s.charAt(s.length() - 1) == '\t'));
1751    }
1752
1753    /**
1754     * Determine if forward slash should be treated as SQL*Plus command delimiter.
1755     * <p>
1756     * Oracle uses '/' as both division operator and SQL*Plus block delimiter.
1757     * This method disambiguates by checking if the '/' appears at the beginning
1758     * of a line (after a return token without trailing whitespace).
1759     *
1760     * @param pstlist token list
1761     * @param pPos position of '/' token
1762     * @return true if '/' should be SQL*Plus command
1763     */
1764    private boolean isValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) {
1765        boolean ret = false;
1766
1767        if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret;
1768
1769        // Token directly before div must be ttreturn without space appending it
1770        gudusoft.gsqlparser.TSourceToken lcst = pstlist.get(pPos - 1);
1771        if (lcst.tokentype != gudusoft.gsqlparser.ETokenType.ttreturn) {
1772            return ret;
1773        }
1774
1775        if (!(lcst.getAstext().charAt(lcst.getAstext().length() - 1) == ' ')) {
1776            ret = true;
1777        }
1778
1779        return ret;
1780    }
1781
1782    /**
1783     * Get previous non-whitespace token.
1784     *
1785     * @param ptoken current token
1786     * @return previous solid token, or null
1787     */
1788    private gudusoft.gsqlparser.TSourceToken getprevsolidtoken(gudusoft.gsqlparser.TSourceToken ptoken) {
1789        gudusoft.gsqlparser.TSourceToken ret = null;
1790        TSourceTokenList lctokenlist = ptoken.container;
1791
1792        if (lctokenlist != null) {
1793            if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) {
1794                if (!(
1795                        (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttwhitespace)
1796                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttreturn)
1797                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttsimplecomment)
1798                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttbracketedcomment)
1799                )) {
1800                    ret = lctokenlist.get(ptoken.posinlist - 1);
1801                } else {
1802                    ret = lctokenlist.nextsolidtoken(ptoken.posinlist - 1, -1, false);
1803                }
1804            }
1805        }
1806        return ret;
1807    }
1808}