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
518        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
519        if (!TBaseType.assigned(sourcetokenlist)) {
520            // No tokens available - populate builder with error and return
521            builder.errorCode(1);
522            builder.errorMessage("No source token list available");
523            builder.sqlStatements(new TStatementList());
524            return;
525        }
526
527        gcurrentsqlstatement = null;
528        EFindSqlStateType gst = EFindSqlStateType.stnormal;
529        TSourceToken lcprevsolidtoken = null, ast = null;
530
531        // Main tokenization loop
532        for (int i = 0; i < sourcetokenlist.size(); i++) {
533
534            if ((ast != null) && (ast.issolidtoken()))
535                lcprevsolidtoken = ast;
536
537            ast = sourcetokenlist.get(i);
538            sourcetokenlist.curpos = i;
539
540            // Token-specific keyword transformations for Oracle
541            performRawStatementTokenTransformations(ast);
542
543            // State machine processing
544            switch (gst) {
545                case sterror: {
546                    if (ast.tokentype == ETokenType.ttsemicolon) {
547                        appendToken(gcurrentsqlstatement, ast);
548                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
549                        gst = EFindSqlStateType.stnormal;
550                    } else {
551                        appendToken(gcurrentsqlstatement, ast);
552                    }
553                    break;
554                } //sterror
555
556                case stnormal: {
557                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
558                            || (ast.tokencode == TBaseType.cmtslashstar)
559                            || (ast.tokencode == TBaseType.lexspace)
560                            || (ast.tokencode == TBaseType.lexnewline)
561                            || (ast.tokentype == ETokenType.ttsemicolon)) {
562                        if (gcurrentsqlstatement != null) {
563                            appendToken(gcurrentsqlstatement, ast);
564                        }
565
566                        if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
567                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
568                                // ;;;; continuous semicolon, treat it as comment
569                                ast.tokentype = ETokenType.ttsimplecomment;
570                                ast.tokencode = TBaseType.cmtdoublehyphen;
571                            }
572                        }
573
574                        continue;
575                    }
576
577                    if (ast.tokencode == TBaseType.sqlpluscmd) {
578                        gst = EFindSqlStateType.stsqlplus;
579                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
580                        appendToken(gcurrentsqlstatement, ast);
581                        continue;
582                    }
583
584                    // find a token to start sql or plsql mode
585                    gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
586
587                    if (gcurrentsqlstatement != null) {
588                        if (gcurrentsqlstatement.isoracleplsql()) {
589                            nestedProcedures = 0;
590                            gst = EFindSqlStateType.ststoredprocedure;
591                            appendToken(gcurrentsqlstatement, ast);
592
593                            switch (gcurrentsqlstatement.sqlstatementtype) {
594                                case sstplsql_createprocedure:
595                                    sptype[nestedProcedures] = stored_procedure_type.procedure;
596                                    break;
597                                case sstplsql_createfunction:
598                                    sptype[nestedProcedures] = stored_procedure_type.function;
599                                    break;
600                                case sstplsql_createpackage:
601                                    sptype[nestedProcedures] = stored_procedure_type.package_spec;
602                                    if (ast.searchToken(TBaseType.rrw_body, 5) != null) {
603                                        sptype[nestedProcedures] = stored_procedure_type.package_body;
604                                    }
605                                    break;
606                                case sst_plsql_block:
607                                    sptype[nestedProcedures] = stored_procedure_type.block_with_declare;
608                                    if (ast.tokencode == TBaseType.rrw_begin) {
609                                        sptype[nestedProcedures] = stored_procedure_type.block_with_begin;
610                                    }
611                                    break;
612                                case sstplsql_createtrigger:
613                                    sptype[nestedProcedures] = stored_procedure_type.create_trigger;
614                                    break;
615                                case sstoraclecreatelibrary:
616                                    sptype[nestedProcedures] = stored_procedure_type.create_library;
617                                    break;
618                                case sstplsql_createtype_placeholder:
619                                    gst = EFindSqlStateType.stsql;
620                                    break;
621                                default:
622                                    sptype[nestedProcedures] = stored_procedure_type.others;
623                                    break;
624                            }
625
626                            if (sptype[0] == stored_procedure_type.block_with_declare) {
627                                endBySlashOnly = false;
628                                procedure_status[0] = stored_procedure_status.is_as;
629                            } else if (sptype[0] == stored_procedure_type.block_with_begin) {
630                                endBySlashOnly = false;
631                                procedure_status[0] = stored_procedure_status.body;
632                            } else if (sptype[0] == stored_procedure_type.procedure) {
633                                endBySlashOnly = false;
634                                procedure_status[0] = stored_procedure_status.start;
635                            } else if (sptype[0] == stored_procedure_type.function) {
636                                endBySlashOnly = false;
637                                procedure_status[0] = stored_procedure_status.start;
638                            } else if (sptype[0] == stored_procedure_type.package_spec) {
639                                endBySlashOnly = false;
640                                procedure_status[0] = stored_procedure_status.start;
641                            } else if (sptype[0] == stored_procedure_type.package_body) {
642                                endBySlashOnly = false;
643                                procedure_status[0] = stored_procedure_status.start;
644                            } else if (sptype[0] == stored_procedure_type.create_trigger) {
645                                endBySlashOnly = false;
646                                procedure_status[0] = stored_procedure_status.start;
647                            } else if (sptype[0] == stored_procedure_type.create_library) {
648                                endBySlashOnly = false;
649                                procedure_status[0] = stored_procedure_status.bodyend;
650                            } else {
651                                endBySlashOnly = true;
652                                procedure_status[0] = stored_procedure_status.bodyend;
653                            }
654
655                            if ((ast.tokencode == TBaseType.rrw_begin)
656                                    || (ast.tokencode == TBaseType.rrw_package)
657                                    || (ast.searchToken(TBaseType.rrw_package, 4) != null)) {
658                                waitingEnds[nestedProcedures] = 1;
659                            }
660                        } else {
661                            gst = EFindSqlStateType.stsql;
662                            appendToken(gcurrentsqlstatement, ast);
663                            nestedParenthesis = 0;
664                        }
665                    } else {
666                        //error token found
667                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo)
668                                , "Error when tokenize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
669
670                        ast.tokentype = ETokenType.tttokenlizererrortoken;
671                        gst = EFindSqlStateType.sterror;
672
673                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
674                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
675                        appendToken(gcurrentsqlstatement, ast);
676                    }
677
678                    break;
679                } // stnormal
680
681                case stsqlplus: {
682                    if (ast.insqlpluscmd) {
683                        appendToken(gcurrentsqlstatement, ast);
684                    } else {
685                        gst = EFindSqlStateType.stnormal; //this token must be newline,
686                        appendToken(gcurrentsqlstatement, ast); // so add it here
687                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
688                    }
689
690                    break;
691                }//case stsqlplus
692
693                case stsql: {
694                    if (ast.tokentype == ETokenType.ttsemicolon) {
695                        gst = EFindSqlStateType.stnormal;
696                        appendToken(gcurrentsqlstatement, ast);
697                        gcurrentsqlstatement.semicolonended = ast;
698                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
699                        continue;
700                    }
701
702                    if (sourcetokenlist.sqlplusaftercurtoken()) //most probably is / cmd
703                    {
704                        gst = EFindSqlStateType.stnormal;
705                        appendToken(gcurrentsqlstatement, ast);
706                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
707                        continue;
708                    }
709
710                    if (ast.tokencode == '(') nestedParenthesis++;
711                    if (ast.tokencode == ')') {
712                        nestedParenthesis--;
713                        if (nestedParenthesis < 0) nestedParenthesis = 0;
714                    }
715
716                    Boolean findNewStmt = false;
717                    TCustomSqlStatement lcStmt = null;
718                    if ((nestedParenthesis == 0) && (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetable)) {
719                        lcStmt = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
720                        if (lcStmt != null) {
721                            findNewStmt = true;
722                            if (lcStmt.sqlstatementtype == ESqlStatementType.sstselect) {
723                                TSourceToken prevst = ast.prevSolidToken();
724                                if ((prevst.tokencode == TBaseType.rrw_as) || (prevst.tokencode == '(') || (prevst.tokencode == ')')) {
725                                    findNewStmt = false;
726                                }
727                            }
728                        }
729                    }
730
731                    if (findNewStmt) {
732                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
733                        gcurrentsqlstatement = lcStmt;
734                        appendToken(gcurrentsqlstatement, ast);
735                        continue;
736                    } else
737                        appendToken(gcurrentsqlstatement, ast);
738
739                    break;
740                }//case stsql
741
742                case ststoredprocedure: {
743
744                    if (procedure_status[nestedProcedures] != stored_procedure_status.bodyend) {
745                        appendToken(gcurrentsqlstatement, ast);
746                    }
747
748                    switch (procedure_status[nestedProcedures]) {
749                        case cursor_declare:
750                            if (ast.tokencode == ';') {
751                                nestedProcedures--;
752                                if (nestedProcedures < 0) {
753                                    nestedProcedures = 0;
754                                }
755                            }
756                            break;
757                        case start:
758                            if ((ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is)) {
759                                if (sptype[nestedProcedures] != stored_procedure_type.create_trigger) {
760                                    if ((sptype[0] == stored_procedure_type.package_spec) && (nestedProcedures > 0)) {
761                                        // when it's a package specification, only top level accept as/is
762                                    } else {
763                                        procedure_status[nestedProcedures] = stored_procedure_status.is_as;
764                                        if (ast.searchToken("language", 1) != null) {
765                                            if (nestedProcedures == 0) {
766                                                gst = EFindSqlStateType.stsql;
767                                            } else {
768                                                procedure_status[nestedProcedures] = stored_procedure_status.body;
769                                                nestedProcedures--;
770                                            }
771                                        }
772                                    }
773                                }
774                            } else if (ast.tokencode == TBaseType.rrw_begin) {
775                                if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) {
776                                    waitingEnds[nestedProcedures]++;
777                                }
778                                if (nestedProcedures > 0) {
779                                    nestedProcedures--;
780                                }
781                                procedure_status[nestedProcedures] = stored_procedure_status.body;
782                            } else if (ast.tokencode == TBaseType.rrw_end) {
783                                if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures - 1] == 1)
784                                        && ((sptype[nestedProcedures - 1] == stored_procedure_type.package_body)
785                                        || (sptype[nestedProcedures - 1] == stored_procedure_type.package_spec))) {
786                                    nestedProcedures--;
787                                    procedure_status[nestedProcedures] = stored_procedure_status.bodyend;
788                                }
789                            } else if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) {
790                                if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0)
791                                        && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) {
792                                    nestedProcedures--;
793                                    nestedProcedures++;
794                                    waitingEnds[nestedProcedures] = 0;
795                                    procedure_status[nestedProcedures] = stored_procedure_status.start;
796                                }
797                            } else if (ast.tokencode == TBaseType.rrw_oracle_cursor) {
798                                if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0)
799                                        && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) {
800                                    nestedProcedures--;
801                                    nestedProcedures++;
802                                    waitingEnds[nestedProcedures] = 0;
803                                    procedure_status[nestedProcedures] = stored_procedure_status.cursor_declare;
804                                }
805                            } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokencode == TBaseType.rrw_declare)) {
806                                procedure_status[nestedProcedures] = stored_procedure_status.is_as;
807                            } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger)
808                                    && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) {
809                                ast.tokenstatus = ETokenStatus.tsignorebyyacc;
810                                gst = EFindSqlStateType.stnormal;
811                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
812
813                                gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
814                                appendToken(gcurrentsqlstatement, ast);
815                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
816                            } else if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) {
817                                if (ast.tokencode == TBaseType.rrw_trigger) {
818                                    TSourceToken compoundSt = ast.searchToken(TBaseType.rrw_oracle_compound, -1);
819                                    if (compoundSt != null) {
820                                        procedure_status[nestedProcedures] = stored_procedure_status.body;
821                                        waitingEnds[nestedProcedures]++;
822                                    }
823                                }
824                            } else if ((sptype[nestedProcedures] == stored_procedure_type.function)
825                                    && (ast.tokencode == TBaseType.rrw_teradata_using)) {
826                                if ((ast.searchToken("aggregate", -1) != null) || (ast.searchToken("pipelined", -1) != null)) {
827                                    if (nestedProcedures == 0) {
828                                        gst = EFindSqlStateType.stsql;
829                                    } else {
830                                        procedure_status[nestedProcedures] = stored_procedure_status.body;
831                                        nestedProcedures--;
832                                    }
833                                }
834                            }
835                            break;
836                        case is_as:
837                            if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) {
838                                nestedProcedures++;
839                                waitingEnds[nestedProcedures] = 0;
840                                procedure_status[nestedProcedures] = stored_procedure_status.start;
841
842                                if (nestedProcedures > stored_procedure_nested_level - 1) {
843                                    gst = EFindSqlStateType.sterror;
844                                    nestedProcedures--;
845                                }
846                            } else if (ast.tokencode == TBaseType.rrw_begin) {
847                                if ((nestedProcedures == 0) &&
848                                        ((sptype[nestedProcedures] == stored_procedure_type.package_body)
849                                                || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) {
850                                    // top level package begin already counted
851                                } else {
852                                    waitingEnds[nestedProcedures]++;
853                                }
854                                procedure_status[nestedProcedures] = stored_procedure_status.body;
855                            } else if (ast.tokencode == TBaseType.rrw_end) {
856                                if ((nestedProcedures == 0) && (waitingEnds[nestedProcedures] == 1)
857                                        && ((sptype[nestedProcedures] == stored_procedure_type.package_body)
858                                        || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) {
859                                    procedure_status[nestedProcedures] = stored_procedure_status.bodyend;
860                                    waitingEnds[nestedProcedures]--;
861                                } else {
862                                    waitingEnds[nestedProcedures]--;
863                                }
864                            } else if (ast.tokencode == TBaseType.rrw_case) {
865                                if (ast.searchToken(';', 1) == null) {
866                                    waitingEnds[nestedProcedures]++;
867                                }
868                            }
869                            break;
870                        case body:
871                            if (ast.tokencode == TBaseType.rrw_begin) {
872                                waitingEnds[nestedProcedures]++;
873                            } else if (ast.tokencode == TBaseType.rrw_if) {
874                                if (ast.searchToken(';', 2) == null) {
875                                    waitingEnds[nestedProcedures]++;
876                                }
877                            } else if (ast.tokencode == TBaseType.rrw_case) {
878                                if (ast.searchToken(';', 2) == null) {
879                                    if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
880                                        waitingEnds[nestedProcedures]++;
881                                    }
882                                }
883                            } else if (ast.tokencode == TBaseType.rrw_loop) {
884                                if (!((ast.searchToken(TBaseType.rrw_end, -1) != null)
885                                        && (ast.searchToken(';', 2) != null))) {
886                                    waitingEnds[nestedProcedures]++;
887                                }
888                            } else if (ast.tokencode == TBaseType.rrw_end) {
889                                waitingEnds[nestedProcedures]--;
890                                if (waitingEnds[nestedProcedures] == 0) {
891                                    if (nestedProcedures == 0) {
892                                        procedure_status[nestedProcedures] = stored_procedure_status.bodyend;
893                                    } else {
894                                        nestedProcedures--;
895                                        procedure_status[nestedProcedures] = stored_procedure_status.is_as;
896                                    }
897                                }
898                            } else if ((waitingEnds[nestedProcedures] == 0)
899                                    && (ast.tokentype == ETokenType.ttslash)
900                                    && (ast.tokencode == TBaseType.sqlpluscmd)) {
901                                ast.tokenstatus = ETokenStatus.tsignorebyyacc;
902                                gst = EFindSqlStateType.stnormal;
903                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
904
905                                gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
906                                appendToken(gcurrentsqlstatement, ast);
907                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
908                            }
909                            break;
910                        case bodyend:
911                            if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) {
912                                // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast;
913                                ast.tokenstatus = ETokenStatus.tsignorebyyacc;
914                                gst = EFindSqlStateType.stnormal;
915                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
916
917                                //make / a sqlplus cmd
918                                gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
919                                appendToken(gcurrentsqlstatement, ast);
920                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
921                            } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) {
922                                // single dot at a seperate line
923                                ast.tokenstatus = ETokenStatus.tsignorebyyacc;
924                                gst = EFindSqlStateType.stnormal;
925                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
926
927                                //make ttperiod a sqlplus cmd
928                                gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
929                                appendToken(gcurrentsqlstatement, ast);
930                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
931                            } else if ((ast.searchToken(TBaseType.rrw_package, 1) != null) && (!endBySlashOnly)) {
932                                appendToken(gcurrentsqlstatement, ast);
933                                gst = EFindSqlStateType.stnormal;
934                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
935                            } else if ((ast.searchToken(TBaseType.rrw_procedure, 1) != null) && (!endBySlashOnly)) {
936                                appendToken(gcurrentsqlstatement, ast);
937                                gst = EFindSqlStateType.stnormal;
938                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
939                            } else if ((ast.searchToken(TBaseType.rrw_function, 1) != null) && (!endBySlashOnly)) {
940                                appendToken(gcurrentsqlstatement, ast);
941                                gst = EFindSqlStateType.stnormal;
942                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
943                            } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null)
944                                    && ((ast.searchToken(TBaseType.rrw_package, 4) != null) || (ast.searchToken(TBaseType.rrw_package, 5) != null))
945                                    && (!endBySlashOnly)) {
946                                appendToken(gcurrentsqlstatement, ast);
947                                gst = EFindSqlStateType.stnormal;
948                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
949                            } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null)
950                                    && ((ast.searchToken(TBaseType.rrw_procedure, 4) != null)
951                                            || (ast.searchToken(TBaseType.rrw_function, 4) != null)
952                                            || (ast.searchToken(TBaseType.rrw_view, 4) != null)
953                                            || (ast.searchToken(TBaseType.rrw_oracle_synonym, 4) != null)
954                                            || (ast.searchToken(TBaseType.rrw_trigger, 4) != null))
955                                    && (!endBySlashOnly)) {
956                                appendToken(gcurrentsqlstatement, ast);
957                                gst = EFindSqlStateType.stnormal;
958                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
959                            } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_library, 4) != null) && (!endBySlashOnly)) {
960                                appendToken(gcurrentsqlstatement, ast);
961                                gst = EFindSqlStateType.stnormal;
962                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
963                            } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null) && (ast.searchToken(TBaseType.rrw_trigger, 2) != null) && (!endBySlashOnly)) {
964                                appendToken(gcurrentsqlstatement, ast);
965                                gst = EFindSqlStateType.stnormal;
966                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
967                            } else if ((ast.searchToken(TBaseType.rrw_select, 1) != null) && (!endBySlashOnly)) {
968                                appendToken(gcurrentsqlstatement, ast);
969                                gst = EFindSqlStateType.stnormal;
970                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
971                            } else if ((ast.searchToken(TBaseType.rrw_call, 1) != null) && (!endBySlashOnly)) {
972                                appendToken(gcurrentsqlstatement, ast);
973                                gst = EFindSqlStateType.stnormal;
974                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
975                            } else if ((ast.searchToken(TBaseType.rrw_commit, 1) != null) && (!endBySlashOnly)) {
976                                appendToken(gcurrentsqlstatement, ast);
977                                gst = EFindSqlStateType.stnormal;
978                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
979                            } else if ((ast.searchToken(TBaseType.rrw_declare, 1) != null) && (!endBySlashOnly)) {
980                                appendToken(gcurrentsqlstatement, ast);
981                                gst = EFindSqlStateType.stnormal;
982                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
983                            } else if ((ast.searchToken(TBaseType.rrw_grant, 1) != null)
984                                    && (ast.searchToken(TBaseType.rrw_execute, 2) != null) && (!endBySlashOnly)) {
985                                appendToken(gcurrentsqlstatement, ast);
986                                gst = EFindSqlStateType.stnormal;
987                                onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder);
988                            } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null)
989                                    && (ast.searchToken(TBaseType.rrw_table, 2) != 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 {
994                                appendToken(gcurrentsqlstatement, ast);
995                            }
996                            break;
997                        case end:
998                            break;
999                        default:
1000                            break;
1001                    }
1002
1003                    if (ast.tokencode == TBaseType.sqlpluscmd) {
1004                        int m = flexer.getkeywordvalue(ast.getAstext());
1005                        if (m != 0) {
1006                            ast.tokencode = m;
1007                        } else if (ast.tokentype == ETokenType.ttslash) {
1008                            ast.tokencode = '/';
1009                        } else {
1010                            ast.tokencode = TBaseType.ident;
1011                        }
1012                    }
1013
1014                    final int wrapped_keyword_max_pos = 20;
1015                    if ((ast.tokencode == TBaseType.rrw_wrapped)
1016                            && (ast.posinlist - gcurrentsqlstatement.sourcetokenlist.get(0).posinlist < wrapped_keyword_max_pos)) {
1017                        if (gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.TCommonStoredProcedureSqlStatement) {
1018                            ((gudusoft.gsqlparser.stmt.TCommonStoredProcedureSqlStatement) gcurrentsqlstatement).setWrapped(true);
1019                        }
1020
1021                        if (gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage) {
1022                            if (ast.prevSolidToken() != null) {
1023                                ((gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage) gcurrentsqlstatement)
1024                                        .setPackageName(fparser.getNf().createObjectNameWithPart(ast.prevSolidToken()));
1025                            }
1026                        }
1027                    }
1028
1029                    break;
1030                } //ststoredprocedure
1031
1032            } //switch
1033        }//for
1034
1035        //last statement
1036        if ((gcurrentsqlstatement != null) &&
1037                ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) ||
1038                        (gst == EFindSqlStateType.sterror))) {
1039            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, true, builder);
1040        }
1041
1042        // Populate builder with results
1043        builder.sqlStatements(this.sqlstatements);
1044        builder.syntaxErrors(syntaxErrors instanceof ArrayList ?
1045                (ArrayList<TSyntaxError>) syntaxErrors : new ArrayList<>(syntaxErrors));
1046        builder.errorCode(syntaxErrors.isEmpty() ? 0 : syntaxErrors.size());
1047        builder.errorMessage(syntaxErrors.isEmpty() ? "" :
1048                String.format("Raw extraction completed with %d error(s)", syntaxErrors.size()));
1049    }
1050
1051    /**
1052     * Handle token transformations during raw statement extraction.
1053     * <p>
1054     * This performs Oracle-specific keyword disambiguation that must happen
1055     * before statement boundary detection. Examples:
1056     * <ul>
1057     *   <li>RETURN after WHERE → treat as identifier</li>
1058     *   <li>VALUE after BY → mark as value_after_by</li>
1059     *   <li>NEW → treat as identifier or constructor based on context</li>
1060     *   <li>And many more Oracle-specific cases</li>
1061     * </ul>
1062     *
1063     * @param ast current token being processed
1064     */
1065    private void performRawStatementTokenTransformations(TSourceToken ast) {
1066        // This method contains the keyword transformation logic from dooraclegetrawsqlstatements
1067        // It's been extracted to keep the main method more readable
1068
1069        if (ast.tokencode == TBaseType.rrw_return) {
1070            TSourceToken stMatch = ast.searchToken(TBaseType.rrw_where, 1);
1071            if (stMatch != null) {
1072                ast.tokencode = TBaseType.ident;
1073            }
1074        } else if (ast.tokencode == TBaseType.rrw_value_oracle) {
1075            TSourceToken stBy = ast.searchToken(TBaseType.rrw_by, -1);
1076            if (stBy != null) {
1077                ast.tokencode = TBaseType.rrw_value_after_by;
1078            }
1079        } else if (ast.tokencode == TBaseType.rrw_new_oracle) {
1080            TSourceToken stRightParen = ast.searchToken(')', -1);
1081            if (stRightParen != null) {
1082                ast.tokencode = TBaseType.ident;
1083            }
1084            TSourceToken stDot = ast.searchToken('.', 1);
1085            if (stDot != null) {
1086                ast.tokencode = TBaseType.ident;
1087            }
1088
1089            TSourceToken stNext = ast.searchTokenAfterObjectName();
1090            stDot = ast.searchToken('.', 1);
1091            if ((stDot == null) && (stNext != null) && (stNext.tokencode == '(')) {
1092                ast.tokencode = TBaseType.rrw_oracle_new_constructor;
1093            }
1094        } else if (ast.tokencode == TBaseType.rrw_chr_oracle) {
1095            TSourceToken stLeftParen = ast.searchToken('(', 1);
1096            if (stLeftParen == null) {
1097                ast.tokencode = TBaseType.ident;
1098            }
1099        } else if (ast.tokencode == TBaseType.rrw_log_oracle) {
1100            TSourceToken stNext = ast.searchToken(TBaseType.rrw_errors_oracle, 1);
1101            TSourceToken stPrev = ast.searchToken(TBaseType.rrw_view, -1);
1102            if (stPrev == null) {
1103                stPrev = ast.searchToken(TBaseType.rrw_oracle_supplemental, -1);
1104            }
1105            if ((stNext == null) && (stPrev == null)) {
1106                ast.tokencode = TBaseType.ident;
1107            }
1108        } else if (ast.tokencode == TBaseType.rrw_delete) {
1109            TSourceToken stPrev = ast.searchToken('.', -1);
1110            if (stPrev != null) {
1111                ast.tokencode = TBaseType.ident;
1112            }
1113        } else if (ast.tokencode == TBaseType.rrw_partition) {
1114            TSourceToken stPrev = ast.searchToken(TBaseType.rrw_add, -1);
1115            if (stPrev != null) {
1116                stPrev.tokencode = TBaseType.rrw_add_p;
1117            }
1118        } else if (ast.tokencode == TBaseType.rrw_oracle_column) {
1119            TSourceToken stPrev = ast.searchToken(TBaseType.rrw_oracle_modify, -1);
1120            if (stPrev != null) {
1121                ast.tokencode = TBaseType.rrw_oracle_column_after_modify;
1122            }
1123        } else if (ast.tokencode == TBaseType.rrw_oracle_apply) {
1124            TSourceToken stPrev = ast.searchToken(TBaseType.rrw_outer, -1);
1125            if (stPrev != null) {
1126                stPrev.tokencode = TBaseType.ORACLE_OUTER2;
1127            }
1128        } else if (ast.tokencode == TBaseType.rrw_oracle_subpartition) {
1129            TSourceToken stNext = ast.searchToken("(", 2);
1130            if (stNext != null) {
1131                TSourceToken st1 = ast.nextSolidToken();
1132                if (st1.toString().equalsIgnoreCase("template")) {
1133                    // don't change, keep as RW_SUBPARTITION
1134                } else {
1135                    ast.tokencode = TBaseType.rrw_oracle_subpartition_tablesample;
1136                }
1137            }
1138        } else if (ast.tokencode == TBaseType.rrw_primary) {
1139            TSourceToken stNext = ast.searchToken("key", 1);
1140            if (stNext == null) {
1141                ast.tokencode = TBaseType.ident;
1142            }
1143        } else if (ast.tokencode == TBaseType.rrw_oracle_offset) {
1144            TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_row, 2);
1145            if (stNext == null) {
1146                stNext = ast.searchToken(TBaseType.rrw_oracle_rows, 2);
1147            }
1148            if (stNext != null) {
1149                ast.tokencode = TBaseType.rrw_oracle_offset_row;
1150            }
1151        } else if (ast.tokencode == TBaseType.rrw_translate) {
1152            TSourceToken stNext = ast.searchToken("(", 2);
1153            if (stNext == null) {
1154                ast.tokencode = TBaseType.ident;
1155            }
1156        } else if (ast.tokencode == TBaseType.rrw_constraint) {
1157            TSourceToken stNext = ast.nextSolidToken();
1158            if (stNext == null) {
1159                ast.tokencode = TBaseType.ident;
1160            } else {
1161                if (stNext.tokencode != TBaseType.ident) {
1162                    ast.tokencode = TBaseType.ident;
1163                }
1164            }
1165        } else if (ast.tokencode == TBaseType.rrw_oracle_without) {
1166            TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_count, 1);
1167            if (stNext != null) {
1168                ast.tokencode = TBaseType.rrw_oracle_without_before_count;
1169            }
1170        } else if (ast.tokencode == TBaseType.rrw_bulk) {
1171            TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_collect, 1);
1172            if (stNext == null) {
1173                ast.tokencode = TBaseType.ident;
1174            }
1175        } else if (ast.tokencode == TBaseType.rrw_oracle_model) {
1176            TSourceToken stNext = ast.nextSolidToken();
1177            if (stNext != null) {
1178                switch (stNext.toString().toUpperCase()) {
1179                    case "RETURN":
1180                    case "REFERENCE":
1181                    case "IGNORE":
1182                    case "KEEP":
1183                    case "UNIQUE":
1184                    case "PARTITION":
1185                    case "DIMENSION":
1186                    case "MEASURES":
1187                    case "RULES":
1188                        ast.tokencode = TBaseType.rrw_oracle_model_in_model_clause;
1189                        break;
1190                    default:
1191                        ;
1192                }
1193            }
1194        }
1195    }
1196
1197    private void appendToken(TCustomSqlStatement statement, TSourceToken token) {
1198        if (statement == null || token == null) {
1199            return;
1200        }
1201        token.stmt = statement;
1202        statement.sourcetokenlist.add(token);
1203    }
1204
1205    // ========== Error Handling and Recovery ==========
1206
1207    /**
1208     * Find all syntax errors in PL/SQL statements recursively.
1209     * Extracted from TGSqlParser.findAllSyntaxErrorsInPlsql().
1210     */
1211    private void findAllSyntaxErrorsInPlsql(TCustomSqlStatement psql) {
1212        if (psql.getErrorCount() > 0) {
1213            copyErrorsFromStatement(psql);
1214        }
1215
1216        for (int k = 0; k < psql.getStatements().size(); k++) {
1217            findAllSyntaxErrorsInPlsql(psql.getStatements().get(k));
1218        }
1219    }
1220
1221    /**
1222     * Handle error recovery for CREATE TABLE/INDEX statements.
1223     * Oracle allows table properties that may not be fully parsed.
1224     * This method marks unparseable properties as SQL*Plus commands to skip them.
1225     *
1226     * <p>Extracted from TGSqlParser.doparse() lines 16916-16971
1227     */
1228    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
1229        if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) ||
1230             (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) &&
1231            (!TBaseType.c_createTableStrictParsing)) {
1232
1233            // Find the closing parenthesis of table definition
1234            int nested = 0;
1235            boolean isIgnore = false, isFoundIgnoreToken = false;
1236            TSourceToken firstIgnoreToken = null;
1237
1238            for (int k = 0; k < stmt.sourcetokenlist.size(); k++) {
1239                TSourceToken st = stmt.sourcetokenlist.get(k);
1240
1241                if (isIgnore) {
1242                    if (st.issolidtoken() && (st.tokencode != ';')) {
1243                        isFoundIgnoreToken = true;
1244                        if (firstIgnoreToken == null) {
1245                            firstIgnoreToken = st;
1246                        }
1247                    }
1248                    if (st.tokencode != ';') {
1249                        st.tokencode = TBaseType.sqlpluscmd;
1250                    }
1251                    continue;
1252                }
1253
1254                if (st.tokencode == (int) ')') {
1255                    nested--;
1256                    if (nested == 0) {
1257                        // Check if next token is "AS ( SELECT"
1258                        boolean isSelect = false;
1259                        TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
1260                        if (st1 != null) {
1261                            TSourceToken st2 = st.searchToken((int) '(', 2);
1262                            if (st2 != null) {
1263                                TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
1264                                isSelect = (st3 != null);
1265                            }
1266                        }
1267                        if (!isSelect) isIgnore = true;
1268                    }
1269                }
1270
1271                if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
1272                    nested++;
1273                }
1274            }
1275
1276            // Verify it's a valid Oracle table property
1277            if ((firstIgnoreToken != null) &&
1278                (!TBaseType.searchOracleTablePros(firstIgnoreToken.toString()))) {
1279                // Not a valid property, keep the error
1280                isFoundIgnoreToken = false;
1281            }
1282
1283            // Retry parsing if we found ignoreable properties
1284            if (isFoundIgnoreToken) {
1285                stmt.clearError();
1286                stmt.parsestatement(null, false);
1287            }
1288        }
1289    }
1290
1291    /**
1292     * Copy syntax errors from a statement to our error list.
1293     * Extracted from TGSqlParser.copyerrormsg().
1294     */
1295
1296    @Override
1297    public String toString() {
1298        return "OracleSqlParser{vendor=" + vendor + "}";
1299    }
1300
1301    // ========== Main Oracle Tokenization ==========
1302    // Core tokenization logic extracted from TGSqlParser.dooraclesqltexttotokenlist()
1303
1304    /**
1305     * Perform Oracle-specific tokenization with SQL*Plus command detection.
1306     * <p>
1307     * This method implements Oracle's complex tokenization rules including:
1308     * <ul>
1309     *   <li>SQL*Plus command detection (SPOOL, SET, START, etc.)</li>
1310     *   <li>Forward slash disambiguation (division vs PL/SQL delimiter)</li>
1311     *   <li>Oracle-specific keyword transformations (INNER, TYPE, FULL, etc.)</li>
1312     *   <li>Context-dependent token code modifications</li>
1313     * </ul>
1314     *
1315     * <p><b>State Machine:</b> Uses 5 boolean flags to track tokenization state:
1316     * <ul>
1317     *   <li>{@code insqlpluscmd} - Currently inside SQL*Plus command</li>
1318     *   <li>{@code isvalidplace} - Valid place to start SQL*Plus command</li>
1319     *   <li>{@code waitingreturnforfloatdiv} - Slash seen, waiting for newline</li>
1320     *   <li>{@code waitingreturnforsemicolon} - Semicolon seen, waiting for newline</li>
1321     *   <li>{@code continuesqlplusatnewline} - SQL*Plus command continues to next line</li>
1322     * </ul>
1323     *
1324     * <p><b>Extracted from:</b> TGSqlParser.dooraclesqltexttotokenlist() (lines 3931-4298)
1325     *
1326     * @throws RuntimeException if tokenization fails
1327     */
1328    private void dooraclesqltexttotokenlist() {
1329        // Initialize state machine for SQL*Plus command detection
1330        insqlpluscmd = false;
1331        isvalidplace = true;
1332        waitingreturnforfloatdiv = false;
1333        waitingreturnforsemicolon = false;
1334        continuesqlplusatnewline = false;
1335
1336        ESqlPlusCmd currentCmdType = ESqlPlusCmd.spcUnknown;
1337
1338        TSourceToken lct = null, prevst = null;
1339
1340        TSourceToken asourcetoken, lcprevst;
1341        int yychar;
1342
1343        asourcetoken = getanewsourcetoken();
1344        if (asourcetoken == null) return;
1345        yychar = asourcetoken.tokencode;
1346
1347        while (yychar > 0) {
1348            sourcetokenlist.add(asourcetoken);
1349
1350            switch (yychar) {
1351                case TBaseType.cmtdoublehyphen:
1352                case TBaseType.cmtslashstar:
1353                case TBaseType.lexspace: {
1354                    if (insqlpluscmd) {
1355                        asourcetoken.insqlpluscmd = true;
1356                    }
1357                    break;
1358                }
1359
1360                case TBaseType.lexnewline: {
1361                    if (insqlpluscmd) {
1362                        insqlpluscmd = false;
1363                        isvalidplace = true;
1364
1365                        if (continuesqlplusatnewline) {
1366                            insqlpluscmd = true;
1367                            isvalidplace = false;
1368                            asourcetoken.insqlpluscmd = true;
1369                        }
1370
1371                        if (!insqlpluscmd) {
1372                            currentCmdType = ESqlPlusCmd.spcUnknown;
1373                        }
1374                    }
1375
1376                    if (waitingreturnforsemicolon) {
1377                        isvalidplace = true;
1378                    }
1379
1380                    if (waitingreturnforfloatdiv) {
1381                        isvalidplace = true;
1382                        lct.tokencode = TBaseType.sqlpluscmd;
1383                        if (lct.tokentype != ETokenType.ttslash) {
1384                            lct.tokentype = ETokenType.ttsqlpluscmd;
1385                        }
1386                    }
1387
1388                    if (countLines(asourcetoken.toString()) > 1) {
1389                        // There is a line after select, so spool is the right place to start a sqlplus command
1390                        isvalidplace = true;
1391                    }
1392
1393                    flexer.insqlpluscmd = insqlpluscmd;
1394                    break;
1395                }
1396
1397                default: {
1398                    // Solid token
1399                    continuesqlplusatnewline = false;
1400                    waitingreturnforsemicolon = false;
1401                    waitingreturnforfloatdiv = false;
1402
1403                    if (insqlpluscmd) {
1404                        asourcetoken.insqlpluscmd = true;
1405                        if (asourcetoken.toString().equalsIgnoreCase("-")) {
1406                            continuesqlplusatnewline = true;
1407                        }
1408                    } else {
1409                        if (asourcetoken.tokentype == ETokenType.ttsemicolon) {
1410                            waitingreturnforsemicolon = true;
1411                        }
1412
1413                        if ((asourcetoken.tokentype == ETokenType.ttslash)
1414                                && (isvalidplace || (isValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) {
1415                            lct = asourcetoken;
1416                            waitingreturnforfloatdiv = true;
1417                        }
1418
1419                        currentCmdType = TSqlplusCmdStatement.searchCmd(asourcetoken.toString(), asourcetoken.nextToken());
1420                        if (currentCmdType != ESqlPlusCmd.spcUnknown) {
1421                            if (isvalidplace) {
1422                                TSourceToken lnbreak = null;
1423                                boolean aRealSqlplusCmd = true;
1424                                if (sourcetokenlist.curpos > 0) {
1425                                    lnbreak = sourcetokenlist.get(sourcetokenlist.curpos - 1);
1426                                    aRealSqlplusCmd = !spaceAtTheEndOfReturnToken(lnbreak.toString());
1427                                }
1428
1429                                if (aRealSqlplusCmd) {
1430                                    asourcetoken.prevTokenCode = asourcetoken.tokencode;
1431                                    asourcetoken.tokencode = TBaseType.sqlpluscmd;
1432                                    if (asourcetoken.tokentype != ETokenType.ttslash) {
1433                                        asourcetoken.tokentype = ETokenType.ttsqlpluscmd;
1434                                    }
1435                                    insqlpluscmd = true;
1436                                    flexer.insqlpluscmd = insqlpluscmd;
1437                                }
1438                            } else if ((asourcetoken.tokencode == TBaseType.rrw_connect) && (sourcetokenlist.returnbeforecurtoken(true))) {
1439                                asourcetoken.tokencode = TBaseType.sqlpluscmd;
1440                                if (asourcetoken.tokentype != ETokenType.ttslash) {
1441                                    asourcetoken.tokentype = ETokenType.ttsqlpluscmd;
1442                                }
1443                                insqlpluscmd = true;
1444                                flexer.insqlpluscmd = insqlpluscmd;
1445                            } else if (sourcetokenlist.returnbeforecurtoken(true)) {
1446                                TSourceToken lnbreak = sourcetokenlist.get(sourcetokenlist.curpos - 1);
1447
1448                                if ((countLines(lnbreak.toString()) > 1) && (!spaceAtTheEndOfReturnToken(lnbreak.toString()))) {
1449                                    asourcetoken.tokencode = TBaseType.sqlpluscmd;
1450                                    if (asourcetoken.tokentype != ETokenType.ttslash) {
1451                                        asourcetoken.tokentype = ETokenType.ttsqlpluscmd;
1452                                    }
1453                                    insqlpluscmd = true;
1454                                    flexer.insqlpluscmd = insqlpluscmd;
1455                                }
1456                            }
1457                        }
1458                    }
1459
1460                    isvalidplace = false;
1461
1462                    // Oracle-specific keyword handling (inline to match legacy behavior)
1463                    if (prevst != null) {
1464                        if (prevst.tokencode == TBaseType.rrw_inner) {
1465                            if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) {
1466                                prevst.tokencode = TBaseType.ident;
1467                            }
1468                        } else if ((prevst.tokencode == TBaseType.rrw_not)
1469                                && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) {
1470                            prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE");
1471                        }
1472                    }
1473
1474                    if (asourcetoken.tokencode == TBaseType.rrw_inner) {
1475                        prevst = asourcetoken;
1476                    } else if (asourcetoken.tokencode == TBaseType.rrw_not) {
1477                        prevst = asourcetoken;
1478                    } else {
1479                        prevst = null;
1480                    }
1481
1482                    // Oracle keyword transformations that rely on prev token state
1483                    if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD"))
1484                            || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) {
1485                        lcprevst = getprevsolidtoken(asourcetoken);
1486                        if (lcprevst != null) {
1487                            if (lcprevst.tokencode == TBaseType.rrw_for)
1488                                lcprevst.tokencode = TBaseType.rw_for1;
1489                        }
1490                    } else if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) {
1491                        TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2);
1492                        if (stKeep != null) {
1493                            stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank;
1494                        }
1495                    } else if (asourcetoken.tokencode == TBaseType.rrw_full) {
1496                        TSourceToken stMatch = asourcetoken.searchToken(TBaseType.rrw_match, -1);
1497                        if (stMatch != null) {
1498                            asourcetoken.tokencode = TBaseType.RW_FULL2;
1499                        }
1500                    } else if (asourcetoken.tokencode == TBaseType.rrw_join) {
1501                        TSourceToken stFull = asourcetoken.searchToken(TBaseType.rrw_full, -1);
1502                        if (stFull != null) {
1503                            stFull.tokencode = TBaseType.RW_FULL2;
1504                        } else {
1505                            TSourceToken stNatural = asourcetoken.searchToken(TBaseType.rrw_natural, -4);
1506                            if (stNatural != null) {
1507                                stNatural.tokencode = TBaseType.RW_NATURAL2;
1508                            }
1509                        }
1510                    } else if (asourcetoken.tokencode == TBaseType.rrw_outer) {
1511                        TSourceToken stFull = asourcetoken.searchToken(TBaseType.rrw_full, -1);
1512                        if (stFull != null) {
1513                            stFull.tokencode = TBaseType.RW_FULL2;
1514                        }
1515                    } else if (asourcetoken.tokencode == TBaseType.rrw_is) {
1516                        TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2);
1517                        if (stType != null) {
1518                            stType.tokencode = TBaseType.rrw_type2;
1519                        }
1520                    } else if (asourcetoken.tokencode == TBaseType.rrw_as) {
1521                        TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2);
1522                        if (stType != null) {
1523                            stType.tokencode = TBaseType.rrw_type2;
1524                        }
1525                    } else if (asourcetoken.tokencode == TBaseType.rrw_oid) {
1526                        TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2);
1527                        if (stType != null) {
1528                            stType.tokencode = TBaseType.rrw_type2;
1529                        }
1530                    } else if (asourcetoken.tokencode == TBaseType.rrw_type) {
1531                        TSourceToken stPrev;
1532                        stPrev = asourcetoken.searchToken(TBaseType.rrw_drop, -1);
1533                        if (stPrev != null) {
1534                            asourcetoken.tokencode = TBaseType.rrw_type2;
1535                        }
1536                        if (asourcetoken.tokencode == TBaseType.rrw_type) {
1537                            stPrev = asourcetoken.searchToken(TBaseType.rrw_of, -1);
1538                            if (stPrev != null) {
1539                                asourcetoken.tokencode = TBaseType.rrw_type2;
1540                            }
1541                        }
1542                        if (asourcetoken.tokencode == TBaseType.rrw_type) {
1543                            stPrev = asourcetoken.searchToken(TBaseType.rrw_create, -1);
1544                            if (stPrev != null) {
1545                                asourcetoken.tokencode = TBaseType.rrw_type2;
1546                            }
1547                        }
1548                        if (asourcetoken.tokencode == TBaseType.rrw_type) {
1549                            stPrev = asourcetoken.searchToken(TBaseType.rrw_replace, -1);
1550                            if (stPrev != null) {
1551                                asourcetoken.tokencode = TBaseType.rrw_type2;
1552                            }
1553                        }
1554                        if (asourcetoken.tokencode == TBaseType.rrw_type) {
1555                            stPrev = asourcetoken.searchToken('%', -1);
1556                            if (stPrev != null) {
1557                                asourcetoken.tokencode = TBaseType.rrw_type2;
1558                            }
1559                        }
1560                    } else if ((asourcetoken.tokencode == TBaseType.rrw_by) || (asourcetoken.tokencode == TBaseType.rrw_to)) {
1561                        lcprevst = getprevsolidtoken(asourcetoken);
1562                        if (lcprevst != null) {
1563                            if ((lcprevst.tokencode == TBaseType.sqlpluscmd) && (lcprevst.toString().equalsIgnoreCase("connect"))) {
1564                                lcprevst.tokencode = TBaseType.rrw_connect;
1565                                lcprevst.tokentype = ETokenType.ttkeyword;
1566                                flexer.insqlpluscmd = false;
1567
1568                                continuesqlplusatnewline = false;
1569                                waitingreturnforsemicolon = false;
1570                                waitingreturnforfloatdiv = false;
1571                                isvalidplace = false;
1572                                insqlpluscmd = false;
1573                            }
1574                        }
1575                    } else if (asourcetoken.tokencode == TBaseType.rrw_with) {
1576                        lcprevst = getprevsolidtoken(asourcetoken);
1577                        if (lcprevst != null) {
1578                            if ((lcprevst.tokencode == TBaseType.sqlpluscmd) && (lcprevst.toString().equalsIgnoreCase("start"))) {
1579                                lcprevst.tokencode = TBaseType.rrw_start;
1580                                lcprevst.tokentype = ETokenType.ttkeyword;
1581                                flexer.insqlpluscmd = false;
1582
1583                                continuesqlplusatnewline = false;
1584                                waitingreturnforsemicolon = false;
1585                                waitingreturnforfloatdiv = false;
1586                                isvalidplace = false;
1587                                insqlpluscmd = false;
1588                            }
1589                        }
1590                    } else if (asourcetoken.tokencode == TBaseType.rrw_set) {
1591                        lcprevst = getprevsolidtoken(asourcetoken);
1592                        if (lcprevst != null) {
1593                            if (lcprevst.getAstext().equalsIgnoreCase("a")) {
1594                                TSourceToken lcpp = getprevsolidtoken(lcprevst);
1595                                if (lcpp != null) {
1596                                    if ((lcpp.tokencode == TBaseType.rrw_not) || (lcpp.tokencode == TBaseType.rrw_is)) {
1597                                        lcprevst.tokencode = TBaseType.rrw_oracle_a_in_aset;
1598                                        asourcetoken.tokencode = TBaseType.rrw_oracle_set_in_aset;
1599                                    }
1600                                }
1601                            }
1602                        }
1603                    }
1604
1605                    break;
1606                }
1607            }
1608
1609            // Get next token
1610            asourcetoken = getanewsourcetoken();
1611            if (asourcetoken != null) {
1612                yychar = asourcetoken.tokencode;
1613
1614                // Handle special case: dot after SQL*Plus commands
1615                if ((asourcetoken.tokencode == '.') && (getprevsolidtoken(asourcetoken) != null)
1616                        && ((currentCmdType == ESqlPlusCmd.spcAppend)
1617                        || (currentCmdType == ESqlPlusCmd.spcChange) || (currentCmdType == ESqlPlusCmd.spcInput)
1618                        || (currentCmdType == ESqlPlusCmd.spcList) || (currentCmdType == ESqlPlusCmd.spcRun))) {
1619                    // a.ent_rp_usr_id is not a real sqlplus command
1620                    TSourceToken lcprevst2 = getprevsolidtoken(asourcetoken);
1621                    lcprevst2.insqlpluscmd = false;
1622                    if (lcprevst2.prevTokenCode != 0) {
1623                        lcprevst2.tokencode = lcprevst2.prevTokenCode;
1624                    } else {
1625                        lcprevst2.tokencode = TBaseType.ident;
1626                    }
1627
1628                    flexer.insqlpluscmd = false;
1629                    continuesqlplusatnewline = false;
1630                    waitingreturnforsemicolon = false;
1631                    waitingreturnforfloatdiv = false;
1632                    isvalidplace = false;
1633                    insqlpluscmd = false;
1634                }
1635            } else {
1636                yychar = 0;
1637
1638                if (waitingreturnforfloatdiv) {
1639                    // / at the end of line treat as sqlplus command
1640                    lct.tokencode = TBaseType.sqlpluscmd;
1641                    if (lct.tokentype != ETokenType.ttslash) {
1642                        lct.tokentype = ETokenType.ttsqlpluscmd;
1643                    }
1644                }
1645            }
1646
1647            if ((yychar == 0) && (prevst != null)) {
1648                if (prevst.tokencode == TBaseType.rrw_inner) {
1649                    prevst.tokencode = TBaseType.ident;
1650                }
1651            }
1652        }
1653    }
1654
1655    // ========== Helper Methods for Tokenization ==========
1656    // These methods support Oracle-specific tokenization logic
1657
1658    /**
1659     * Count number of newlines in a string.
1660     *
1661     * @param s string to analyze
1662     * @return number of line breaks (LF or CR)
1663     */
1664    private int countLines(String s) {
1665        int pos = 0, lf = 0, cr = 0;
1666
1667        while (pos < s.length()) {
1668            if (s.charAt(pos) == '\r') {
1669                cr++;
1670                pos++;
1671                continue;
1672            }
1673            if (s.charAt(pos) == '\n') {
1674                lf++;
1675                pos++;
1676                continue;
1677            }
1678
1679            if (s.charAt(pos) == ' ') {
1680                pos++;
1681                continue;
1682            }
1683            break;
1684        }
1685
1686        if (lf >= cr) return lf;
1687        else return cr;
1688    }
1689
1690    /**
1691     * Check if return token ends with space or tab.
1692     *
1693     * @param s token text
1694     * @return true if ends with space/tab
1695     */
1696    private boolean spaceAtTheEndOfReturnToken(String s) {
1697        if (s == null) return false;
1698        if (s.length() == 0) return false;
1699
1700        return ((s.charAt(s.length() - 1) == ' ') || (s.charAt(s.length() - 1) == '\t'));
1701    }
1702
1703    /**
1704     * Determine if forward slash should be treated as SQL*Plus command delimiter.
1705     * <p>
1706     * Oracle uses '/' as both division operator and SQL*Plus block delimiter.
1707     * This method disambiguates by checking if the '/' appears at the beginning
1708     * of a line (after a return token without trailing whitespace).
1709     *
1710     * @param pstlist token list
1711     * @param pPos position of '/' token
1712     * @return true if '/' should be SQL*Plus command
1713     */
1714    private boolean isValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) {
1715        boolean ret = false;
1716
1717        if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret;
1718
1719        // Token directly before div must be ttreturn without space appending it
1720        gudusoft.gsqlparser.TSourceToken lcst = pstlist.get(pPos - 1);
1721        if (lcst.tokentype != gudusoft.gsqlparser.ETokenType.ttreturn) {
1722            return ret;
1723        }
1724
1725        if (!(lcst.getAstext().charAt(lcst.getAstext().length() - 1) == ' ')) {
1726            ret = true;
1727        }
1728
1729        return ret;
1730    }
1731
1732    /**
1733     * Get previous non-whitespace token.
1734     *
1735     * @param ptoken current token
1736     * @return previous solid token, or null
1737     */
1738    private gudusoft.gsqlparser.TSourceToken getprevsolidtoken(gudusoft.gsqlparser.TSourceToken ptoken) {
1739        gudusoft.gsqlparser.TSourceToken ret = null;
1740        TSourceTokenList lctokenlist = ptoken.container;
1741
1742        if (lctokenlist != null) {
1743            if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) {
1744                if (!(
1745                        (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttwhitespace)
1746                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttreturn)
1747                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttsimplecomment)
1748                        || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttbracketedcomment)
1749                )) {
1750                    ret = lctokenlist.get(ptoken.posinlist - 1);
1751                } else {
1752                    ret = lctokenlist.nextsolidtoken(ptoken.posinlist - 1, -1, false);
1753                }
1754            }
1755        }
1756        return ret;
1757    }
1758}