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 // Brace nesting inside CREATE JAVA SOURCE ... AS <java code>: the Java 174 // body contains its own semicolons, which must not end the SQL statement. 175 private int javaBraceNesting = 0; 176 177 // ========== Statement Parsing State (used during statement parsing) ========== 178 // These instance variables are used during the statement parsing process 179 180 // NOTE: The following fields moved to AbstractSqlParser (inherited): 181 // - sqlcmds (ISqlCmds) 182 // - sqlstatements (TStatementList) 183 // - parserContext (ParserContext) 184 185 /** Current statement being built */ 186 private TCustomSqlStatement gcurrentsqlstatement; 187 188 /** SQL parser (for regular SQL statements) */ 189 private TParserOracleSql fparser; 190 191 /** PL/SQL parser (for PL/SQL blocks) */ 192 private TParserOraclePLSql fplsqlparser; 193 194 // Note: Global context and frame stack fields inherited from AbstractSqlParser: 195 // - protected TContext globalContext 196 // - protected TSQLEnv sqlEnv 197 // - protected Stack<TFrame> frameStack 198 // - protected TFrame globalFrame 199 200 // ========== Enums for State Machine ========== 201 // These enums are used by the dooraclegetrawsqlstatements state machine 202 203 enum stored_procedure_status {start,is_as,body,bodyend,end, cursor_declare}; 204 enum stored_procedure_type {function,procedure,package_spec,package_body, block_with_begin,block_with_declare, 205 create_trigger,create_library,cursor_in_package_spec,others}; 206 207 static final int stored_procedure_nested_level = 1024; 208 209 // ========== AbstractSqlParser Abstract Methods Implementation ========== 210 211 /** 212 * Return the Oracle lexer instance. 213 * <p> 214 * The lexer is created once in the constructor and reused for all 215 * parsing operations. This method simply returns the existing instance, 216 * matching the original TGSqlParser pattern where the lexer is created 217 * once and reset before each use. 218 * 219 * @param context parser context (not used, lexer already created) 220 * @return the Oracle lexer instance created in constructor 221 */ 222 @Override 223 protected TCustomLexer getLexer(ParserContext context) { 224 // Return existing lexer instance (created in constructor) 225 // No need to create new instance - matches original TGSqlParser pattern 226 return this.flexer; 227 } 228 229 /** 230 * Return the Oracle SQL parser instance with updated token list. 231 * <p> 232 * The parser is created once in the constructor and reused for all 233 * parsing operations. This method updates the token list and returns 234 * the existing instance, matching the original TGSqlParser pattern. 235 * 236 * @param context parser context (not used, parser already created) 237 * @param tokens source token list to parse 238 * @return the Oracle SQL parser instance created in constructor 239 */ 240 @Override 241 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 242 // Update token list for reused parser instance 243 this.fparser.sourcetokenlist = tokens; 244 return this.fparser; 245 } 246 247 /** 248 * Return the Oracle PL/SQL parser instance with updated token list. 249 * <p> 250 * Oracle needs a secondary parser (TParserOraclePLSql) for PL/SQL blocks 251 * (procedures, functions, packages, triggers, anonymous blocks). 252 * <p> 253 * The parser is created once in the constructor and reused for all 254 * parsing operations. This method updates the token list and returns 255 * the existing instance, matching the original TGSqlParser pattern. 256 * 257 * @param context parser context (not used, parser already created) 258 * @param tokens source token list to parse 259 * @return the Oracle PL/SQL parser instance created in constructor 260 */ 261 @Override 262 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 263 // Update token list for reused parser instance 264 this.fplsqlparser.sourcetokenlist = tokens; 265 return this.fplsqlparser; 266 } 267 268 /** 269 * Call Oracle-specific tokenization logic. 270 * <p> 271 * Delegates to dooraclesqltexttotokenlist which handles Oracle's 272 * specific keyword recognition, SQL*Plus commands, forward slash 273 * disambiguation, and token generation. 274 */ 275 @Override 276 protected void tokenizeVendorSql() { 277 dooraclesqltexttotokenlist(); 278 restoreSqlSetStatements(); 279 } 280 281 /** 282 * SET is both a SQL*Plus command (SET PAGESIZE ..., line-terminated) and 283 * the head of the SQL statements SET CONSTRAINT[S] / SET ROLE / 284 * SET TRANSACTION (semicolon-terminated). Tokenization cannot look ahead 285 * (the token list is built incrementally), so every line-leading SET is 286 * first marked sqlpluscmd; a multi-line SET CONSTRAINTS was then truncated 287 * at the newline. This pass runs on the complete token list and restores 288 * the SQL reading when the word after SET is one of the SQL forms: the 289 * SET token gets its saved keyword code back and the rest of its line 290 * loses the insqlpluscmd flag, so the statement splitter routes it through 291 * the command table (sstoraclesetconstraint etc.) and runs to ';'. 292 */ 293 private void restoreSqlSetStatements() { 294 for (int i = 0; i < sourcetokenlist.size(); i++) { 295 TSourceToken st = sourcetokenlist.get(i); 296 if (st.tokencode != TBaseType.sqlpluscmd) continue; 297 if (!st.toString().equalsIgnoreCase("SET")) continue; 298 if (st.prevTokenCode != TBaseType.rrw_set) continue; 299 TSourceToken nx = st.nextSolidToken(); 300 if (nx == null) continue; 301 String w = nx.toString(); 302 if (!(w.equalsIgnoreCase("CONSTRAINT") || w.equalsIgnoreCase("CONSTRAINTS") 303 || w.equalsIgnoreCase("ROLE") || w.equalsIgnoreCase("TRANSACTION"))) continue; 304 st.tokencode = st.prevTokenCode; 305 st.tokentype = ETokenType.ttkeyword; 306 st.insqlpluscmd = false; 307 for (int j = i + 1; j < sourcetokenlist.size(); j++) { 308 TSourceToken t = sourcetokenlist.get(j); 309 if (t.tokencode == TBaseType.lexnewline) break; 310 t.insqlpluscmd = false; 311 } 312 } 313 } 314 315 /** 316 * Post-tokenization: merge ${...} template variable tokens into single IDENT tokens. 317 * Template syntax like ${if(len(X) == 0, "", "...")} is used by BI tools. 318 */ 319 @Override 320 protected void doAfterTokenize(TSourceTokenList tokens) { 321 super.doAfterTokenize(tokens); 322 mergeTemplateVariableTokens(tokens); 323 } 324 325 private void mergeTemplateVariableTokens(TSourceTokenList tokens) { 326 for (int i = 0; i < tokens.size() - 1; i++) { 327 TSourceToken dollar = tokens.get(i); 328 329 // Match either bare '$' (self-char) or '$IDENT' like $P, $X (identifier starting with $) 330 boolean isDollarChar = (dollar.tokencode == '$'); 331 boolean isDollarIdent = (dollar.tokencode == TBaseType.ident 332 && dollar.astext != null && dollar.astext.startsWith("$")); 333 if (!isDollarChar && !isDollarIdent) continue; 334 335 // Find next non-whitespace token — for $IDENT pattern, require immediate '{' (no whitespace) 336 int braceIdx = i + 1; 337 if (isDollarChar) { 338 while (braceIdx < tokens.size() && tokens.get(braceIdx).tokentype == ETokenType.ttwhitespace) { 339 braceIdx++; 340 } 341 } 342 if (braceIdx >= tokens.size() || tokens.get(braceIdx).tokencode != '{') continue; 343 344 // Found ${ pattern — find matching } with depth tracking 345 int depth = 1; 346 int endIdx = braceIdx + 1; 347 boolean isComplex = false; 348 while (endIdx < tokens.size() && depth > 0) { 349 int code = tokens.get(endIdx).tokencode; 350 if (code == '{') depth++; 351 else if (code == '}') depth--; 352 else if (code == '(' || code == ',' || code == '\'' || code == '"') isComplex = true; 353 if (depth > 0) endIdx++; 354 } 355 if (depth != 0) continue; // unclosed 356 357 // Build merged token text 358 StringBuilder sb = new StringBuilder(); 359 for (int j = i; j <= endIdx; j++) { 360 sb.append(tokens.get(j).astext); 361 } 362 363 if (isComplex && isDollarChar) { 364 // Complex template starting with bare $ like ${if(len(X)==0,...)} 365 // These expand to SQL fragments (e.g., AND clauses) at runtime, 366 // so convert to whitespace to let parser skip them entirely. 367 for (int j = i; j <= endIdx; j++) { 368 tokens.get(j).tokentype = ETokenType.ttwhitespace; 369 tokens.get(j).tokencode = TBaseType.lexspace; 370 } 371 } else { 372 // Simple template like ${NAME}, or JasperReports $P{VAR}/$X{IN,COL,PARAM} 373 // These expand to single values/expressions, so merge into IDENT placeholder. 374 dollar.astext = sb.toString(); 375 dollar.tokencode = TBaseType.ident; 376 dollar.tokentype = ETokenType.ttidentifier; 377 // Convert remaining tokens to whitespace so parser skips them even if 378 // tokenstatus is overwritten by statement splitter (tsignoredbygetrawstatement) 379 for (int j = i + 1; j <= endIdx; j++) { 380 tokens.get(j).tokentype = ETokenType.ttwhitespace; 381 tokens.get(j).tokencode = TBaseType.lexspace; 382 } 383 } 384 385 i = endIdx; // skip past merged tokens 386 } 387 } 388 389 /** 390 * Setup Oracle parsers for raw statement extraction. 391 * <p> 392 * Oracle uses dual parsers (SQL + PL/SQL), so we inject sqlcmds and 393 * update token lists for both parsers. 394 */ 395 @Override 396 protected void setupVendorParsersForExtraction() { 397 // Inject sqlcmds into BOTH parsers (SQL + PL/SQL) 398 this.fparser.sqlcmds = this.sqlcmds; 399 this.fplsqlparser.sqlcmds = this.sqlcmds; 400 401 // Update token list for BOTH parsers 402 this.fparser.sourcetokenlist = this.sourcetokenlist; 403 this.fplsqlparser.sourcetokenlist = this.sourcetokenlist; 404 } 405 406 /** 407 * Call Oracle-specific raw statement extraction logic. 408 * <p> 409 * Delegates to dooraclegetrawsqlstatements which handles Oracle's 410 * statement delimiters (semicolon and forward slash). 411 */ 412 @Override 413 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 414 dooraclegetrawsqlstatements(builder); 415 } 416 417 /** 418 * Perform full parsing of statements with syntax checking. 419 * <p> 420 * This method orchestrates the parsing of all statements by: 421 * <ul> 422 * <li>Using the raw statements passed from AbstractSqlParser.parse()</li> 423 * <li>Initializing SQL and PL/SQL parsers</li> 424 * <li>Creating global context and frame stack</li> 425 * <li>Looping through each raw statement</li> 426 * <li>Calling parsestatement() on each to build AST</li> 427 * <li>Handling error recovery for CREATE TABLE/INDEX</li> 428 * <li>Collecting syntax errors</li> 429 * </ul> 430 * 431 * <p><b>Important:</b> This method does NOT extract raw statements - they are 432 * passed in as a parameter already extracted by {@link #extractRawStatements}. 433 * This eliminates duplicate extraction that was occurring in the old design. 434 * 435 * <p>Extracted from: TGSqlParser.doparse() lines 16903-17026 436 * 437 * @param context parser context 438 * @param parser main SQL parser (TParserOracleSql) 439 * @param secondaryParser PL/SQL parser (TParserOraclePLSql) 440 * @param tokens source token list 441 * @param rawStatements raw statements already extracted (never null) 442 * @return list of fully parsed statements with AST built 443 */ 444 @Override 445 protected TStatementList performParsing(ParserContext context, 446 TCustomParser parser, 447 TCustomParser secondaryParser, 448 TSourceTokenList tokens, 449 TStatementList rawStatements) { 450 // Store references 451 this.fparser = (TParserOracleSql) parser; 452 this.fplsqlparser = (TParserOraclePLSql) secondaryParser; 453 this.sourcetokenlist = tokens; 454 this.parserContext = context; 455 456 // Use the raw statements passed from AbstractSqlParser.parse() 457 // (already extracted - DO NOT re-extract to avoid duplication) 458 this.sqlstatements = rawStatements; 459 460 // Initialize statement parsing infrastructure 461 this.sqlcmds = SqlCmdsFactory.get(vendor); 462 463 // Inject sqlcmds into parsers (required for make_stmt and other methods) 464 this.fparser.sqlcmds = this.sqlcmds; 465 this.fplsqlparser.sqlcmds = this.sqlcmds; 466 467 // Initialize global context for semantic analysis 468 // CRITICAL: When delegated from TGSqlParser, use TGSqlParser's frameStack 469 // so that variables set in statements can be found by other statements 470 if (context != null && context.getGsqlparser() != null) { 471 TGSqlParser gsqlparser = (TGSqlParser) context.getGsqlparser(); 472 this.frameStack = gsqlparser.getFrameStack(); 473 474 // CRITICAL: Set gsqlparser on the NodeFactory - matches TGSqlParser behavior 475 // This is needed for proper AST node creation during parsing 476 // Without this, expression traversal order may differ, causing 477 // dataflow constant ordering issues 478 this.fparser.getNf().setGsqlParser(gsqlparser); 479 this.fplsqlparser.getNf().setGsqlParser(gsqlparser); 480 481 // Create global context if needed 482 this.globalContext = new TContext(); 483 this.sqlEnv = new TSQLEnv(this.vendor) { 484 @Override 485 public void initSQLEnv() { 486 } 487 }; 488 this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements); 489 } else { 490 initializeGlobalContext(); 491 } 492 493 // Parse each statement with exception handling for robustness 494 for (int i = 0; i < sqlstatements.size(); i++) { 495 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 496 497 try { 498 stmt.setFrameStack(frameStack); 499 500 // Parse the statement 501 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 502 503 // Oracle-specific post-processing (overridden hook method) 504 afterStatementParsed(stmt); 505 506 // Handle error recovery for CREATE TABLE/INDEX 507 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 508 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 509 handleCreateTableErrorRecovery(stmt); 510 } 511 512 // Collect syntax errors 513 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 514 copyErrorsFromStatement(stmt); 515 } 516 517 } catch (Exception ex) { 518 // Use inherited exception handler from AbstractSqlParser 519 // This provides consistent error handling across all database parsers 520 handleStatementParsingException(stmt, i, ex); 521 continue; 522 } 523 } 524 525 // Clean up frame stack 526 if (globalFrame != null) { 527 globalFrame.popMeFromStack(frameStack); 528 } 529 530 return this.sqlstatements; 531 } 532 533 // Note: initializeGlobalContext() inherited from AbstractSqlParser 534 535 /** 536 * Override to provide Oracle-specific post-processing after statement parsing. 537 * <p> 538 * For Oracle, we check if the statement is PL/SQL and recursively find syntax 539 * errors in nested PL/SQL statements. 540 */ 541 @Override 542 protected void afterStatementParsed(TCustomSqlStatement stmt) { 543 if (stmt.isoracleplsql()) { 544 findAllSyntaxErrorsInPlsql(stmt); 545 } 546 } 547 548 /** 549 * Perform Oracle-specific semantic analysis using TSQLResolver. 550 * 551 * <p>This includes: 552 * <ul> 553 * <li>Column-to-table resolution</li> 554 * <li>Dataflow analysis</li> 555 * <li>Reference resolution</li> 556 * <li>Scope resolution</li> 557 * </ul> 558 * 559 * @param context the parser context 560 * @param statements the parsed statements 561 */ 562 @Override 563 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 564 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 565 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 566 resolver.resolve(); 567 } 568 } 569 570 /** 571 * Perform Oracle-specific AST interpretation/evaluation using TASTEvaluator. 572 * 573 * <p>This executes simple SQL statements and evaluates expressions 574 * for static analysis and constant folding. 575 * 576 * @param context the parser context 577 * @param statements the parsed statements 578 */ 579 @Override 580 protected void performInterpreter(ParserContext context, TStatementList statements) { 581 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 582 TLog.clearLogs(); 583 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 584 TLog.enableInterpreterLogOnly(); 585 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 586 astEvaluator.eval(); 587 } 588 } 589 590 // ========== Raw Statement Extraction ========== 591 // These methods extract raw SQL statements from tokens without full parsing 592 // Extracted from TGSqlParser.dooraclegetrawsqlstatements() and related methods 593 594 /** 595 * Extract raw Oracle SQL statements from tokenized source. 596 * <p> 597 * This is the main Oracle statement extraction state machine that: 598 * <ul> 599 * <li>Groups tokens into statement boundaries</li> 600 * <li>Identifies statement types (SQL vs PL/SQL, SQL*Plus commands)</li> 601 * <li>Handles nested PL/SQL blocks (procedures, functions, packages, triggers)</li> 602 * <li>Tracks BEGIN/END pairs and other block delimiters</li> 603 * <li>Detects statement terminators (semicolon, forward slash, period)</li> 604 * </ul> 605 * 606 * <p><b>State Machine:</b> Uses 4 main states: 607 * <ul> 608 * <li>{@code stnormal} - Between statements, looking for start of next statement</li> 609 * <li>{@code stsql} - Inside a SQL statement</li> 610 * <li>{@code stsqlplus} - Inside a SQL*Plus command</li> 611 * <li>{@code ststoredprocedure} - Inside a PL/SQL block (procedure/function/package/trigger)</li> 612 * <li>{@code sterror} - Error recovery mode</li> 613 * </ul> 614 * 615 * <p><b>Extracted from:</b> TGSqlParser.dooraclegetrawsqlstatements() (lines 10071-10859) 616 * 617 * <p><b>Design Note:</b> This method now receives a builder to populate with results, 618 * following Option A design where the vendor-specific method focuses on parsing logic 619 * while extractRawStatements() handles result construction. 620 * 621 * @param builder the result builder to populate with statements and error information 622 */ 623 private void dooraclegetrawsqlstatements(SqlParseResult.Builder builder) { 624 int waitingEnds[] = new int[stored_procedure_nested_level]; 625 stored_procedure_type sptype[] = new stored_procedure_type[stored_procedure_nested_level]; 626 stored_procedure_status procedure_status[] = new stored_procedure_status[stored_procedure_nested_level]; 627 boolean endBySlashOnly = true; 628 int nestedProcedures = 0, nestedParenthesis = 0; 629 // Flag for CREATE MLE MODULE with AS clause - terminates with / not ; 630 boolean mleModuleWithAs = false; 631 // Flag for WITH FUNCTION/PROCEDURE - track BEGIN/END nesting to handle embedded semicolons 632 boolean withPlsqlDefinition = false; 633 int withPlsqlBeginEndNesting = 0; 634 boolean withPlsqlFoundSelect = false; // True when SELECT has been found after WITH FUNCTION 635 // Track whether the current CTE statement's main SELECT has been found 636 // (i.e., the SELECT after WITH name AS (...) at paren level 0) 637 boolean cteMainSelectFound = false; 638 639 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 640 if (!TBaseType.assigned(sourcetokenlist)) { 641 // No tokens available - populate builder with error and return 642 builder.errorCode(1); 643 builder.errorMessage("No source token list available"); 644 builder.sqlStatements(new TStatementList()); 645 return; 646 } 647 648 gcurrentsqlstatement = null; 649 EFindSqlStateType gst = EFindSqlStateType.stnormal; 650 TSourceToken lcprevsolidtoken = null, ast = null; 651 652 // Main tokenization loop 653 for (int i = 0; i < sourcetokenlist.size(); i++) { 654 655 if ((ast != null) && (ast.issolidtoken())) 656 lcprevsolidtoken = ast; 657 658 ast = sourcetokenlist.get(i); 659 sourcetokenlist.curpos = i; 660 661 // Token-specific keyword transformations for Oracle 662 performRawStatementTokenTransformations(ast); 663 664 // State machine processing 665 switch (gst) { 666 case sterror: { 667 if (ast.tokentype == ETokenType.ttsemicolon) { 668 appendToken(gcurrentsqlstatement, ast); 669 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 670 gst = EFindSqlStateType.stnormal; 671 } else { 672 appendToken(gcurrentsqlstatement, ast); 673 } 674 break; 675 } //sterror 676 677 case stnormal: { 678 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 679 || (ast.tokencode == TBaseType.cmtslashstar) 680 || (ast.tokencode == TBaseType.lexspace) 681 || (ast.tokencode == TBaseType.lexnewline) 682 || (ast.tokentype == ETokenType.ttsemicolon)) { 683 if (gcurrentsqlstatement != null) { 684 appendToken(gcurrentsqlstatement, ast); 685 } 686 687 if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) { 688 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 689 // ;;;; continuous semicolon, treat it as comment 690 ast.tokentype = ETokenType.ttsimplecomment; 691 ast.tokencode = TBaseType.cmtdoublehyphen; 692 } 693 } 694 695 continue; 696 } 697 698 if (ast.tokencode == TBaseType.sqlpluscmd) { 699 gst = EFindSqlStateType.stsqlplus; 700 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 701 appendToken(gcurrentsqlstatement, ast); 702 continue; 703 } 704 705 // find a token to start sql or plsql mode 706 gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 707 708 if (gcurrentsqlstatement != null) { 709 if (gcurrentsqlstatement.isoracleplsql()) { 710 nestedProcedures = 0; 711 gst = EFindSqlStateType.ststoredprocedure; 712 appendToken(gcurrentsqlstatement, ast); 713 714 switch (gcurrentsqlstatement.sqlstatementtype) { 715 case sstplsql_createprocedure: 716 sptype[nestedProcedures] = stored_procedure_type.procedure; 717 break; 718 case sstplsql_createfunction: 719 sptype[nestedProcedures] = stored_procedure_type.function; 720 break; 721 case sstplsql_createpackage: 722 sptype[nestedProcedures] = stored_procedure_type.package_spec; 723 if (ast.searchToken(TBaseType.rrw_body, 5) != null) { 724 sptype[nestedProcedures] = stored_procedure_type.package_body; 725 } 726 break; 727 case sst_plsql_block: 728 sptype[nestedProcedures] = stored_procedure_type.block_with_declare; 729 if (ast.tokencode == TBaseType.rrw_begin) { 730 sptype[nestedProcedures] = stored_procedure_type.block_with_begin; 731 } 732 break; 733 case sstplsql_createtrigger: 734 sptype[nestedProcedures] = stored_procedure_type.create_trigger; 735 break; 736 case sstoraclecreatelibrary: 737 sptype[nestedProcedures] = stored_procedure_type.create_library; 738 break; 739 case sstplsql_createtype_placeholder: 740 gst = EFindSqlStateType.stsql; 741 break; 742 default: 743 sptype[nestedProcedures] = stored_procedure_type.others; 744 break; 745 } 746 747 if (sptype[0] == stored_procedure_type.block_with_declare) { 748 endBySlashOnly = false; 749 procedure_status[0] = stored_procedure_status.is_as; 750 } else if (sptype[0] == stored_procedure_type.block_with_begin) { 751 endBySlashOnly = false; 752 procedure_status[0] = stored_procedure_status.body; 753 } else if (sptype[0] == stored_procedure_type.procedure) { 754 endBySlashOnly = false; 755 procedure_status[0] = stored_procedure_status.start; 756 } else if (sptype[0] == stored_procedure_type.function) { 757 endBySlashOnly = false; 758 procedure_status[0] = stored_procedure_status.start; 759 } else if (sptype[0] == stored_procedure_type.package_spec) { 760 endBySlashOnly = false; 761 procedure_status[0] = stored_procedure_status.start; 762 } else if (sptype[0] == stored_procedure_type.package_body) { 763 endBySlashOnly = false; 764 procedure_status[0] = stored_procedure_status.start; 765 } else if (sptype[0] == stored_procedure_type.create_trigger) { 766 endBySlashOnly = false; 767 procedure_status[0] = stored_procedure_status.start; 768 } else if (sptype[0] == stored_procedure_type.create_library) { 769 endBySlashOnly = false; 770 procedure_status[0] = stored_procedure_status.bodyend; 771 } else { 772 endBySlashOnly = true; 773 procedure_status[0] = stored_procedure_status.bodyend; 774 } 775 776 if ((ast.tokencode == TBaseType.rrw_begin) 777 || (ast.tokencode == TBaseType.rrw_package) 778 || (ast.searchToken(TBaseType.rrw_package, 4) != null)) { 779 waitingEnds[nestedProcedures] = 1; 780 } 781 } else { 782 gst = EFindSqlStateType.stsql; 783 appendToken(gcurrentsqlstatement, ast); 784 nestedParenthesis = 0; 785 // Check if this is CREATE MLE MODULE with AS clause (JavaScript code) 786 // If AS is found after LANGUAGE JAVASCRIPT, it terminates with / not ; 787 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstoraclecreatemlemodule) { 788 // Look ahead to see if there's an AS keyword 789 TSourceToken asToken = ast.searchToken(TBaseType.rrw_as, 10); 790 mleModuleWithAs = (asToken != null); 791 } else { 792 mleModuleWithAs = false; 793 } 794 795 // Check if this is WITH FUNCTION/PROCEDURE (Oracle 12c inline PL/SQL) 796 // Need to track BEGIN/END nesting to handle embedded semicolons 797 if (ast.tokencode == TBaseType.rrw_with && gcurrentsqlstatement.isctequery) { 798 // Look ahead for FUNCTION or PROCEDURE keyword 799 TSourceToken nextSolid = ast.nextSolidToken(); 800 if (nextSolid != null && (nextSolid.tokencode == TBaseType.rrw_function 801 || nextSolid.tokencode == TBaseType.rrw_procedure)) { 802 withPlsqlDefinition = true; 803 withPlsqlBeginEndNesting = 0; 804 } 805 } 806 } 807 } else { 808 //error token found 809 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 810 , "Error when tokenize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 811 812 ast.tokentype = ETokenType.tttokenlizererrortoken; 813 gst = EFindSqlStateType.sterror; 814 815 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 816 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 817 appendToken(gcurrentsqlstatement, ast); 818 } 819 820 break; 821 } // stnormal 822 823 case stsqlplus: { 824 if (ast.insqlpluscmd) { 825 appendToken(gcurrentsqlstatement, ast); 826 } else { 827 gst = EFindSqlStateType.stnormal; //this token must be newline, 828 appendToken(gcurrentsqlstatement, ast); // so add it here 829 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 830 } 831 832 break; 833 }//case stsqlplus 834 835 case stsql: { 836 // WITH FUNCTION/PROCEDURE can also open the query of a statement that does not 837 // start with WITH: CREATE VIEW ... AS WITH FUNCTION ..., CREATE TABLE ... AS, 838 // INSERT ... WITH FUNCTION ... SELECT. The semicolons of the inline PL/SQL belong 839 // to that statement as well (Mantis 4949). 840 if ((ast.tokencode == TBaseType.rrw_with) && isInlinePlsqlWith(ast)) { 841 withPlsqlDefinition = true; 842 withPlsqlBeginEndNesting = 0; 843 withPlsqlFoundSelect = false; 844 } 845 846 // For WITH FUNCTION/PROCEDURE, track BEGIN/END nesting and when SELECT is found 847 if (withPlsqlDefinition) { 848 if (ast.tokencode == TBaseType.rrw_begin) { 849 withPlsqlBeginEndNesting++; 850 } else if (ast.tokencode == TBaseType.rrw_end) { 851 withPlsqlBeginEndNesting--; 852 if (withPlsqlBeginEndNesting < 0) withPlsqlBeginEndNesting = 0; 853 } else if (ast.tokencode == TBaseType.rrw_select && withPlsqlBeginEndNesting == 0) { 854 // Found SELECT after all function definitions are done 855 withPlsqlFoundSelect = true; 856 } 857 } 858 859 // For CREATE MLE MODULE with AS clause, don't terminate on semicolon 860 // The JavaScript code may contain semicolons; wait for / to terminate 861 // For WITH FUNCTION/PROCEDURE, don't terminate on semicolon until SELECT is found 862 // (the semicolons in function body and after END are part of the function definition) 863 // CREATE JAVA SOURCE ... AS <java code>: semicolons inside 864 // the Java class body (brace depth > 0) are Java statement 865 // terminators, not SQL statement terminators. 866 if ((gcurrentsqlstatement != null) 867 && (gcurrentsqlstatement.sqlstatementtype == gudusoft.gsqlparser.ESqlStatementType.sstoraclecreatejava)) { 868 if ("{".equals(ast.toString())) { 869 javaBraceNesting++; 870 } else if ("}".equals(ast.toString()) && (javaBraceNesting > 0)) { 871 javaBraceNesting--; 872 } 873 } 874 875 boolean skipSemicolonTermination = mleModuleWithAs || (javaBraceNesting > 0) || (withPlsqlDefinition && !withPlsqlFoundSelect); 876 if (ast.tokentype == ETokenType.ttsemicolon && !skipSemicolonTermination) { 877 gst = EFindSqlStateType.stnormal; 878 appendToken(gcurrentsqlstatement, ast); 879 gcurrentsqlstatement.semicolonended = ast; 880 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 881 mleModuleWithAs = false; // Reset flag 882 javaBraceNesting = 0; 883 withPlsqlDefinition = false; // Reset WITH FUNCTION flag 884 withPlsqlBeginEndNesting = 0; 885 cteMainSelectFound = false; 886 withPlsqlFoundSelect = false; 887 continue; 888 } 889 890 if (sourcetokenlist.sqlplusaftercurtoken()) //most probably is / cmd 891 { 892 gst = EFindSqlStateType.stnormal; 893 appendToken(gcurrentsqlstatement, ast); 894 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 895 mleModuleWithAs = false; // Reset flag 896 javaBraceNesting = 0; 897 continue; 898 } 899 900 if (ast.tokencode == '(') nestedParenthesis++; 901 if (ast.tokencode == ')') { 902 nestedParenthesis--; 903 if (nestedParenthesis < 0) nestedParenthesis = 0; 904 } 905 906 Boolean findNewStmt = false; 907 TCustomSqlStatement lcStmt = null; 908 // Check for new statement: CREATE TABLE (original), or SELECT inside a non-CTE SELECT 909 boolean shouldCheckNewStmt = false; 910 if ((nestedParenthesis == 0) && (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetable)) { 911 shouldCheckNewStmt = true; 912 } else if ((nestedParenthesis == 0) && (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstselect) 913 && (ast.tokencode == TBaseType.rrw_select || ast.tokencode == TBaseType.rrw_with)) { 914 // Check if current statement is a CTE (starts with WITH) 915 boolean isCteContext = false; 916 for (int si = 0; si < gcurrentsqlstatement.sourcetokenlist.size(); si++) { 917 TSourceToken st = gcurrentsqlstatement.sourcetokenlist.get(si); 918 if (st.tokentype == ETokenType.ttwhitespace || st.tokentype == ETokenType.ttreturn 919 || st.tokencode == TBaseType.cmtdoublehyphen || st.tokencode == TBaseType.cmtslashstar) { 920 continue; 921 } 922 if (st.tokencode == TBaseType.rrw_with) { 923 isCteContext = true; 924 } 925 break; 926 } 927 // Don't split if previous token makes this SELECT part of current statement: 928 // - Set operators: UNION, INTERSECT, MINUS, EXCEPT, ALL 929 // - Left paren: (SELECT ...) — SELECT is main query of parenthesized expr 930 boolean suppressSplit = false; 931 if (ast.tokencode == TBaseType.rrw_select && lcprevsolidtoken != null) { 932 int prevCode = lcprevsolidtoken.tokencode; 933 if (prevCode == TBaseType.rrw_union || prevCode == TBaseType.rrw_intersect 934 || prevCode == TBaseType.rrw_minus || prevCode == TBaseType.rrw_except 935 || prevCode == TBaseType.rrw_all 936 || prevCode == '(') { 937 suppressSplit = true; 938 } 939 } 940 if (suppressSplit) { 941 // SELECT is part of current statement — don't split 942 } else if (!isCteContext) { 943 // Non-CTE SELECT: any SELECT/WITH at paren level 0 starts a new statement 944 shouldCheckNewStmt = true; 945 } else if (cteMainSelectFound) { 946 // CTE context: main SELECT already consumed, so this SELECT/WITH 947 // at paren level 0 is a new statement 948 shouldCheckNewStmt = true; 949 } else if (ast.tokencode == TBaseType.rrw_select) { 950 // CTE context: this is the main SELECT after WITH name AS (...) 951 cteMainSelectFound = true; 952 // Don't split — this SELECT is part of the CTE statement 953 } 954 // If ast is WITH and main SELECT not yet found, it could be another 955 // CTE definition (WITH a AS (...), b AS (...)) — don't split 956 } 957 if (shouldCheckNewStmt) { 958 // For SELECT-after-SELECT/WITH splitting, use stnormal so issql can detect CTE starts. 959 // For CREATE TABLE, preserve original stsql state to avoid false positives 960 // (e.g., INSERT/DELETE keywords in blockchain table clauses). 961 EFindSqlStateType issqlState = (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstselect) 962 ? EFindSqlStateType.stnormal : gst; 963 lcStmt = sqlcmds.issql(ast, issqlState, gcurrentsqlstatement); 964 if (lcStmt != null) { 965 findNewStmt = true; 966 if (lcStmt.sqlstatementtype == ESqlStatementType.sstselect) { 967 TSourceToken prevst = ast.prevSolidToken(); 968 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetable) { 969 // For CREATE TABLE, suppress split when SELECT follows AS/(/): AS (SELECT ...) 970 if ((prevst.tokencode == TBaseType.rrw_as) || (prevst.tokencode == '(') || (prevst.tokencode == ')')) { 971 findNewStmt = false; 972 } 973 } 974 // For SELECT-after-SELECT/WITH splitting at paren level 0, 975 // no suppression needed — the new SELECT/WITH is a new statement 976 } 977 } 978 } 979 980 if (findNewStmt) { 981 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 982 gcurrentsqlstatement = lcStmt; 983 cteMainSelectFound = false; // Reset for new statement 984 nestedParenthesis = 0; // Reset paren tracking for new statement 985 appendToken(gcurrentsqlstatement, ast); 986 continue; 987 } else 988 appendToken(gcurrentsqlstatement, ast); 989 990 break; 991 }//case stsql 992 993 case ststoredprocedure: { 994 995 if (procedure_status[nestedProcedures] != stored_procedure_status.bodyend) { 996 appendToken(gcurrentsqlstatement, ast); 997 } 998 999 switch (procedure_status[nestedProcedures]) { 1000 case cursor_declare: 1001 if (ast.tokencode == ';') { 1002 nestedProcedures--; 1003 if (nestedProcedures < 0) { 1004 nestedProcedures = 0; 1005 } 1006 } 1007 break; 1008 case start: 1009 if ((ast.tokencode == TBaseType.rrw_as) || (ast.tokencode == TBaseType.rrw_is)) { 1010 if (sptype[nestedProcedures] != stored_procedure_type.create_trigger) { 1011 if ((sptype[0] == stored_procedure_type.package_spec) && (nestedProcedures > 0)) { 1012 // when it's a package specification, only top level accept as/is 1013 } else { 1014 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1015 if (ast.searchToken("language", 1) != null) { 1016 if (nestedProcedures == 0) { 1017 gst = EFindSqlStateType.stsql; 1018 } else { 1019 procedure_status[nestedProcedures] = stored_procedure_status.body; 1020 nestedProcedures--; 1021 } 1022 } 1023 } 1024 } 1025 } else if (ast.tokencode == TBaseType.rrw_begin) { 1026 if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) { 1027 waitingEnds[nestedProcedures]++; 1028 } 1029 if (nestedProcedures > 0) { 1030 nestedProcedures--; 1031 } 1032 procedure_status[nestedProcedures] = stored_procedure_status.body; 1033 } else if (ast.tokencode == TBaseType.rrw_end) { 1034 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures - 1] == 1) 1035 && ((sptype[nestedProcedures - 1] == stored_procedure_type.package_body) 1036 || (sptype[nestedProcedures - 1] == stored_procedure_type.package_spec))) { 1037 nestedProcedures--; 1038 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1039 } 1040 } else if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) { 1041 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0) 1042 && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) { 1043 nestedProcedures--; 1044 nestedProcedures++; 1045 waitingEnds[nestedProcedures] = 0; 1046 procedure_status[nestedProcedures] = stored_procedure_status.start; 1047 } 1048 } else if (ast.tokencode == TBaseType.rrw_oracle_cursor) { 1049 if ((nestedProcedures > 0) && (waitingEnds[nestedProcedures] == 0) 1050 && (procedure_status[nestedProcedures - 1] == stored_procedure_status.is_as)) { 1051 nestedProcedures--; 1052 nestedProcedures++; 1053 waitingEnds[nestedProcedures] = 0; 1054 procedure_status[nestedProcedures] = stored_procedure_status.cursor_declare; 1055 } 1056 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) && (ast.tokencode == TBaseType.rrw_declare)) { 1057 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1058 } else if ((sptype[nestedProcedures] == stored_procedure_type.create_trigger) 1059 && (ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 1060 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1061 gst = EFindSqlStateType.stnormal; 1062 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1063 1064 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1065 appendToken(gcurrentsqlstatement, ast); 1066 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1067 } else if (sptype[nestedProcedures] == stored_procedure_type.create_trigger) { 1068 if (ast.tokencode == TBaseType.rrw_trigger) { 1069 TSourceToken compoundSt = ast.searchToken(TBaseType.rrw_oracle_compound, -1); 1070 if (compoundSt != null) { 1071 procedure_status[nestedProcedures] = stored_procedure_status.body; 1072 waitingEnds[nestedProcedures]++; 1073 } 1074 } 1075 } else if ((sptype[nestedProcedures] == stored_procedure_type.function) 1076 && (ast.tokencode == TBaseType.rrw_teradata_using)) { 1077 if ((ast.searchToken("aggregate", -1) != null) || (ast.searchToken("pipelined", -1) != null)) { 1078 if (nestedProcedures == 0) { 1079 gst = EFindSqlStateType.stsql; 1080 } else { 1081 procedure_status[nestedProcedures] = stored_procedure_status.body; 1082 nestedProcedures--; 1083 } 1084 } 1085 } 1086 break; 1087 case is_as: 1088 if ((ast.tokencode == TBaseType.rrw_procedure) || (ast.tokencode == TBaseType.rrw_function)) { 1089 nestedProcedures++; 1090 if (nestedProcedures > stored_procedure_nested_level - 1) { 1091 gst = EFindSqlStateType.sterror; 1092 nestedProcedures--; 1093 } else { 1094 waitingEnds[nestedProcedures] = 0; 1095 procedure_status[nestedProcedures] = stored_procedure_status.start; 1096 } 1097 } else if (ast.tokencode == TBaseType.rrw_begin) { 1098 if ((nestedProcedures == 0) && 1099 ((sptype[nestedProcedures] == stored_procedure_type.package_body) 1100 || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) { 1101 // top level package begin already counted 1102 } else { 1103 waitingEnds[nestedProcedures]++; 1104 } 1105 procedure_status[nestedProcedures] = stored_procedure_status.body; 1106 } else if (ast.tokencode == TBaseType.rrw_end) { 1107 if ((nestedProcedures == 0) && (waitingEnds[nestedProcedures] == 1) 1108 && ((sptype[nestedProcedures] == stored_procedure_type.package_body) 1109 || (sptype[nestedProcedures] == stored_procedure_type.package_spec))) { 1110 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1111 waitingEnds[nestedProcedures]--; 1112 } else { 1113 waitingEnds[nestedProcedures]--; 1114 } 1115 } else if (ast.tokencode == TBaseType.rrw_case) { 1116 if (ast.searchToken(';', 1) == null) { 1117 waitingEnds[nestedProcedures]++; 1118 } 1119 } 1120 break; 1121 case body: 1122 if (ast.tokencode == TBaseType.rrw_begin) { 1123 waitingEnds[nestedProcedures]++; 1124 } else if (ast.tokencode == TBaseType.rrw_if) { 1125 if (ast.searchToken(';', 2) == null) { 1126 waitingEnds[nestedProcedures]++; 1127 } 1128 } else if (ast.tokencode == TBaseType.rrw_case) { 1129 if (ast.searchToken(';', 2) == null) { 1130 if (ast.searchToken(TBaseType.rrw_end, -1) == null) { 1131 waitingEnds[nestedProcedures]++; 1132 } 1133 } 1134 } else if (ast.tokencode == TBaseType.rrw_loop) { 1135 if (!((ast.searchToken(TBaseType.rrw_end, -1) != null) 1136 && (ast.searchToken(';', 2) != null))) { 1137 waitingEnds[nestedProcedures]++; 1138 } 1139 } else if (ast.tokencode == TBaseType.rrw_end) { 1140 waitingEnds[nestedProcedures]--; 1141 if (waitingEnds[nestedProcedures] == 0) { 1142 if (nestedProcedures == 0) { 1143 procedure_status[nestedProcedures] = stored_procedure_status.bodyend; 1144 } else { 1145 nestedProcedures--; 1146 procedure_status[nestedProcedures] = stored_procedure_status.is_as; 1147 } 1148 } 1149 } else if ((waitingEnds[nestedProcedures] == 0) 1150 && (ast.tokentype == ETokenType.ttslash) 1151 && (ast.tokencode == TBaseType.sqlpluscmd)) { 1152 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1153 gst = EFindSqlStateType.stnormal; 1154 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1155 1156 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1157 appendToken(gcurrentsqlstatement, ast); 1158 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1159 } 1160 break; 1161 case bodyend: 1162 if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) { 1163 // TPlsqlStatementParse(asqlstatement).TerminatorToken := ast; 1164 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1165 gst = EFindSqlStateType.stnormal; 1166 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1167 1168 //make / a sqlplus cmd 1169 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1170 appendToken(gcurrentsqlstatement, ast); 1171 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1172 } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) { 1173 // single dot at a seperate line 1174 ast.tokenstatus = ETokenStatus.tsignorebyyacc; 1175 gst = EFindSqlStateType.stnormal; 1176 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1177 1178 //make ttperiod a sqlplus cmd 1179 gcurrentsqlstatement = new TSqlplusCmdStatement(vendor); 1180 appendToken(gcurrentsqlstatement, ast); 1181 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1182 } else if ((ast.searchToken(TBaseType.rrw_package, 1) != null) && (!endBySlashOnly)) { 1183 appendToken(gcurrentsqlstatement, ast); 1184 gst = EFindSqlStateType.stnormal; 1185 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1186 } else if ((ast.searchToken(TBaseType.rrw_procedure, 1) != null) && (!endBySlashOnly)) { 1187 appendToken(gcurrentsqlstatement, ast); 1188 gst = EFindSqlStateType.stnormal; 1189 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1190 } else if ((ast.searchToken(TBaseType.rrw_function, 1) != null) && (!endBySlashOnly)) { 1191 appendToken(gcurrentsqlstatement, ast); 1192 gst = EFindSqlStateType.stnormal; 1193 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1194 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) 1195 && ((ast.searchToken(TBaseType.rrw_package, 4) != null) || (ast.searchToken(TBaseType.rrw_package, 5) != null)) 1196 && (!endBySlashOnly)) { 1197 appendToken(gcurrentsqlstatement, ast); 1198 gst = EFindSqlStateType.stnormal; 1199 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1200 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) 1201 && ((ast.searchToken(TBaseType.rrw_procedure, 4) != null) 1202 || (ast.searchToken(TBaseType.rrw_function, 4) != null) 1203 || (ast.searchToken(TBaseType.rrw_view, 4) != null) 1204 || (ast.searchToken(TBaseType.rrw_oracle_synonym, 4) != null) 1205 || (ast.searchToken(TBaseType.rrw_trigger, 4) != null)) 1206 && (!endBySlashOnly)) { 1207 appendToken(gcurrentsqlstatement, ast); 1208 gst = EFindSqlStateType.stnormal; 1209 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1210 } else if ((ast.searchToken(TBaseType.rrw_create, 1) != null) && (ast.searchToken(TBaseType.rrw_library, 4) != null) && (!endBySlashOnly)) { 1211 appendToken(gcurrentsqlstatement, ast); 1212 gst = EFindSqlStateType.stnormal; 1213 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1214 } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null) && (ast.searchToken(TBaseType.rrw_trigger, 2) != null) && (!endBySlashOnly)) { 1215 appendToken(gcurrentsqlstatement, ast); 1216 gst = EFindSqlStateType.stnormal; 1217 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1218 } else if ((ast.searchToken(TBaseType.rrw_select, 1) != null) && (!endBySlashOnly)) { 1219 appendToken(gcurrentsqlstatement, ast); 1220 gst = EFindSqlStateType.stnormal; 1221 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1222 } else if ((ast.searchToken(TBaseType.rrw_call, 1) != null) && (!endBySlashOnly)) { 1223 appendToken(gcurrentsqlstatement, ast); 1224 gst = EFindSqlStateType.stnormal; 1225 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1226 } else if ((ast.searchToken(TBaseType.rrw_commit, 1) != null) && (!endBySlashOnly)) { 1227 appendToken(gcurrentsqlstatement, ast); 1228 gst = EFindSqlStateType.stnormal; 1229 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1230 } else if ((ast.searchToken(TBaseType.rrw_declare, 1) != null) && (!endBySlashOnly)) { 1231 appendToken(gcurrentsqlstatement, ast); 1232 gst = EFindSqlStateType.stnormal; 1233 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1234 } else if ((ast.searchToken(TBaseType.rrw_grant, 1) != null) 1235 && (ast.searchToken(TBaseType.rrw_execute, 2) != null) && (!endBySlashOnly)) { 1236 appendToken(gcurrentsqlstatement, ast); 1237 gst = EFindSqlStateType.stnormal; 1238 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1239 } else if ((ast.searchToken(TBaseType.rrw_alter, 1) != null) 1240 && (ast.searchToken(TBaseType.rrw_table, 2) != null) && (!endBySlashOnly)) { 1241 appendToken(gcurrentsqlstatement, ast); 1242 gst = EFindSqlStateType.stnormal; 1243 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, false, builder); 1244 } else { 1245 appendToken(gcurrentsqlstatement, ast); 1246 } 1247 break; 1248 case end: 1249 break; 1250 default: 1251 break; 1252 } 1253 1254 if (ast.tokencode == TBaseType.sqlpluscmd) { 1255 int m = flexer.getkeywordvalue(ast.getAstext()); 1256 if (m != 0) { 1257 ast.tokencode = m; 1258 } else if (ast.tokentype == ETokenType.ttslash) { 1259 ast.tokencode = '/'; 1260 } else { 1261 ast.tokencode = TBaseType.ident; 1262 } 1263 } 1264 1265 final int wrapped_keyword_max_pos = 20; 1266 if ((ast.tokencode == TBaseType.rrw_wrapped) 1267 && (ast.posinlist - gcurrentsqlstatement.sourcetokenlist.get(0).posinlist < wrapped_keyword_max_pos)) { 1268 if (gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.TCommonStoredProcedureSqlStatement) { 1269 ((gudusoft.gsqlparser.stmt.TCommonStoredProcedureSqlStatement) gcurrentsqlstatement).setWrapped(true); 1270 } 1271 1272 if (gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage) { 1273 if (ast.prevSolidToken() != null) { 1274 ((gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage) gcurrentsqlstatement) 1275 .setPackageName(fparser.getNf().createObjectNameWithPart(ast.prevSolidToken())); 1276 } 1277 } 1278 } 1279 1280 break; 1281 } //ststoredprocedure 1282 1283 } //switch 1284 }//for 1285 1286 //last statement 1287 if ((gcurrentsqlstatement != null) && 1288 ((gst == EFindSqlStateType.stsqlplus) || (gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) || 1289 (gst == EFindSqlStateType.sterror))) { 1290 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, this.fplsqlparser, this.sqlstatements, true, builder); 1291 } 1292 1293 // Populate builder with results 1294 builder.sqlStatements(this.sqlstatements); 1295 builder.syntaxErrors(syntaxErrors instanceof ArrayList ? 1296 (ArrayList<TSyntaxError>) syntaxErrors : new ArrayList<>(syntaxErrors)); 1297 builder.errorCode(syntaxErrors.isEmpty() ? 0 : syntaxErrors.size()); 1298 builder.errorMessage(syntaxErrors.isEmpty() ? "" : 1299 String.format("Raw extraction completed with %d error(s)", syntaxErrors.size())); 1300 } 1301 1302 /** 1303 * True when a WITH inside a statement opens inline PL/SQL declarations (Mantis 4949): it has 1304 * the routine shape {@code WITH FUNCTION name (} or {@code WITH FUNCTION name RETURN}, or 1305 * {@code WITH PROCEDURE name} followed by {@code (}, {@code IS} or {@code AS}. FUNCTION and 1306 * PROCEDURE are legal column and CTE names, so {@code START WITH function = 1} and 1307 * {@code WITH function AS (...)} are not inline PL/SQL. 1308 */ 1309 private static boolean isInlinePlsqlWith(TSourceToken withToken) { 1310 TSourceToken prev = withToken.prevSolidToken(); 1311 if ((prev != null) && (prev.tokencode == TBaseType.rrw_start)) return false; 1312 TSourceToken routine = withToken.nextSolidToken(); 1313 if (routine == null) return false; 1314 boolean isFunction = (routine.tokencode == TBaseType.rrw_function); 1315 if (!isFunction && (routine.tokencode != TBaseType.rrw_procedure)) return false; 1316 TSourceToken name = routine.nextSolidToken(); 1317 if (name == null) return false; 1318 if ((name.tokentype != ETokenType.ttidentifier) && (name.tokentype != ETokenType.ttdqstring) 1319 && (name.tokentype != ETokenType.ttkeyword)) return false; 1320 if ((name.tokencode == TBaseType.rrw_as) || (name.tokencode == TBaseType.rrw_is) 1321 || (name.tokencode == TBaseType.rrw_return)) return false; 1322 TSourceToken next = name.nextSolidToken(); 1323 if (next == null) return false; 1324 if (next.tokencode == '(') return true; 1325 if (isFunction) return (next.tokencode == TBaseType.rrw_return); 1326 return (next.tokencode == TBaseType.rrw_is) || (next.tokencode == TBaseType.rrw_as); 1327 } 1328 1329 /** 1330 * Handle token transformations during raw statement extraction. 1331 * <p> 1332 * This performs Oracle-specific keyword disambiguation that must happen 1333 * before statement boundary detection. Examples: 1334 * <ul> 1335 * <li>RETURN after WHERE → treat as identifier</li> 1336 * <li>VALUE after BY → mark as value_after_by</li> 1337 * <li>NEW → treat as identifier or constructor based on context</li> 1338 * <li>And many more Oracle-specific cases</li> 1339 * </ul> 1340 * 1341 * @param ast current token being processed 1342 */ 1343 private void performRawStatementTokenTransformations(TSourceToken ast) { 1344 // This method contains the keyword transformation logic from dooraclegetrawsqlstatements 1345 // It's been extracted to keep the main method more readable 1346 1347 if (ast.tokencode == TBaseType.rrw_return) { 1348 TSourceToken stMatch = ast.searchToken(TBaseType.rrw_where, 1); 1349 if (stMatch != null) { 1350 ast.tokencode = TBaseType.ident; 1351 } 1352 } else if (ast.tokencode == TBaseType.rrw_value_oracle) { 1353 TSourceToken stBy = ast.searchToken(TBaseType.rrw_by, -1); 1354 if (stBy != null) { 1355 ast.tokencode = TBaseType.rrw_value_after_by; 1356 } 1357 } else if (ast.tokencode == TBaseType.rrw_new_oracle) { 1358 TSourceToken stRightParen = ast.searchToken(')', -1); 1359 if (stRightParen != null) { 1360 ast.tokencode = TBaseType.ident; 1361 } 1362 TSourceToken stDot = ast.searchToken('.', 1); 1363 if (stDot != null) { 1364 ast.tokencode = TBaseType.ident; 1365 } 1366 1367 TSourceToken stNext = ast.searchTokenAfterObjectName(); 1368 stDot = ast.searchToken('.', 1); 1369 if ((stDot == null) && (stNext != null) && (stNext.tokencode == '(')) { 1370 ast.tokencode = TBaseType.rrw_oracle_new_constructor; 1371 } 1372 } else if (ast.tokencode == TBaseType.rrw_chr_oracle) { 1373 TSourceToken stLeftParen = ast.searchToken('(', 1); 1374 if (stLeftParen == null) { 1375 ast.tokencode = TBaseType.ident; 1376 } 1377 } else if (ast.tokencode == TBaseType.rrw_log_oracle) { 1378 TSourceToken stNext = ast.searchToken(TBaseType.rrw_errors_oracle, 1); 1379 TSourceToken stPrev = ast.searchToken(TBaseType.rrw_view, -1); 1380 if (stPrev == null) { 1381 stPrev = ast.searchToken(TBaseType.rrw_oracle_supplemental, -1); 1382 } 1383 if ((stNext == null) && (stPrev == null)) { 1384 ast.tokencode = TBaseType.ident; 1385 } 1386 } else if (ast.tokencode == TBaseType.rrw_delete) { 1387 TSourceToken stPrev = ast.searchToken('.', -1); 1388 if (stPrev != null) { 1389 ast.tokencode = TBaseType.ident; 1390 } 1391 } else if (ast.tokencode == TBaseType.rrw_partition) { 1392 TSourceToken stPrev = ast.searchToken(TBaseType.rrw_add, -1); 1393 if (stPrev != null) { 1394 stPrev.tokencode = TBaseType.rrw_add_p; 1395 } 1396 } else if (ast.tokencode == TBaseType.rrw_oracle_column) { 1397 TSourceToken stPrev = ast.searchToken(TBaseType.rrw_oracle_modify, -1); 1398 if (stPrev != null) { 1399 ast.tokencode = TBaseType.rrw_oracle_column_after_modify; 1400 } 1401 } else if (ast.tokencode == TBaseType.rrw_oracle_apply) { 1402 TSourceToken stPrev = ast.searchToken(TBaseType.rrw_outer, -1); 1403 if (stPrev != null) { 1404 stPrev.tokencode = TBaseType.ORACLE_OUTER2; 1405 } 1406 } else if (ast.tokencode == TBaseType.rrw_oracle_subpartition) { 1407 TSourceToken stNext = ast.searchToken("(", 2); 1408 if (stNext != null) { 1409 TSourceToken st1 = ast.nextSolidToken(); 1410 if (st1.toString().equalsIgnoreCase("template")) { 1411 // don't change, keep as RW_SUBPARTITION 1412 } else { 1413 ast.tokencode = TBaseType.rrw_oracle_subpartition_tablesample; 1414 } 1415 } 1416 } else if (ast.tokencode == TBaseType.rrw_primary) { 1417 TSourceToken stNext = ast.searchToken("key", 1); 1418 if (stNext == null) { 1419 ast.tokencode = TBaseType.ident; 1420 } 1421 } else if (ast.tokencode == TBaseType.rrw_oracle_offset) { 1422 TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_row, 2); 1423 if (stNext == null) { 1424 stNext = ast.searchToken(TBaseType.rrw_oracle_rows, 2); 1425 } 1426 if (stNext == null) { 1427 stNext = searchRowAfterOffsetExpression(ast); 1428 } 1429 if (stNext != null) { 1430 ast.tokencode = TBaseType.rrw_oracle_offset_row; 1431 } 1432 } else if (ast.tokencode == TBaseType.rrw_translate) { 1433 TSourceToken stNext = ast.searchToken("(", 2); 1434 if (stNext == null) { 1435 ast.tokencode = TBaseType.ident; 1436 } 1437 } else if (ast.tokencode == TBaseType.rrw_constraint) { 1438 TSourceToken stNext = ast.nextSolidToken(); 1439 if (stNext == null) { 1440 ast.tokencode = TBaseType.ident; 1441 } else { 1442 if (stNext.tokencode != TBaseType.ident) { 1443 ast.tokencode = TBaseType.ident; 1444 } 1445 } 1446 } else if (ast.tokencode == TBaseType.rrw_oracle_without) { 1447 TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_count, 1); 1448 if (stNext != null) { 1449 ast.tokencode = TBaseType.rrw_oracle_without_before_count; 1450 } 1451 } else if (ast.tokencode == TBaseType.rrw_bulk) { 1452 TSourceToken stNext = ast.searchToken(TBaseType.rrw_oracle_collect, 1); 1453 if (stNext == null) { 1454 ast.tokencode = TBaseType.ident; 1455 } 1456 } else if (ast.tokencode == TBaseType.rrw_oracle_model) { 1457 TSourceToken stNext = ast.nextSolidToken(); 1458 if (stNext != null) { 1459 switch (stNext.toString().toUpperCase(java.util.Locale.ROOT)) { 1460 case "RETURN": 1461 case "REFERENCE": 1462 case "IGNORE": 1463 case "KEEP": 1464 case "UNIQUE": 1465 case "PARTITION": 1466 case "DIMENSION": 1467 case "MEASURES": 1468 case "RULES": 1469 ast.tokencode = TBaseType.rrw_oracle_model_in_model_clause; 1470 break; 1471 default: 1472 ; 1473 } 1474 } 1475 } 1476 } 1477 1478 /** 1479 * Look for the ROW/ROWS keyword terminating an OFFSET clause whose value is 1480 * a multi-token arithmetic expression, e.g. <code>OFFSET 1 * 10 ROWS</code> 1481 * (Mantis 4614). The single-token case is handled by the caller via 1482 * {@link TSourceToken#searchToken(int, int)}; this scan only accepts a 1483 * strictly alternating operand/operator token run (with balanced 1484 * parentheses), so a bare <code>OFFSET</code> used as a column name is not 1485 * rewritten. 1486 * 1487 * @param offsetToken the OFFSET keyword token 1488 * @return the terminating ROW/ROWS token, or null if the following tokens 1489 * do not form an arithmetic expression ended by ROW/ROWS 1490 */ 1491 private TSourceToken searchRowAfterOffsetExpression(TSourceToken offsetToken) { 1492 // generous cap: only bounds runaway scans on token runs that never hit 1493 // ROW/ROWS; any realistic offset expression is far shorter (a tight cap 1494 // would reject valid long expressions before the grammar sees them) 1495 final int maxTokens = 512; 1496 TSourceToken t = offsetToken.nextSolidToken(); 1497 int parenDepth = 0; 1498 boolean expectOperand = true; 1499 int steps = 0; 1500 while (t != null && steps++ < maxTokens) { 1501 String text = t.toString(); 1502 if (expectOperand) { 1503 if (text.equals("(")) { 1504 parenDepth++; 1505 } else if (text.equals("+") || text.equals("-")) { 1506 // unary sign, keep expecting an operand 1507 } else if (t.tokencode == TBaseType.iconst 1508 || t.tokencode == TBaseType.fconst 1509 || t.tokencode == TBaseType.ident 1510 || text.startsWith(":")) { 1511 expectOperand = false; 1512 } else { 1513 return null; 1514 } 1515 } else { 1516 if (parenDepth == 0 1517 && (t.tokencode == TBaseType.rrw_oracle_row || t.tokencode == TBaseType.rrw_oracle_rows)) { 1518 return t; 1519 } 1520 if (text.equals(")")) { 1521 if (--parenDepth < 0) { 1522 return null; 1523 } 1524 } else if (text.equals("+") || text.equals("-") || text.equals("*") || text.equals("/")) { 1525 expectOperand = true; 1526 } else { 1527 return null; 1528 } 1529 } 1530 t = t.nextSolidToken(); 1531 } 1532 return null; 1533 } 1534 1535 private void appendToken(TCustomSqlStatement statement, TSourceToken token) { 1536 if (statement == null || token == null) { 1537 return; 1538 } 1539 token.stmt = statement; 1540 statement.sourcetokenlist.add(token); 1541 } 1542 1543 // ========== Error Handling and Recovery ========== 1544 1545 /** 1546 * Find all syntax errors in PL/SQL statements recursively. 1547 * Extracted from TGSqlParser.findAllSyntaxErrorsInPlsql(). 1548 */ 1549 private void findAllSyntaxErrorsInPlsql(TCustomSqlStatement psql) { 1550 if (psql.getErrorCount() > 0) { 1551 copyErrorsFromStatement(psql); 1552 } 1553 1554 for (int k = 0; k < psql.getStatements().size(); k++) { 1555 findAllSyntaxErrorsInPlsql(psql.getStatements().get(k)); 1556 } 1557 } 1558 1559 /** 1560 * Handle error recovery for CREATE TABLE/INDEX statements. 1561 * Oracle allows table properties that may not be fully parsed. 1562 * This method marks unparseable properties as SQL*Plus commands to skip them. 1563 * 1564 * <p>Extracted from TGSqlParser.doparse() lines 16916-16971 1565 */ 1566 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 1567 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) || 1568 (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) && 1569 (!TBaseType.c_createTableStrictParsing)) { 1570 1571 // Find the closing parenthesis of table definition 1572 int nested = 0; 1573 boolean isIgnore = false, isFoundIgnoreToken = false; 1574 TSourceToken firstIgnoreToken = null; 1575 1576 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 1577 TSourceToken st = stmt.sourcetokenlist.get(k); 1578 1579 if (isIgnore) { 1580 if (st.issolidtoken() && (st.tokencode != ';')) { 1581 isFoundIgnoreToken = true; 1582 if (firstIgnoreToken == null) { 1583 firstIgnoreToken = st; 1584 } 1585 } 1586 if (st.tokencode != ';') { 1587 st.tokencode = TBaseType.sqlpluscmd; 1588 } 1589 continue; 1590 } 1591 1592 if (st.tokencode == (int) ')') { 1593 nested--; 1594 if (nested == 0) { 1595 // Check if next token is "AS ( SELECT" 1596 boolean isSelect = false; 1597 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 1598 if (st1 != null) { 1599 TSourceToken st2 = st.searchToken((int) '(', 2); 1600 if (st2 != null) { 1601 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 1602 isSelect = (st3 != null); 1603 } 1604 } 1605 if (!isSelect) isIgnore = true; 1606 } 1607 } 1608 1609 if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 1610 nested++; 1611 } 1612 } 1613 1614 // Verify it's a valid Oracle table property 1615 if ((firstIgnoreToken != null) && 1616 (!TBaseType.searchOracleTablePros(firstIgnoreToken.toString()))) { 1617 // Not a valid property, keep the error 1618 isFoundIgnoreToken = false; 1619 } 1620 1621 // Retry parsing if we found ignoreable properties 1622 if (isFoundIgnoreToken) { 1623 stmt.clearError(); 1624 stmt.parsestatement(null, false); 1625 } 1626 } 1627 } 1628 1629 /** 1630 * Copy syntax errors from a statement to our error list. 1631 * Extracted from TGSqlParser.copyerrormsg(). 1632 */ 1633 1634 @Override 1635 public String toString() { 1636 return "OracleSqlParser{vendor=" + vendor + "}"; 1637 } 1638 1639 // ========== Main Oracle Tokenization ========== 1640 // Core tokenization logic extracted from TGSqlParser.dooraclesqltexttotokenlist() 1641 1642 /** 1643 * Perform Oracle-specific tokenization with SQL*Plus command detection. 1644 * <p> 1645 * This method implements Oracle's complex tokenization rules including: 1646 * <ul> 1647 * <li>SQL*Plus command detection (SPOOL, SET, START, etc.)</li> 1648 * <li>Forward slash disambiguation (division vs PL/SQL delimiter)</li> 1649 * <li>Oracle-specific keyword transformations (INNER, TYPE, FULL, etc.)</li> 1650 * <li>Context-dependent token code modifications</li> 1651 * </ul> 1652 * 1653 * <p><b>State Machine:</b> Uses 5 boolean flags to track tokenization state: 1654 * <ul> 1655 * <li>{@code insqlpluscmd} - Currently inside SQL*Plus command</li> 1656 * <li>{@code isvalidplace} - Valid place to start SQL*Plus command</li> 1657 * <li>{@code waitingreturnforfloatdiv} - Slash seen, waiting for newline</li> 1658 * <li>{@code waitingreturnforsemicolon} - Semicolon seen, waiting for newline</li> 1659 * <li>{@code continuesqlplusatnewline} - SQL*Plus command continues to next line</li> 1660 * </ul> 1661 * 1662 * <p><b>Extracted from:</b> TGSqlParser.dooraclesqltexttotokenlist() (lines 3931-4298) 1663 * 1664 * @throws RuntimeException if tokenization fails 1665 */ 1666 private void dooraclesqltexttotokenlist() { 1667 // Initialize state machine for SQL*Plus command detection 1668 insqlpluscmd = false; 1669 javaBraceNesting = 0; 1670 isvalidplace = true; 1671 waitingreturnforfloatdiv = false; 1672 waitingreturnforsemicolon = false; 1673 continuesqlplusatnewline = false; 1674 1675 ESqlPlusCmd currentCmdType = ESqlPlusCmd.spcUnknown; 1676 1677 TSourceToken lct = null, prevst = null; 1678 1679 TSourceToken asourcetoken, lcprevst; 1680 int yychar; 1681 1682 asourcetoken = getanewsourcetoken(); 1683 if (asourcetoken == null) return; 1684 yychar = asourcetoken.tokencode; 1685 1686 while (yychar > 0) { 1687 sourcetokenlist.add(asourcetoken); 1688 1689 switch (yychar) { 1690 case TBaseType.cmtdoublehyphen: 1691 case TBaseType.cmtslashstar: 1692 case TBaseType.lexspace: { 1693 if (insqlpluscmd) { 1694 asourcetoken.insqlpluscmd = true; 1695 } 1696 break; 1697 } 1698 1699 case TBaseType.lexnewline: { 1700 if (insqlpluscmd) { 1701 insqlpluscmd = false; 1702 isvalidplace = true; 1703 1704 if (continuesqlplusatnewline) { 1705 insqlpluscmd = true; 1706 isvalidplace = false; 1707 asourcetoken.insqlpluscmd = true; 1708 } 1709 1710 if (!insqlpluscmd) { 1711 currentCmdType = ESqlPlusCmd.spcUnknown; 1712 } 1713 } 1714 1715 if (waitingreturnforsemicolon) { 1716 isvalidplace = true; 1717 } 1718 1719 if (waitingreturnforfloatdiv) { 1720 isvalidplace = true; 1721 lct.tokencode = TBaseType.sqlpluscmd; 1722 if (lct.tokentype != ETokenType.ttslash) { 1723 lct.tokentype = ETokenType.ttsqlpluscmd; 1724 } 1725 } 1726 1727 if (countLines(asourcetoken.toString()) > 1) { 1728 // There is a line after select, so spool is the right place to start a sqlplus command 1729 isvalidplace = true; 1730 } 1731 1732 flexer.insqlpluscmd = insqlpluscmd; 1733 break; 1734 } 1735 1736 default: { 1737 // Solid token 1738 // Save semicolon flag before clearing: slash after semicolon on 1739 // the same line (e.g. "END; /") should be a SQL*Plus delimiter, 1740 // not division. 1741 boolean prevWasSemicolon = waitingreturnforsemicolon; 1742 continuesqlplusatnewline = false; 1743 waitingreturnforsemicolon = false; 1744 waitingreturnforfloatdiv = false; 1745 1746 if (insqlpluscmd) { 1747 asourcetoken.insqlpluscmd = true; 1748 if (asourcetoken.toString().equalsIgnoreCase("-")) { 1749 continuesqlplusatnewline = true; 1750 } 1751 } else { 1752 if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 1753 waitingreturnforsemicolon = true; 1754 } 1755 1756 if ((asourcetoken.tokentype == ETokenType.ttslash) 1757 && (isvalidplace || prevWasSemicolon || (isValidPlaceForDivToSqlplusCmd(sourcetokenlist, asourcetoken.posinlist)))) { 1758 lct = asourcetoken; 1759 waitingreturnforfloatdiv = true; 1760 } 1761 1762 currentCmdType = TSqlplusCmdStatement.searchCmd(asourcetoken.toString(), asourcetoken.nextToken()); 1763 if (currentCmdType != ESqlPlusCmd.spcUnknown) { 1764 if (isvalidplace) { 1765 TSourceToken lnbreak = null; 1766 boolean aRealSqlplusCmd = true; 1767 if (sourcetokenlist.curpos > 0) { 1768 lnbreak = sourcetokenlist.get(sourcetokenlist.curpos - 1); 1769 aRealSqlplusCmd = !spaceAtTheEndOfReturnToken(lnbreak.toString()); 1770 } 1771 1772 if (aRealSqlplusCmd) { 1773 asourcetoken.prevTokenCode = asourcetoken.tokencode; 1774 asourcetoken.tokencode = TBaseType.sqlpluscmd; 1775 if (asourcetoken.tokentype != ETokenType.ttslash) { 1776 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 1777 } 1778 insqlpluscmd = true; 1779 flexer.insqlpluscmd = insqlpluscmd; 1780 } 1781 } else if ((asourcetoken.tokencode == TBaseType.rrw_connect) && (sourcetokenlist.returnbeforecurtoken(true))) { 1782 asourcetoken.tokencode = TBaseType.sqlpluscmd; 1783 if (asourcetoken.tokentype != ETokenType.ttslash) { 1784 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 1785 } 1786 insqlpluscmd = true; 1787 flexer.insqlpluscmd = insqlpluscmd; 1788 } else if (sourcetokenlist.returnbeforecurtoken(true)) { 1789 TSourceToken lnbreak = sourcetokenlist.get(sourcetokenlist.curpos - 1); 1790 1791 if ((countLines(lnbreak.toString()) > 1) && (!spaceAtTheEndOfReturnToken(lnbreak.toString()))) { 1792 asourcetoken.tokencode = TBaseType.sqlpluscmd; 1793 if (asourcetoken.tokentype != ETokenType.ttslash) { 1794 asourcetoken.tokentype = ETokenType.ttsqlpluscmd; 1795 } 1796 insqlpluscmd = true; 1797 flexer.insqlpluscmd = insqlpluscmd; 1798 } 1799 } 1800 } 1801 } 1802 1803 isvalidplace = false; 1804 1805 // Oracle-specific keyword handling (inline to match legacy behavior) 1806 if (prevst != null) { 1807 if (prevst.tokencode == TBaseType.rrw_inner) { 1808 if (asourcetoken.tokencode != flexer.getkeywordvalue("JOIN")) { 1809 prevst.tokencode = TBaseType.ident; 1810 } 1811 } else if ((prevst.tokencode == TBaseType.rrw_not) 1812 && (asourcetoken.tokencode == flexer.getkeywordvalue("DEFERRABLE"))) { 1813 prevst.tokencode = flexer.getkeywordvalue("NOT_DEFERRABLE"); 1814 } 1815 } 1816 1817 if (asourcetoken.tokencode == TBaseType.rrw_inner) { 1818 prevst = asourcetoken; 1819 } else if (asourcetoken.tokencode == TBaseType.rrw_not) { 1820 prevst = asourcetoken; 1821 } else { 1822 prevst = null; 1823 } 1824 1825 // Oracle keyword transformations that rely on prev token state 1826 if ((asourcetoken.tokencode == flexer.getkeywordvalue("DIRECT_LOAD")) 1827 || (asourcetoken.tokencode == flexer.getkeywordvalue("ALL"))) { 1828 lcprevst = getprevsolidtoken(asourcetoken); 1829 if (lcprevst != null) { 1830 if (lcprevst.tokencode == TBaseType.rrw_for) 1831 lcprevst.tokencode = TBaseType.rw_for1; 1832 } 1833 } else if (asourcetoken.tokencode == TBaseType.rrw_dense_rank) { 1834 TSourceToken stKeep = asourcetoken.searchToken(TBaseType.rrw_keep, -2); 1835 if (stKeep != null) { 1836 stKeep.tokencode = TBaseType.rrw_keep_before_dense_rank; 1837 } 1838 } else if (asourcetoken.tokencode == TBaseType.rrw_full) { 1839 TSourceToken stMatch = asourcetoken.searchToken(TBaseType.rrw_match, -1); 1840 if (stMatch != null) { 1841 asourcetoken.tokencode = TBaseType.RW_FULL2; 1842 } 1843 } else if (asourcetoken.tokencode == TBaseType.rrw_join) { 1844 TSourceToken stFull = asourcetoken.searchToken(TBaseType.rrw_full, -1); 1845 if (stFull != null) { 1846 stFull.tokencode = TBaseType.RW_FULL2; 1847 } else { 1848 TSourceToken stNatural = asourcetoken.searchToken(TBaseType.rrw_natural, -4); 1849 if (stNatural != null) { 1850 stNatural.tokencode = TBaseType.RW_NATURAL2; 1851 } 1852 } 1853 } else if (asourcetoken.tokencode == TBaseType.rrw_outer) { 1854 TSourceToken stFull = asourcetoken.searchToken(TBaseType.rrw_full, -1); 1855 if (stFull != null) { 1856 stFull.tokencode = TBaseType.RW_FULL2; 1857 } 1858 } else if (asourcetoken.tokencode == TBaseType.rrw_is) { 1859 TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2); 1860 if (stType != null) { 1861 stType.tokencode = TBaseType.rrw_type2; 1862 } 1863 } else if (asourcetoken.tokencode == TBaseType.rrw_as) { 1864 TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2); 1865 if (stType != null) { 1866 stType.tokencode = TBaseType.rrw_type2; 1867 } 1868 } else if (asourcetoken.tokencode == TBaseType.rrw_oid) { 1869 TSourceToken stType = asourcetoken.searchToken(TBaseType.rrw_type, -2); 1870 if (stType != null) { 1871 stType.tokencode = TBaseType.rrw_type2; 1872 } 1873 } else if (asourcetoken.tokencode == TBaseType.rrw_type) { 1874 TSourceToken stPrev; 1875 stPrev = asourcetoken.searchToken(TBaseType.rrw_drop, -1); 1876 if (stPrev != null) { 1877 asourcetoken.tokencode = TBaseType.rrw_type2; 1878 } 1879 if (asourcetoken.tokencode == TBaseType.rrw_type) { 1880 stPrev = asourcetoken.searchToken(TBaseType.rrw_of, -1); 1881 if (stPrev != null) { 1882 asourcetoken.tokencode = TBaseType.rrw_type2; 1883 } 1884 } 1885 if (asourcetoken.tokencode == TBaseType.rrw_type) { 1886 stPrev = asourcetoken.searchToken(TBaseType.rrw_create, -1); 1887 if (stPrev != null) { 1888 asourcetoken.tokencode = TBaseType.rrw_type2; 1889 } 1890 } 1891 if (asourcetoken.tokencode == TBaseType.rrw_type) { 1892 stPrev = asourcetoken.searchToken(TBaseType.rrw_replace, -1); 1893 if (stPrev != null) { 1894 asourcetoken.tokencode = TBaseType.rrw_type2; 1895 } 1896 } 1897 if (asourcetoken.tokencode == TBaseType.rrw_type) { 1898 stPrev = asourcetoken.searchToken('%', -1); 1899 if (stPrev != null) { 1900 asourcetoken.tokencode = TBaseType.rrw_type2; 1901 } 1902 } 1903 } else if ((asourcetoken.tokencode == TBaseType.rrw_by) || (asourcetoken.tokencode == TBaseType.rrw_to)) { 1904 lcprevst = getprevsolidtoken(asourcetoken); 1905 if (lcprevst != null) { 1906 if ((lcprevst.tokencode == TBaseType.sqlpluscmd) && (lcprevst.toString().equalsIgnoreCase("connect"))) { 1907 lcprevst.tokencode = TBaseType.rrw_connect; 1908 lcprevst.tokentype = ETokenType.ttkeyword; 1909 flexer.insqlpluscmd = false; 1910 1911 continuesqlplusatnewline = false; 1912 waitingreturnforsemicolon = false; 1913 waitingreturnforfloatdiv = false; 1914 isvalidplace = false; 1915 insqlpluscmd = false; 1916 } 1917 } 1918 } else if (asourcetoken.tokencode == TBaseType.rrw_with) { 1919 lcprevst = getprevsolidtoken(asourcetoken); 1920 if (lcprevst != null) { 1921 if ((lcprevst.tokencode == TBaseType.sqlpluscmd) && (lcprevst.toString().equalsIgnoreCase("start"))) { 1922 lcprevst.tokencode = TBaseType.rrw_start; 1923 lcprevst.tokentype = ETokenType.ttkeyword; 1924 flexer.insqlpluscmd = false; 1925 1926 continuesqlplusatnewline = false; 1927 waitingreturnforsemicolon = false; 1928 waitingreturnforfloatdiv = false; 1929 isvalidplace = false; 1930 insqlpluscmd = false; 1931 } 1932 } 1933 } else if (asourcetoken.tokencode == TBaseType.rrw_set) { 1934 lcprevst = getprevsolidtoken(asourcetoken); 1935 if (lcprevst != null) { 1936 if (lcprevst.getAstext().equalsIgnoreCase("a")) { 1937 TSourceToken lcpp = getprevsolidtoken(lcprevst); 1938 if (lcpp != null) { 1939 if ((lcpp.tokencode == TBaseType.rrw_not) || (lcpp.tokencode == TBaseType.rrw_is)) { 1940 lcprevst.tokencode = TBaseType.rrw_oracle_a_in_aset; 1941 asourcetoken.tokencode = TBaseType.rrw_oracle_set_in_aset; 1942 } 1943 } 1944 } 1945 } 1946 } 1947 1948 break; 1949 } 1950 } 1951 1952 // Get next token 1953 asourcetoken = getanewsourcetoken(); 1954 if (asourcetoken != null) { 1955 yychar = asourcetoken.tokencode; 1956 1957 // Handle special case: dot after SQL*Plus commands 1958 if ((asourcetoken.tokencode == '.') && (getprevsolidtoken(asourcetoken) != null) 1959 && ((currentCmdType == ESqlPlusCmd.spcAppend) 1960 || (currentCmdType == ESqlPlusCmd.spcChange) || (currentCmdType == ESqlPlusCmd.spcInput) 1961 || (currentCmdType == ESqlPlusCmd.spcList) || (currentCmdType == ESqlPlusCmd.spcRun))) { 1962 // a.ent_rp_usr_id is not a real sqlplus command 1963 TSourceToken lcprevst2 = getprevsolidtoken(asourcetoken); 1964 lcprevst2.insqlpluscmd = false; 1965 if (lcprevst2.prevTokenCode != 0) { 1966 lcprevst2.tokencode = lcprevst2.prevTokenCode; 1967 } else { 1968 lcprevst2.tokencode = TBaseType.ident; 1969 } 1970 1971 flexer.insqlpluscmd = false; 1972 continuesqlplusatnewline = false; 1973 waitingreturnforsemicolon = false; 1974 waitingreturnforfloatdiv = false; 1975 isvalidplace = false; 1976 insqlpluscmd = false; 1977 } 1978 } else { 1979 yychar = 0; 1980 1981 if (waitingreturnforfloatdiv) { 1982 // / at the end of line treat as sqlplus command 1983 lct.tokencode = TBaseType.sqlpluscmd; 1984 if (lct.tokentype != ETokenType.ttslash) { 1985 lct.tokentype = ETokenType.ttsqlpluscmd; 1986 } 1987 } 1988 } 1989 1990 if ((yychar == 0) && (prevst != null)) { 1991 if (prevst.tokencode == TBaseType.rrw_inner) { 1992 prevst.tokencode = TBaseType.ident; 1993 } 1994 } 1995 } 1996 } 1997 1998 // ========== Helper Methods for Tokenization ========== 1999 // These methods support Oracle-specific tokenization logic 2000 2001 /** 2002 * Count number of newlines in a string. 2003 * 2004 * @param s string to analyze 2005 * @return number of line breaks (LF or CR) 2006 */ 2007 private int countLines(String s) { 2008 int pos = 0, lf = 0, cr = 0; 2009 2010 while (pos < s.length()) { 2011 if (s.charAt(pos) == '\r') { 2012 cr++; 2013 pos++; 2014 continue; 2015 } 2016 if (s.charAt(pos) == '\n') { 2017 lf++; 2018 pos++; 2019 continue; 2020 } 2021 2022 if (s.charAt(pos) == ' ') { 2023 pos++; 2024 continue; 2025 } 2026 break; 2027 } 2028 2029 if (lf >= cr) return lf; 2030 else return cr; 2031 } 2032 2033 /** 2034 * Check if return token ends with space or tab. 2035 * 2036 * @param s token text 2037 * @return true if ends with space/tab 2038 */ 2039 private boolean spaceAtTheEndOfReturnToken(String s) { 2040 if (s == null) return false; 2041 if (s.length() == 0) return false; 2042 2043 return ((s.charAt(s.length() - 1) == ' ') || (s.charAt(s.length() - 1) == '\t')); 2044 } 2045 2046 /** 2047 * Determine if forward slash should be treated as SQL*Plus command delimiter. 2048 * <p> 2049 * Oracle uses '/' as both division operator and SQL*Plus block delimiter. 2050 * This method disambiguates by checking if the '/' appears at the beginning 2051 * of a line (after a return token without trailing whitespace). 2052 * 2053 * @param pstlist token list 2054 * @param pPos position of '/' token 2055 * @return true if '/' should be SQL*Plus command 2056 */ 2057 private boolean isValidPlaceForDivToSqlplusCmd(TSourceTokenList pstlist, int pPos) { 2058 boolean ret = false; 2059 2060 if ((pPos <= 0) || (pPos > pstlist.size() - 1)) return ret; 2061 2062 // Token directly before div must be ttreturn without space appending it 2063 gudusoft.gsqlparser.TSourceToken lcst = pstlist.get(pPos - 1); 2064 if (lcst.tokentype != gudusoft.gsqlparser.ETokenType.ttreturn) { 2065 return ret; 2066 } 2067 2068 if (!(lcst.getAstext().charAt(lcst.getAstext().length() - 1) == ' ')) { 2069 ret = true; 2070 } 2071 2072 return ret; 2073 } 2074 2075 /** 2076 * Get previous non-whitespace token. 2077 * 2078 * @param ptoken current token 2079 * @return previous solid token, or null 2080 */ 2081 private gudusoft.gsqlparser.TSourceToken getprevsolidtoken(gudusoft.gsqlparser.TSourceToken ptoken) { 2082 gudusoft.gsqlparser.TSourceToken ret = null; 2083 TSourceTokenList lctokenlist = ptoken.container; 2084 2085 if (lctokenlist != null) { 2086 if ((ptoken.posinlist > 0) && (lctokenlist.size() > ptoken.posinlist - 1)) { 2087 if (!( 2088 (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttwhitespace) 2089 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttreturn) 2090 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttsimplecomment) 2091 || (lctokenlist.get(ptoken.posinlist - 1).tokentype == gudusoft.gsqlparser.ETokenType.ttbracketedcomment) 2092 )) { 2093 ret = lctokenlist.get(ptoken.posinlist - 1); 2094 } else { 2095 ret = lctokenlist.nextsolidtoken(ptoken.posinlist - 1, -1, false); 2096 } 2097 } 2098 } 2099 return ret; 2100 } 2101}