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