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