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 gst = EFindSqlStateType.stExec; 496 break; 497 } 498 default: { 499 gcurrentsqlstatement.sourcetokenlist.add(ast); 500 gst = EFindSqlStateType.stsql; 501 break; 502 } 503 } // case 504 } else { 505 if (ast.tokencode == TBaseType.rrw_begin) { 506 gcurrentsqlstatement = new TMssqlBlock(vendor); 507 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 508 gcurrentsqlstatement.sourcetokenlist.add(ast); 509 gst = EFindSqlStateType.stblock; 510 } else { 511 if (sqlstatements.size() == 0) { 512 //first statement of mssql batch, treat it as exec sp 513 gst = EFindSqlStateType.stsql; 514 gcurrentsqlstatement = new TMssqlExecute(vendor); 515 //tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 516// todo need previous line need to be implemented 517 gcurrentsqlstatement.sourcetokenlist.add(ast); 518 } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) { 519 // prev sql is go, treat it as exec sp 520 gst = EFindSqlStateType.stsql; 521 gcurrentsqlstatement = new TMssqlExecute(vendor); 522 //todo need to be implemented: tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 523 gcurrentsqlstatement.sourcetokenlist.add(ast); 524 } else { 525 } 526 } 527 } 528 529 530 if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found 531 { 532 533// if ( TBaseType.assigned(ontokenlizertokenerror) ) 534// ontokenlizertokenerror(self,ast); 535// incerrorcount; 536// fsyntaxerrors[ferrorcount].hint = 'error when tokenlize'; 537// fsyntaxerrors[ferrorcount].errortype = spwarning; 538// fsyntaxerrors[ferrorcount].tokentext = ast.sourcecode; 539// fsyntaxerrors[ferrorcount].lines = ast.lines; 540// fsyntaxerrors[ferrorcount].columns = ast.columns;// - length(fsyntaxerrors[ferrorcount].tokentext); 541 542// errormessage = "error when tokenlize:"+ast.astext+"("+ast.lineNo +","+ast.columnNo +")"; 543// errorcount = 1; 544 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 545 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 546 547 ast.tokentype = ETokenType.tttokenlizererrortoken; 548 gst = EFindSqlStateType.sterror; 549 550 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 551 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 552 gcurrentsqlstatement.sourcetokenlist.add(ast); 553 } 554 break; 555 } 556 case stExec: { 557 gcurrentsqlstatement.sourcetokenlist.add(ast); 558 if (ast.tokentype == ETokenType.ttsemicolon) { 559 gst = EFindSqlStateType.stnormal; 560 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 561 } 562 break; 563 } 564 case stblock: { 565 if (TBaseType.assigned(lcnextsqlstatement)) { 566 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 567 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 568 gcurrentsqlstatement = lcnextsqlstatement; 569 gcurrentsqlstatement.sourcetokenlist.add(ast); 570 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 571 gst = EFindSqlStateType.stnormal; 572 } else { 573 lcnextsqlstatement = null; 574 } 575 } 576 577 if (gst == EFindSqlStateType.stblock) { 578 gcurrentsqlstatement.sourcetokenlist.add(ast); 579 if (ast.tokencode == TBaseType.rrw_begin) { 580 // { [distributed] transaxtion/trans statement 581 // { dialog [ conversation ] 582 // { conversation timer 583 // doesn't start block ({ .. }) 584 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 585 if (TBaseType.assigned(lcnextsolidtoken)) { 586 if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran") 587 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction") 588 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "distributed") 589 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "dialog") 590 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "conversation") 591 )) 592 lcblocklevel++; 593 } else 594 lcblocklevel++; 595 596 } else if (ast.tokencode == TBaseType.rrw_case) // case ... } 597 lcblocklevel++; 598 else if (ast.tokencode == TBaseType.rrw_end) { 599 600 lcisendconversation = false; 601 602 603 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 604 if (TBaseType.assigned(lcnextsolidtoken)) { 605 if (lcnextsolidtoken.tokencode == flexer.getkeywordvalue("conversation".toUpperCase())) 606 lcisendconversation = true; // } conversation statement 607 } 608 609 610 if (!lcisendconversation) { 611 612 if (lcblocklevel == 0) { 613 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 614 if (TBaseType.assigned(lcnextsolidtoken)) { 615 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 616 // { .. } else 617 gst = EFindSqlStateType.stsql; 618 } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) { 619 lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false); 620 if (TBaseType.assigned(lcnnextsolidtoken)) { 621 if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) { 622 // { .. } else 623 gst = EFindSqlStateType.stsql; 624 } 625 } 626 } 627 } 628 } 629 630 if (gst != EFindSqlStateType.stsql) { 631 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 632 gst = EFindSqlStateType.stnormal; 633 } 634 635 } else { 636 lcblocklevel--; 637 } 638 639 } 640 } 641 } 642 break; 643 } 644 case sttrycatch: { 645 646 if (TBaseType.assigned(lcnextsqlstatement)) { 647 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 648 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 649 gcurrentsqlstatement = lcnextsqlstatement; 650 gcurrentsqlstatement.sourcetokenlist.add(ast); 651 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 652 gst = EFindSqlStateType.stnormal; 653 } else { 654 if ( 655 (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry) 656 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 657 ) 658 lctrycatchlevel++; 659 lcnextsqlstatement = null; 660 } 661 } 662 663 if (gst == EFindSqlStateType.sttrycatch) { 664 gcurrentsqlstatement.sourcetokenlist.add(ast); 665 if ((ast.tokencode == TBaseType.rrw_try) || 666 (ast.tokencode == TBaseType.rrw_catch)) { 667 // lcprevsolidtoken = sourcetokenlist.solidtokenbefore(i); 668 if (TBaseType.assigned(lcprevsolidtoken)) { 669 if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) { 670 if (lctrycatchlevel == 0) { 671 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 672 gst = EFindSqlStateType.stnormal; 673 } else 674 lctrycatchlevel--; 675 } 676 } 677 } 678 } 679 break; 680 } 681 case stsql: { 682 if ((ast.tokentype == ETokenType.ttsemicolon)) { 683 lcstillinsql = false; 684 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 685 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 686 if (TBaseType.assigned(lcnextsolidtoken)) { 687 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 688 // if ( expr stmt; else 689 gcurrentsqlstatement.sourcetokenlist.add(ast); 690 lcstillinsql = true; 691 } 692 693 } 694 } 695 696 if (!lcstillinsql) { 697 gst = EFindSqlStateType.stnormal; 698 gcurrentsqlstatement.sourcetokenlist.add(ast); 699 gcurrentsqlstatement.semicolonended = ast; 700 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 701 } 702 703 } else if (TBaseType.assigned(lcnextsqlstatement)) { 704 705 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 706 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 707 gcurrentsqlstatement = lcnextsqlstatement; 708 gcurrentsqlstatement.sourcetokenlist.add(ast); 709 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 710 gst = EFindSqlStateType.stnormal; 711 continue; 712 } 713 714 switch (gcurrentsqlstatement.sqlstatementtype) { // 715 case sstmssqlif: 716 case sstmssqlwhile: { 717 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 718 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) { 719 gcurrentsqlstatement.sourcetokenlist.add(ast); 720 gst = EFindSqlStateType.stblock; 721 lcblocklevel = 1; 722 lcnextsqlstatement = null; 723 continue; 724 725 } 726 // if ( || while only contain one sql(not closed by {/} pair) 727 // so will still in if ( || while statement 728 else if (gcurrentsqlstatement.dummytag == 1) { 729 // if ( cond ^stmt nextstmt (^ stands for current pos) 730 gcurrentsqlstatement.sourcetokenlist.add(ast); 731 732 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 733 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 734 ) 735 gcurrentsqlstatement.dummytag = 1; 736 else 737 gcurrentsqlstatement.dummytag = 0; 738 739 740 lcnextsqlstatement = null; 741 continue; 742 } 743 break; 744 }// 745 case sstmssqlalterqueue: { 746 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) { 747 // execute can't be used to delimite alter queue 748 gcurrentsqlstatement.sourcetokenlist.add(ast); 749 750 751 lcnextsqlstatement = null; 752 continue; 753 754 } 755 break; 756 } 757 case sstmssqlcreateschema: { 758 gcurrentsqlstatement.sourcetokenlist.add(ast); 759 lcnextsqlstatement = null; 760 continue; 761 } 762 case sstinformixGrant: 763 case sstinformixGrantFragment: 764 case sstinformixRevoke: 765 case sstinformixRevokeFragment: 766 case sstinformixSetIsolation: 767 case sstinformixSetEnvironment: { 768 // Privilege lists (GRANT/REVOKE SELECT, INSERT, 769 // UPDATE, DELETE, INDEX, ALTER ...) and isolation 770 // options (RETAIN UPDATE LOCKS) contain statement- 771 // starting keywords that are part of the current 772 // statement; never split before the terminating 773 // semicolon. 774 gcurrentsqlstatement.sourcetokenlist.add(ast); 775 lcnextsqlstatement = null; 776 continue; 777 } 778 }//case 779 780 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 781 gcurrentsqlstatement = lcnextsqlstatement; 782 gcurrentsqlstatement.sourcetokenlist.add(ast); 783 784 switch (gcurrentsqlstatement.sqlstatementtype) { // 785 case sstinformixCreateProcedure: 786 case sstinformixCreateFunction: 787 case sstcreatetrigger: 788 case sstinformixAlterProcedure: 789 case sstinformixAlterFunction: { 790 gst = EFindSqlStateType.ststoredprocedure; 791 break; 792 } 793 case sstmssqlbegintry: 794 case sstmssqlbegincatch: { 795 gst = EFindSqlStateType.sttrycatch; 796 lctrycatchlevel = 0; 797 break; 798 } 799 case sstmssqlgo: { 800 gst = EFindSqlStateType.stnormal; 801 break; 802 } 803 default: { 804 gst = EFindSqlStateType.stsql; 805 break; 806 } 807 } // case 808 809 }//TBaseType.assigned(lcnextsqlstatement) 810 else if ((ast.tokencode == TBaseType.rrw_begin)) { 811 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 812 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 813 ) { 814 // start block of if ( || while statement 815 gst = EFindSqlStateType.stblock; 816 lcblocklevel = 0; 817 gcurrentsqlstatement.sourcetokenlist.add(ast); 818 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqldeclare) { 819 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 820 gcurrentsqlstatement = new TMssqlBlock(vendor); 821 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 822 gcurrentsqlstatement.sourcetokenlist.add(ast); 823 gst = EFindSqlStateType.stblock; 824 } else { 825 gcurrentsqlstatement.sourcetokenlist.add(ast); 826 } 827 } else if ((ast.tokencode == TBaseType.rrw_case)) { 828 case_end_nest++; 829 gcurrentsqlstatement.sourcetokenlist.add(ast); 830 } else if ((ast.tokencode == TBaseType.rrw_end)) { 831 if (case_end_nest > 0) { 832 case_end_nest--; 833 } 834 gcurrentsqlstatement.sourcetokenlist.add(ast); 835 } else if ((ast.tokencode == TBaseType.rrw_else)) { 836 gcurrentsqlstatement.sourcetokenlist.add(ast); 837 // if ( cond stmt ^else stmt 838 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 839 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 840 ) && (case_end_nest == 0)) 841 gcurrentsqlstatement.dummytag = 1; // reduce to 1 while stmt after else is found: if ( cond stmt ^else stmt 842 } else { 843 gcurrentsqlstatement.sourcetokenlist.add(ast); 844 } 845 break; 846 } 847 case ststoredprocedure: { 848 if (TBaseType.assigned(lcnextsqlstatement)) { 849 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 850 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 851 gcurrentsqlstatement = lcnextsqlstatement; 852 gcurrentsqlstatement.sourcetokenlist.add(ast); 853 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 854 gst = EFindSqlStateType.stnormal; 855 } else { 856 gst = EFindSqlStateType.ststoredprocedurebody; 857 gcurrentsqlstatement.sourcetokenlist.add(ast); 858 859 lcnextsqlstatement = null; 860 } 861 } 862 863 if (gst == EFindSqlStateType.ststoredprocedure) { 864 gcurrentsqlstatement.sourcetokenlist.add(ast); 865 if ((ast.tokencode == TBaseType.rrw_procedure) 866 || (ast.tokencode == TBaseType.rrw_function)) { 867 if (ast.searchToken(TBaseType.rrw_end, -1) != null) 868 gst = EFindSqlStateType.stsql; 869 } 870 } 871 break; 872 } //stplsql 873 case ststoredprocedurebody: { 874 if (TBaseType.assigned(lcnextsqlstatement)) { 875 switch (lcnextsqlstatement.sqlstatementtype) { // 876 case sstmssqlgo: { 877 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 878 gcurrentsqlstatement = lcnextsqlstatement; 879 gcurrentsqlstatement.sourcetokenlist.add(ast); 880 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 881 gst = EFindSqlStateType.stnormal; 882 break; 883 } 884 case sstinformixCreateProcedure: 885 case sstinformixCreateFunction: 886 case sstcreatetrigger: 887 case sstinformixAlterProcedure: 888 case sstinformixAlterFunction: { 889 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 890 gcurrentsqlstatement = lcnextsqlstatement; 891 gcurrentsqlstatement.sourcetokenlist.add(ast); 892 gst = EFindSqlStateType.ststoredprocedure; 893 break; 894 } 895 default: { 896 lcnextsqlstatement = null; 897 break; 898 } 899 }//case 900 } 901 902 if (gst == EFindSqlStateType.ststoredprocedurebody) 903 gcurrentsqlstatement.sourcetokenlist.add(ast); 904 } 905 break; 906 } //case 907 } //for 908 909 910 //last statement 911 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 912 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 913 } 914 915 // Set results in builder 916 builder.sqlStatements(this.sqlstatements); 917 builder.errorCode(errorcount); 918 builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount)); 919 920 return errorcount; 921 922 } 923 924 // ========== Statement Parsing ========== 925 926 /** 927 * Parse all raw statements. 928 * <p> 929 * Migrated from TGSqlParser.doparse() with Informix-specific handling. 930 */ 931 @Override 932 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 933 TCustomParser secondaryParser, TSourceTokenList tokens, 934 TStatementList rawStatements) { 935 // Store references 936 this.fparser = (TParserInformix) parser; 937 this.sourcetokenlist = tokens; 938 this.parserContext = context; 939 940 // Use the raw statements passed from AbstractSqlParser.parse() 941 this.sqlstatements = rawStatements; 942 943 // Initialize statement parsing infrastructure 944 this.sqlcmds = SqlCmdsFactory.get(vendor); 945 946 // CRITICAL: Inject sqlcmds into parser 947 this.fparser.sqlcmds = this.sqlcmds; 948 949 // Initialize global context for semantic analysis 950 initializeGlobalContext(); 951 952 // Parse each statement 953 for (int i = 0; i < sqlstatements.size(); i++) { 954 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 955 956 try { 957 stmt.setFrameStack(frameStack); 958 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 959 960 // Vendor-specific post-processing hook (default: no-op) 961 afterStatementParsed(stmt); 962 963 // Error recovery for CREATE TABLE statements 964 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 965 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 966 handleCreateTableErrorRecovery(stmt); 967 } 968 969 // Collect errors from statement 970 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 971 copyErrorsFromStatement(stmt); 972 } 973 974 } catch (Exception ex) { 975 // Use inherited exception handler 976 handleStatementParsingException(stmt, i, ex); 977 continue; 978 } 979 } 980 981 // Clean up frame stack 982 if (globalFrame != null) { 983 globalFrame.popMeFromStack(frameStack); 984 } 985 986 return this.sqlstatements; 987 } 988 989 /** 990 * Handle CREATE TABLE/INDEX error recovery for Informix. 991 * <p> 992 * Migrated from TGSqlParser.doparse() lines 16914-16971. 993 * <p> 994 * This method implements intelligent error recovery for CREATE TABLE statements 995 * that fail to parse due to vendor-specific table properties or options that 996 * appear after the column definition. 997 * <p> 998 * <b>Recovery Strategy:</b> 999 * <ol> 1000 * <li>Find the closing parenthesis ')' of the column definitions</li> 1001 * <li>Mark all tokens after this parenthesis (except semicolon) as sqlpluscmd</li> 1002 * <li>This causes the parser to ignore unknown table properties/options</li> 1003 * <li>Re-parse the statement with ignored tokens</li> 1004 * </ol> 1005 * <p> 1006 * <b>Example:</b> Informix CREATE TABLE with FRAGMENT BY clause that may not 1007 * be fully supported: 1008 * <pre> 1009 * CREATE TABLE customer (id INT, name CHAR(32)) 1010 * FRAGMENT BY EXPRESSION ... -- This gets ignored if it causes parse errors 1011 * </pre> 1012 */ 1013 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 1014 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 1015 || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) 1016 && (!TBaseType.c_createTableStrictParsing)) { 1017 1018 int nested = 0; 1019 boolean isIgnore = false, isFoundIgnoreToken = false; 1020 TSourceToken firstIgnoreToken = null; 1021 1022 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 1023 TSourceToken st = stmt.sourcetokenlist.get(k); 1024 if (isIgnore) { 1025 if (st.issolidtoken() && (st.tokencode != ';')) { 1026 isFoundIgnoreToken = true; 1027 if (firstIgnoreToken == null) { 1028 firstIgnoreToken = st; 1029 } 1030 } 1031 if (st.tokencode != ';') { 1032 st.tokencode = TBaseType.sqlpluscmd; 1033 } 1034 continue; 1035 } 1036 if (st.tokencode == (int) ')') { 1037 nested--; 1038 if (nested == 0) { 1039 // Check if this is CREATE TABLE AS (SELECT ...) 1040 // If so, don't ignore the SELECT part 1041 boolean isSelect = false; 1042 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 1043 if (st1 != null) { 1044 TSourceToken st2 = st.searchToken((int) '(', 2); 1045 if (st2 != null) { 1046 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 1047 isSelect = (st3 != null); 1048 } 1049 } 1050 if (!isSelect) isIgnore = true; 1051 } 1052 } else if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 1053 nested++; 1054 } 1055 } 1056 1057 // If we found tokens to ignore, clear errors and re-parse 1058 if (isFoundIgnoreToken) { 1059 stmt.clearError(); 1060 stmt.parsestatement(null, false); 1061 } 1062 } 1063 } 1064 1065 /** 1066 * Perform semantic analysis using TSQLResolver. 1067 * <p> 1068 * Inherited pattern from AbstractSqlParser with Informix-specific context. 1069 */ 1070 @Override 1071 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 1072 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 1073 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 1074 resolver.resolve(); 1075 } 1076 } 1077 1078 /** 1079 * Perform interpretation (AST evaluation). 1080 * <p> 1081 * Inherited pattern from AbstractSqlParser with Informix-specific context. 1082 */ 1083 @Override 1084 protected void performInterpreter(ParserContext context, TStatementList statements) { 1085 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 1086 TLog.clearLogs(); 1087 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 1088 TLog.enableInterpreterLogOnly(); 1089 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 1090 astEvaluator.eval(); 1091 } 1092 } 1093 1094 @Override 1095 public String toString() { 1096 return "InformixSqlParser{vendor=" + vendor + "}"; 1097 } 1098}