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.TLexerInformix; 009import gudusoft.gsqlparser.TParserInformix; 010import gudusoft.gsqlparser.TSourceToken; 011import gudusoft.gsqlparser.TSourceTokenList; 012import gudusoft.gsqlparser.TStatementList; 013import gudusoft.gsqlparser.TSyntaxError; 014import gudusoft.gsqlparser.EFindSqlStateType; 015import gudusoft.gsqlparser.ETokenType; 016import gudusoft.gsqlparser.ETokenStatus; 017import gudusoft.gsqlparser.ESqlStatementType; 018import gudusoft.gsqlparser.EErrorType; 019import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 020import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock; 021import gudusoft.gsqlparser.stmt.mssql.TMssqlExecute; 022import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 023import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 024import gudusoft.gsqlparser.compiler.TContext; 025import gudusoft.gsqlparser.sqlenv.TSQLEnv; 026import gudusoft.gsqlparser.compiler.TGlobalScope; 027import gudusoft.gsqlparser.compiler.TFrame; 028import gudusoft.gsqlparser.resolver.TSQLResolver; 029import gudusoft.gsqlparser.TLog; 030import gudusoft.gsqlparser.compiler.TASTEvaluator; 031 032import java.io.BufferedReader; 033import java.util.ArrayList; 034import java.util.List; 035import java.util.Stack; 036 037/** 038 * IBM Informix SQL parser implementation. 039 * 040 * <p>This parser handles Informix-specific SQL syntax including: 041 * <ul> 042 * <li>Informix stored procedures and functions</li> 043 * <li>Informix triggers</li> 044 * <li>Informix execute statements</li> 045 * <li>Special token handling (CONNECT TO, LOCK TABLE, etc.)</li> 046 * </ul> 047 * 048 * <p><b>Design Notes:</b> 049 * <ul> 050 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 051 * <li>Uses {@link TLexerInformix} for tokenization</li> 052 * <li>Uses {@link TParserInformix} for parsing</li> 053 * <li>Delimiter character: ';' for SQL statements</li> 054 * </ul> 055 * 056 * <p><b>Usage Example:</b> 057 * <pre> 058 * // Get Informix parser from factory 059 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvinformix); 060 * 061 * // Build context 062 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvinformix) 063 * .sqlText("SELECT * FROM customer WHERE customer_num = 101") 064 * .build(); 065 * 066 * // Parse 067 * SqlParseResult result = parser.parse(context); 068 * 069 * // Access statements 070 * TStatementList statements = result.getSqlStatements(); 071 * </pre> 072 * 073 * @see SqlParser 074 * @see AbstractSqlParser 075 * @see TLexerInformix 076 * @see TParserInformix 077 * @since 3.2.0.0 078 */ 079public class InformixSqlParser extends AbstractSqlParser { 080 081 /** 082 * Construct Informix SQL parser. 083 * <p> 084 * Configures the parser for Informix database with default delimiter (;). 085 * <p> 086 * Following the original TGSqlParser pattern, the lexer and parser are 087 * created once in the constructor and reused for all parsing operations. 088 */ 089 public InformixSqlParser() { 090 super(EDbVendor.dbvinformix); 091 this.delimiterChar = ';'; 092 this.defaultDelimiterStr = ";"; 093 094 // Create lexer once - will be reused for all parsing operations 095 this.flexer = new TLexerInformix(); 096 this.flexer.delimiterchar = this.delimiterChar; 097 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 098 099 // Set parent's lexer reference for shared tokenization logic 100 this.lexer = this.flexer; 101 102 // Create parser once - will be reused for all parsing operations 103 this.fparser = new TParserInformix(null); 104 this.fparser.lexer = this.flexer; 105 } 106 107 // ========== Parser Components ========== 108 109 /** The Informix lexer used for tokenization */ 110 public TLexerInformix flexer; 111 112 /** Informix SQL parser */ 113 private TParserInformix fparser; 114 115 /** Current statement being built during extraction */ 116 private TCustomSqlStatement gcurrentsqlstatement; 117 118 // Note: Global context and frame stack fields inherited from AbstractSqlParser: 119 // - protected TContext globalContext 120 // - protected TSQLEnv sqlEnv 121 // - protected Stack<TFrame> frameStack 122 // - protected TFrame globalFrame 123 124 // ========== AbstractSqlParser Abstract Methods Implementation ========== 125 126 /** 127 * Return the Informix lexer instance. 128 */ 129 @Override 130 protected TCustomLexer getLexer(ParserContext context) { 131 return this.flexer; 132 } 133 134 /** 135 * Return the Informix SQL parser instance with updated token list. 136 */ 137 @Override 138 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 139 this.fparser.sourcetokenlist = tokens; 140 return this.fparser; 141 } 142 143 /** 144 * Call Informix-specific tokenization logic. 145 * <p> 146 * Delegates to doinformixtexttotokenlist which handles Informix's 147 * special token transformations and statement recognition. 148 */ 149 @Override 150 protected void tokenizeVendorSql() { 151 doinformixtexttotokenlist(); 152 } 153 154 /** 155 * Setup parsers for raw statement extraction. 156 * <p> 157 * Informix uses a single parser (no secondary parser like Oracle). 158 */ 159 @Override 160 protected void setupVendorParsersForExtraction() { 161 this.fparser.sqlcmds = this.sqlcmds; 162 this.fparser.sourcetokenlist = this.sourcetokenlist; 163 } 164 165 /** 166 * Call Informix-specific raw statement extraction logic. 167 * <p> 168 * Delegates to doinformixgetrawsqlstatements which handles Informix's 169 * statement boundary detection and stored procedure recognition. 170 */ 171 @Override 172 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 173 doinformixgetrawsqlstatements(builder); 174 } 175 176 /** 177 * No secondary parser for Informix (only Oracle has dual parsers). 178 */ 179 @Override 180 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 181 return null; 182 } 183 184 // ========== Informix-Specific Tokenization ========== 185 186 /** 187 * Tokenize Informix SQL text to token list. 188 * <p> 189 * Migrated from TGSqlParser.doinformixtexttotokenlist(). 190 * <p> 191 * Handles Informix-specific token transformations: 192 * <ul> 193 * <li>OPENROWSET special handling</li> 194 * <li>Keyword token adjustments (PRIMARY, FOREIGN, UNIQUE, DISTINCT)</li> 195 * <li>Semicolon type detection (after BEGIN, in OPENROWSET)</li> 196 * <li>GO command recognition</li> 197 * <li>LOCK TABLE handling</li> 198 * <li>CONNECT TO statement recognition</li> 199 * </ul> 200 */ 201 private void doinformixtexttotokenlist() { 202 TSourceToken lcprevtoken = null; 203 int lcsteps = 0; 204 TSourceToken asourcetoken, lctoken, lctoken2, lctoken3; 205 int yychar; 206 boolean iskeywordgo; 207 208 asourcetoken = getanewsourcetoken(); 209 210 if (asourcetoken == null) return; 211 212 yychar = asourcetoken.tokencode; 213 214 boolean lcinopenrowset = false; 215 int lcnested = 0; 216 217 while (yychar > 0) { 218 219 if (asourcetoken.tokencode == TBaseType.rrw_openrowset) { 220 // openrowset(....) 221 lcinopenrowset = true; 222 lcnested = 0; 223 } else if (asourcetoken.tokentype == ETokenType.ttleftparenthesis) { 224 if ((lcsteps > 0) && TBaseType.assigned(lcprevtoken)) { 225 if (lcprevtoken.tokencode == TBaseType.rrw_primary) { 226 lcprevtoken.tokencode = TBaseType.rrw_select - 2; //rw_primary2 227 //checkconstarinttoken(lcprevtoken); 228 } else if (lcprevtoken.tokencode == TBaseType.rrw_foreign) { 229 lcprevtoken.tokencode = TBaseType.rrw_select - 4; //rw_foreign2 230 //checkconstarinttoken(lcprevtoken); 231 } else if (lcprevtoken.tokencode == TBaseType.rrw_unique) { 232 lcprevtoken.tokencode = TBaseType.rrw_select - 1; //rw_unique2 233 // checkconstarinttoken(lcprevtoken); 234 } else if (lcprevtoken.tokencode == TBaseType.rrw_distinct) { 235 lcprevtoken.tokencode = TBaseType.rrw_select - 6; //rw_distinct2 236 //checkconstarinttoken(lcprevtoken); 237 } 238 lcprevtoken = null; 239 lcsteps = 0; 240 } 241 242 // openrowset(....) 243 if (lcinopenrowset) 244 lcnested++; 245 } else if (asourcetoken.tokentype == ETokenType.ttrightparenthesis) { 246 // openrowset(....) 247 if (lcinopenrowset) { 248 if ((lcnested > 0)) 249 lcnested--; 250 if (lcnested == 0) 251 lcinopenrowset = false; 252 } 253 } else if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 254 if (lcinopenrowset) { 255 asourcetoken.tokentype = ETokenType.ttsemicolon2; 256 } else { 257 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_begin, -1); 258 if (lctoken2 != null) { 259 asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN; 260 asourcetoken.tokentype = ETokenType.ttsemicolon3; 261 } else { 262 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_try, -1); 263 if (lctoken2 == null) { 264 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_catch, -1); 265 } 266 if (lctoken2 != null) { 267 lctoken3 = asourcetoken.searchToken(TBaseType.rrw_begin, -2); 268 if (lctoken3 != null) { 269 asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN; 270 asourcetoken.tokentype = ETokenType.ttsemicolon3; 271 } 272 } 273 } 274 } 275 } else if (asourcetoken.tokentype == ETokenType.ttperiod) { 276 lctoken = getprevtoken(asourcetoken); 277 // System.out.println(lctoken); 278 if (TBaseType.assigned(lctoken)) { 279 // go.fieldname, go is a table alias, not a go statement 280 if (lctoken.tokencode == TBaseType.rrw_go) { 281 lctoken.tokencode = TBaseType.ident; 282 lctoken.tokentype = ETokenType.ttidentifier; 283 } 284 } 285 } else if (asourcetoken.tokencode == TBaseType.rrw_table) { 286 lctoken = getprevtoken(asourcetoken); 287 if (TBaseType.assigned(lctoken)) { 288 if (lctoken.tokencode == TBaseType.rrw_lock) { 289 lctoken.tokencode = TBaseType.rw_locktable; //TBaseType.rw_locktable 290 } 291 } 292 } else if (asourcetoken.tokencode == TBaseType.rrw_to) { 293 lctoken = getprevtoken(asourcetoken); 294 if (TBaseType.assigned(lctoken)) { 295 if (lctoken.tokencode == TBaseType.rrw_connect) { 296 lctoken.tokencode = TBaseType.rrw_informix_connect_to;//connect to statement 297 } 298 } 299 } else if (asourcetoken.tokencode == TBaseType.rrw_go) { 300 iskeywordgo = true; 301 lctoken = getprevtoken(asourcetoken); 302 if (TBaseType.assigned(lctoken)) { 303 // go should not at same line as other sql statement 304 if (lctoken.lineNo == asourcetoken.lineNo) { 305 iskeywordgo = false; 306 } 307 } 308 309 if (iskeywordgo) { 310 lcinopenrowset = false; 311 lcnested = 0; 312 lcprevtoken = asourcetoken; 313 } else { 314 // System.out.println(asourcetoken); 315 asourcetoken.tokencode = TBaseType.ident; 316 asourcetoken.tokentype = ETokenType.ttidentifier; 317 } 318 } else if (asourcetoken.tokencode == TBaseType.rrw_primary) { 319 // primary key [clustered | nonclustered ] ( column list) 320 lcsteps = 2; 321 lcprevtoken = asourcetoken; 322 } else if (asourcetoken.tokencode == TBaseType.rrw_foreign) { 323 // foreign key [clustered | nonclustered ] ( column list) 324 lcsteps = 2; 325 lcprevtoken = asourcetoken; 326 } else if (asourcetoken.tokencode == TBaseType.rrw_unique) { 327 // unique [clustered | nonclustered ] ( column list) 328 lcsteps = 1; 329 lcprevtoken = asourcetoken; 330 } else if (asourcetoken.issolidtoken()) { 331 if (lcsteps > 0) { 332 if (!(TBaseType.mysametext("clustered", asourcetoken.toString()) 333 || TBaseType.mysametext("nonclustered", asourcetoken.toString()))) 334 lcsteps--; 335 } 336 } 337 338 //System.out.println(asourcetoken); 339 sourcetokenlist.add(asourcetoken); 340 341 //flexer.yylexwrap(asourcetoken); 342 asourcetoken = getanewsourcetoken(); 343 if (asourcetoken != null) { 344 yychar = asourcetoken.tokencode; 345 } else { 346 yychar = 0; 347 } 348 349 } 350 351 } 352 353 /** 354 * Get previous solid token (non-whitespace, non-comment). 355 * <p> 356 * Helper method migrated from TGSqlParser for token navigation. 357 */ 358 private TSourceToken getprevtoken(TSourceToken ast) { 359 int i = ast.posinlist; 360 TSourceToken lctoken; 361 362 for (int j = i - 1; j >= 0; j--) { 363 lctoken = sourcetokenlist.get(j); 364 if (lctoken.issolidtoken()) 365 return lctoken; 366 } 367 return null; 368 } 369 370 // ========== Informix-Specific Raw Statement Extraction ========== 371 372 /** 373 * Extract raw SQL statements for Informix. 374 * <p> 375 * Migrated from TGSqlParser.doinformixgetrawsqlstatements(). 376 * <p> 377 * Handles Informix-specific statement boundaries and nested constructs: 378 * <ul> 379 * <li>Stored procedures (CREATE/ALTER PROCEDURE, FUNCTION)</li> 380 * <li>Triggers</li> 381 * <li>EXECUTE statements</li> 382 * <li>BEGIN/END blocks</li> 383 * <li>TRY/CATCH blocks (MSSQL compatibility)</li> 384 * <li>GO command (batch separator)</li> 385 * </ul> 386 */ 387 private int doinformixgetrawsqlstatements(SqlParseResult.Builder builder) { 388 int errorcount = 0; 389 int case_end_nest = 0; 390 391 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 392 if (!TBaseType.assigned(sourcetokenlist)) return -1; 393 394 gcurrentsqlstatement = null; 395 EFindSqlStateType gst = EFindSqlStateType.stnormal; 396 int lcblocklevel = 0; 397 int lctrycatchlevel = 0; 398 TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken; 399 TSourceToken ast = null; 400 int i; 401 boolean lcisendconversation, lcstillinsql; 402 403 for (i = 0; i < sourcetokenlist.size(); i++) { 404 405 if ((ast != null) && (ast.issolidtoken())) 406 lcprevsolidtoken = ast; 407 408 ast = sourcetokenlist.get(i); 409 sourcetokenlist.curpos = i; 410 if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) { 411 //tsignoredbygetrawstatement is set when cte is found in dbcmds.findcte function 412 gcurrentsqlstatement.sourcetokenlist.add(ast); 413 continue; 414 } 415 416 if (gst == EFindSqlStateType.ststoredprocedurebody) { 417 if (!( 418 (ast.tokencode == TBaseType.rrw_go) 419 || (ast.tokencode == TBaseType.rrw_create) 420 || (ast.tokencode == TBaseType.rrw_alter)) 421 ) { 422 gcurrentsqlstatement.sourcetokenlist.add(ast); 423 continue; 424 } 425 } 426 427 TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 428 429 switch (gst) { 430 case sterror: { 431 if (TBaseType.assigned(lcnextsqlstatement)) { 432 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 433 gcurrentsqlstatement = lcnextsqlstatement; 434 gcurrentsqlstatement.sourcetokenlist.add(ast); 435 gst = EFindSqlStateType.stsql; 436 } else if ((ast.tokentype == ETokenType.ttsemicolon)) { 437 gcurrentsqlstatement.sourcetokenlist.add(ast); 438 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 439 gst = EFindSqlStateType.stnormal; 440 } else { 441 gcurrentsqlstatement.sourcetokenlist.add(ast); 442 } 443 break; 444 } 445 case stnormal: { 446 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 447 || (ast.tokencode == TBaseType.cmtslashstar) 448 || (ast.tokencode == TBaseType.lexspace) 449 || (ast.tokencode == TBaseType.lexnewline) 450 || (ast.tokentype == ETokenType.ttsemicolon)) { 451 if (TBaseType.assigned(gcurrentsqlstatement)) { 452 gcurrentsqlstatement.sourcetokenlist.add(ast); 453 } 454 455 if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) { 456 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 457 // ;;;; continuous semicolon,treat it as comment 458 ast.tokentype = ETokenType.ttsimplecomment; 459 ast.tokencode = TBaseType.cmtdoublehyphen; 460 } else { 461 } 462 463 } 464 465 continue; 466 } 467 468 gcurrentsqlstatement = lcnextsqlstatement; //isstoredprocedure(ast,dbvendor,gst,sqlstatements); 469 470 if (TBaseType.assigned(gcurrentsqlstatement)) { 471 switch (gcurrentsqlstatement.sqlstatementtype) { // 472 case sstinformixCreateProcedure: 473 case sstinformixCreateFunction: 474 case sstcreatetrigger: 475 case sstinformixAlterProcedure: 476 case sstinformixAlterFunction: { 477 gcurrentsqlstatement.sourcetokenlist.add(ast); 478 gst = EFindSqlStateType.ststoredprocedure; 479 break; 480 } 481 case sstmssqlbegintry: 482 case sstmssqlbegincatch: { 483 gcurrentsqlstatement.sourcetokenlist.add(ast); 484 gst = EFindSqlStateType.sttrycatch; 485 lctrycatchlevel = 0; 486 break; 487 } 488 case sstmssqlgo: { 489 gcurrentsqlstatement.sourcetokenlist.add(ast); 490 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 491 gst = EFindSqlStateType.stnormal; 492 break; 493 } 494 case sstinformixExecute: { 495 // the EXECUTE keyword opens the statement, as in every other case 496 gcurrentsqlstatement.sourcetokenlist.add(ast); 497 gst = EFindSqlStateType.stExec; 498 break; 499 } 500 default: { 501 gcurrentsqlstatement.sourcetokenlist.add(ast); 502 gst = EFindSqlStateType.stsql; 503 break; 504 } 505 } // case 506 } else { 507 if (ast.tokencode == TBaseType.rrw_begin) { 508 gcurrentsqlstatement = new TMssqlBlock(vendor); 509 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 510 gcurrentsqlstatement.sourcetokenlist.add(ast); 511 gst = EFindSqlStateType.stblock; 512 } else { 513 if (sqlstatements.size() == 0) { 514 //first statement of mssql batch, treat it as exec sp 515 gst = EFindSqlStateType.stsql; 516 gcurrentsqlstatement = new TMssqlExecute(vendor); 517 //tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 518// todo need previous line need to be implemented 519 gcurrentsqlstatement.sourcetokenlist.add(ast); 520 } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) { 521 // prev sql is go, treat it as exec sp 522 gst = EFindSqlStateType.stsql; 523 gcurrentsqlstatement = new TMssqlExecute(vendor); 524 //todo need to be implemented: tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 525 gcurrentsqlstatement.sourcetokenlist.add(ast); 526 } else { 527 } 528 } 529 } 530 531 532 if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found 533 { 534 535// if ( TBaseType.assigned(ontokenlizertokenerror) ) 536// ontokenlizertokenerror(self,ast); 537// incerrorcount; 538// fsyntaxerrors[ferrorcount].hint = 'error when tokenlize'; 539// fsyntaxerrors[ferrorcount].errortype = spwarning; 540// fsyntaxerrors[ferrorcount].tokentext = ast.sourcecode; 541// fsyntaxerrors[ferrorcount].lines = ast.lines; 542// fsyntaxerrors[ferrorcount].columns = ast.columns;// - length(fsyntaxerrors[ferrorcount].tokentext); 543 544// errormessage = "error when tokenlize:"+ast.astext+"("+ast.lineNo +","+ast.columnNo +")"; 545// errorcount = 1; 546 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 547 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 548 549 ast.tokentype = ETokenType.tttokenlizererrortoken; 550 gst = EFindSqlStateType.sterror; 551 552 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 553 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 554 gcurrentsqlstatement.sourcetokenlist.add(ast); 555 } 556 break; 557 } 558 case stExec: { 559 gcurrentsqlstatement.sourcetokenlist.add(ast); 560 if (ast.tokentype == ETokenType.ttsemicolon) { 561 gst = EFindSqlStateType.stnormal; 562 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 563 } 564 break; 565 } 566 case stblock: { 567 if (TBaseType.assigned(lcnextsqlstatement)) { 568 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 569 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 570 gcurrentsqlstatement = lcnextsqlstatement; 571 gcurrentsqlstatement.sourcetokenlist.add(ast); 572 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 573 gst = EFindSqlStateType.stnormal; 574 } else { 575 lcnextsqlstatement = null; 576 } 577 } 578 579 if (gst == EFindSqlStateType.stblock) { 580 gcurrentsqlstatement.sourcetokenlist.add(ast); 581 if (ast.tokencode == TBaseType.rrw_begin) { 582 // { [distributed] transaxtion/trans statement 583 // { dialog [ conversation ] 584 // { conversation timer 585 // doesn't start block ({ .. }) 586 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 587 if (TBaseType.assigned(lcnextsolidtoken)) { 588 if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran") 589 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction") 590 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "distributed") 591 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "dialog") 592 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "conversation") 593 )) 594 lcblocklevel++; 595 } else 596 lcblocklevel++; 597 598 } else if (ast.tokencode == TBaseType.rrw_case) // case ... } 599 lcblocklevel++; 600 else if (ast.tokencode == TBaseType.rrw_end) { 601 602 lcisendconversation = false; 603 604 605 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 606 if (TBaseType.assigned(lcnextsolidtoken)) { 607 if (lcnextsolidtoken.tokencode == flexer.getkeywordvalue("conversation".toUpperCase(java.util.Locale.ROOT))) 608 lcisendconversation = true; // } conversation statement 609 } 610 611 612 if (!lcisendconversation) { 613 614 if (lcblocklevel == 0) { 615 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 616 if (TBaseType.assigned(lcnextsolidtoken)) { 617 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 618 // { .. } else 619 gst = EFindSqlStateType.stsql; 620 } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) { 621 lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false); 622 if (TBaseType.assigned(lcnnextsolidtoken)) { 623 if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) { 624 // { .. } else 625 gst = EFindSqlStateType.stsql; 626 } 627 } 628 } 629 } 630 } 631 632 if (gst != EFindSqlStateType.stsql) { 633 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 634 gst = EFindSqlStateType.stnormal; 635 } 636 637 } else { 638 lcblocklevel--; 639 } 640 641 } 642 } 643 } 644 break; 645 } 646 case sttrycatch: { 647 648 if (TBaseType.assigned(lcnextsqlstatement)) { 649 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 650 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 651 gcurrentsqlstatement = lcnextsqlstatement; 652 gcurrentsqlstatement.sourcetokenlist.add(ast); 653 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 654 gst = EFindSqlStateType.stnormal; 655 } else { 656 if ( 657 (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry) 658 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 659 ) 660 lctrycatchlevel++; 661 lcnextsqlstatement = null; 662 } 663 } 664 665 if (gst == EFindSqlStateType.sttrycatch) { 666 gcurrentsqlstatement.sourcetokenlist.add(ast); 667 if ((ast.tokencode == TBaseType.rrw_try) || 668 (ast.tokencode == TBaseType.rrw_catch)) { 669 // lcprevsolidtoken = sourcetokenlist.solidtokenbefore(i); 670 if (TBaseType.assigned(lcprevsolidtoken)) { 671 if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) { 672 if (lctrycatchlevel == 0) { 673 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 674 gst = EFindSqlStateType.stnormal; 675 } else 676 lctrycatchlevel--; 677 } 678 } 679 } 680 } 681 break; 682 } 683 case stsql: { 684 if ((ast.tokentype == ETokenType.ttsemicolon)) { 685 lcstillinsql = false; 686 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 687 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 688 if (TBaseType.assigned(lcnextsolidtoken)) { 689 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 690 // if ( expr stmt; else 691 gcurrentsqlstatement.sourcetokenlist.add(ast); 692 lcstillinsql = true; 693 } 694 695 } 696 } 697 698 if (!lcstillinsql) { 699 gst = EFindSqlStateType.stnormal; 700 gcurrentsqlstatement.sourcetokenlist.add(ast); 701 gcurrentsqlstatement.semicolonended = ast; 702 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 703 } 704 705 } else if (TBaseType.assigned(lcnextsqlstatement)) { 706 707 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 708 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 709 gcurrentsqlstatement = lcnextsqlstatement; 710 gcurrentsqlstatement.sourcetokenlist.add(ast); 711 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 712 gst = EFindSqlStateType.stnormal; 713 continue; 714 } 715 716 switch (gcurrentsqlstatement.sqlstatementtype) { // 717 case sstmssqlif: 718 case sstmssqlwhile: { 719 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 720 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) { 721 gcurrentsqlstatement.sourcetokenlist.add(ast); 722 gst = EFindSqlStateType.stblock; 723 lcblocklevel = 1; 724 lcnextsqlstatement = null; 725 continue; 726 727 } 728 // if ( || while only contain one sql(not closed by {/} pair) 729 // so will still in if ( || while statement 730 else if (gcurrentsqlstatement.dummytag == 1) { 731 // if ( cond ^stmt nextstmt (^ stands for current pos) 732 gcurrentsqlstatement.sourcetokenlist.add(ast); 733 734 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 735 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 736 ) 737 gcurrentsqlstatement.dummytag = 1; 738 else 739 gcurrentsqlstatement.dummytag = 0; 740 741 742 lcnextsqlstatement = null; 743 continue; 744 } 745 break; 746 }// 747 case sstmssqlalterqueue: { 748 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) { 749 // execute can't be used to delimite alter queue 750 gcurrentsqlstatement.sourcetokenlist.add(ast); 751 752 753 lcnextsqlstatement = null; 754 continue; 755 756 } 757 break; 758 } 759 case sstmssqlcreateschema: { 760 gcurrentsqlstatement.sourcetokenlist.add(ast); 761 lcnextsqlstatement = null; 762 continue; 763 } 764 case sstinformixGrant: 765 case sstinformixGrantFragment: 766 case sstinformixRevoke: 767 case sstinformixRevokeFragment: 768 case sstinformixSetIsolation: 769 case sstinformixSetEnvironment: { 770 // Privilege lists (GRANT/REVOKE SELECT, INSERT, 771 // UPDATE, DELETE, INDEX, ALTER ...) and isolation 772 // options (RETAIN UPDATE LOCKS) contain statement- 773 // starting keywords that are part of the current 774 // statement; never split before the terminating 775 // semicolon. 776 gcurrentsqlstatement.sourcetokenlist.add(ast); 777 lcnextsqlstatement = null; 778 continue; 779 } 780 }//case 781 782 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 783 gcurrentsqlstatement = lcnextsqlstatement; 784 gcurrentsqlstatement.sourcetokenlist.add(ast); 785 786 switch (gcurrentsqlstatement.sqlstatementtype) { // 787 case sstinformixCreateProcedure: 788 case sstinformixCreateFunction: 789 case sstcreatetrigger: 790 case sstinformixAlterProcedure: 791 case sstinformixAlterFunction: { 792 gst = EFindSqlStateType.ststoredprocedure; 793 break; 794 } 795 case sstmssqlbegintry: 796 case sstmssqlbegincatch: { 797 gst = EFindSqlStateType.sttrycatch; 798 lctrycatchlevel = 0; 799 break; 800 } 801 case sstmssqlgo: { 802 gst = EFindSqlStateType.stnormal; 803 break; 804 } 805 default: { 806 gst = EFindSqlStateType.stsql; 807 break; 808 } 809 } // case 810 811 }//TBaseType.assigned(lcnextsqlstatement) 812 else if ((ast.tokencode == TBaseType.rrw_begin)) { 813 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 814 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 815 ) { 816 // start block of if ( || while statement 817 gst = EFindSqlStateType.stblock; 818 lcblocklevel = 0; 819 gcurrentsqlstatement.sourcetokenlist.add(ast); 820 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqldeclare) { 821 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 822 gcurrentsqlstatement = new TMssqlBlock(vendor); 823 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 824 gcurrentsqlstatement.sourcetokenlist.add(ast); 825 gst = EFindSqlStateType.stblock; 826 } else { 827 gcurrentsqlstatement.sourcetokenlist.add(ast); 828 } 829 } else if ((ast.tokencode == TBaseType.rrw_case)) { 830 case_end_nest++; 831 gcurrentsqlstatement.sourcetokenlist.add(ast); 832 } else if ((ast.tokencode == TBaseType.rrw_end)) { 833 if (case_end_nest > 0) { 834 case_end_nest--; 835 } 836 gcurrentsqlstatement.sourcetokenlist.add(ast); 837 } else if ((ast.tokencode == TBaseType.rrw_else)) { 838 gcurrentsqlstatement.sourcetokenlist.add(ast); 839 // if ( cond stmt ^else stmt 840 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 841 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 842 ) && (case_end_nest == 0)) 843 gcurrentsqlstatement.dummytag = 1; // reduce to 1 while stmt after else is found: if ( cond stmt ^else stmt 844 } else { 845 gcurrentsqlstatement.sourcetokenlist.add(ast); 846 } 847 break; 848 } 849 case ststoredprocedure: { 850 if (TBaseType.assigned(lcnextsqlstatement)) { 851 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 852 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 853 gcurrentsqlstatement = lcnextsqlstatement; 854 gcurrentsqlstatement.sourcetokenlist.add(ast); 855 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 856 gst = EFindSqlStateType.stnormal; 857 } else { 858 gst = EFindSqlStateType.ststoredprocedurebody; 859 gcurrentsqlstatement.sourcetokenlist.add(ast); 860 861 lcnextsqlstatement = null; 862 } 863 } 864 865 if (gst == EFindSqlStateType.ststoredprocedure) { 866 gcurrentsqlstatement.sourcetokenlist.add(ast); 867 if ((ast.tokencode == TBaseType.rrw_procedure) 868 || (ast.tokencode == TBaseType.rrw_function)) { 869 if (ast.searchToken(TBaseType.rrw_end, -1) != null) 870 gst = EFindSqlStateType.stsql; 871 } 872 } 873 break; 874 } //stplsql 875 case ststoredprocedurebody: { 876 if (TBaseType.assigned(lcnextsqlstatement)) { 877 switch (lcnextsqlstatement.sqlstatementtype) { // 878 case sstmssqlgo: { 879 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 880 gcurrentsqlstatement = lcnextsqlstatement; 881 gcurrentsqlstatement.sourcetokenlist.add(ast); 882 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 883 gst = EFindSqlStateType.stnormal; 884 break; 885 } 886 case sstinformixCreateProcedure: 887 case sstinformixCreateFunction: 888 case sstcreatetrigger: 889 case sstinformixAlterProcedure: 890 case sstinformixAlterFunction: { 891 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 892 gcurrentsqlstatement = lcnextsqlstatement; 893 gcurrentsqlstatement.sourcetokenlist.add(ast); 894 gst = EFindSqlStateType.ststoredprocedure; 895 break; 896 } 897 default: { 898 lcnextsqlstatement = null; 899 break; 900 } 901 }//case 902 } 903 904 if (gst == EFindSqlStateType.ststoredprocedurebody) 905 gcurrentsqlstatement.sourcetokenlist.add(ast); 906 } 907 break; 908 } //case 909 } //for 910 911 912 //last statement 913 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 914 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 915 } 916 917 // Set results in builder 918 builder.sqlStatements(this.sqlstatements); 919 builder.errorCode(errorcount); 920 builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount)); 921 922 return errorcount; 923 924 } 925 926 // ========== Statement Parsing ========== 927 928 /** 929 * Parse all raw statements. 930 * <p> 931 * Migrated from TGSqlParser.doparse() with Informix-specific handling. 932 */ 933 @Override 934 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 935 TCustomParser secondaryParser, TSourceTokenList tokens, 936 TStatementList rawStatements) { 937 // Store references 938 this.fparser = (TParserInformix) parser; 939 this.sourcetokenlist = tokens; 940 this.parserContext = context; 941 942 // Use the raw statements passed from AbstractSqlParser.parse() 943 this.sqlstatements = rawStatements; 944 945 // Initialize statement parsing infrastructure 946 this.sqlcmds = SqlCmdsFactory.get(vendor); 947 948 // CRITICAL: Inject sqlcmds into parser 949 this.fparser.sqlcmds = this.sqlcmds; 950 951 // Initialize global context for semantic analysis 952 initializeGlobalContext(); 953 954 // Parse each statement 955 for (int i = 0; i < sqlstatements.size(); i++) { 956 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 957 958 try { 959 stmt.setFrameStack(frameStack); 960 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 961 962 // Vendor-specific post-processing hook (default: no-op) 963 afterStatementParsed(stmt); 964 965 // Error recovery for CREATE TABLE statements 966 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 967 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 968 handleCreateTableErrorRecovery(stmt); 969 } 970 971 // Collect errors from statement 972 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 973 copyErrorsFromStatement(stmt); 974 } 975 976 } catch (Exception ex) { 977 // Use inherited exception handler 978 handleStatementParsingException(stmt, i, ex); 979 continue; 980 } 981 } 982 983 // Clean up frame stack 984 if (globalFrame != null) { 985 globalFrame.popMeFromStack(frameStack); 986 } 987 988 return this.sqlstatements; 989 } 990 991 /** 992 * Handle CREATE TABLE/INDEX error recovery for Informix. 993 * <p> 994 * Migrated from TGSqlParser.doparse() lines 16914-16971. 995 * <p> 996 * This method implements intelligent error recovery for CREATE TABLE statements 997 * that fail to parse due to vendor-specific table properties or options that 998 * appear after the column definition. 999 * <p> 1000 * <b>Recovery Strategy:</b> 1001 * <ol> 1002 * <li>Find the closing parenthesis ')' of the column definitions</li> 1003 * <li>Mark all tokens after this parenthesis (except semicolon) as sqlpluscmd</li> 1004 * <li>This causes the parser to ignore unknown table properties/options</li> 1005 * <li>Re-parse the statement with ignored tokens</li> 1006 * </ol> 1007 * <p> 1008 * <b>Example:</b> Informix CREATE TABLE with FRAGMENT BY clause that may not 1009 * be fully supported: 1010 * <pre> 1011 * CREATE TABLE customer (id INT, name CHAR(32)) 1012 * FRAGMENT BY EXPRESSION ... -- This gets ignored if it causes parse errors 1013 * </pre> 1014 */ 1015 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 1016 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 1017 || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) 1018 && (!TBaseType.c_createTableStrictParsing)) { 1019 1020 int nested = 0; 1021 boolean isIgnore = false, isFoundIgnoreToken = false; 1022 TSourceToken firstIgnoreToken = null; 1023 1024 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 1025 TSourceToken st = stmt.sourcetokenlist.get(k); 1026 if (isIgnore) { 1027 if (st.issolidtoken() && (st.tokencode != ';')) { 1028 isFoundIgnoreToken = true; 1029 if (firstIgnoreToken == null) { 1030 firstIgnoreToken = st; 1031 } 1032 } 1033 if (st.tokencode != ';') { 1034 st.tokencode = TBaseType.sqlpluscmd; 1035 } 1036 continue; 1037 } 1038 if (st.tokencode == (int) ')') { 1039 nested--; 1040 if (nested == 0) { 1041 // Check if this is CREATE TABLE AS (SELECT ...) 1042 // If so, don't ignore the SELECT part 1043 boolean isSelect = false; 1044 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 1045 if (st1 != null) { 1046 TSourceToken st2 = st.searchToken((int) '(', 2); 1047 if (st2 != null) { 1048 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 1049 isSelect = (st3 != null); 1050 } 1051 } 1052 if (!isSelect) isIgnore = true; 1053 } 1054 } else if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 1055 nested++; 1056 } 1057 } 1058 1059 // If we found tokens to ignore, clear errors and re-parse 1060 if (isFoundIgnoreToken) { 1061 stmt.clearError(); 1062 stmt.parsestatement(null, false); 1063 } 1064 } 1065 } 1066 1067 /** 1068 * Perform semantic analysis using TSQLResolver. 1069 * <p> 1070 * Inherited pattern from AbstractSqlParser with Informix-specific context. 1071 */ 1072 @Override 1073 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 1074 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 1075 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 1076 resolver.resolve(); 1077 } 1078 } 1079 1080 /** 1081 * Perform interpretation (AST evaluation). 1082 * <p> 1083 * Inherited pattern from AbstractSqlParser with Informix-specific context. 1084 */ 1085 @Override 1086 protected void performInterpreter(ParserContext context, TStatementList statements) { 1087 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 1088 TLog.clearLogs(); 1089 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 1090 TLog.enableInterpreterLogOnly(); 1091 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 1092 astEvaluator.eval(); 1093 } 1094 } 1095 1096 @Override 1097 public String toString() { 1098 return "InformixSqlParser{vendor=" + vendor + "}"; 1099 } 1100}