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 763 764 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 765 gcurrentsqlstatement = lcnextsqlstatement; 766 gcurrentsqlstatement.sourcetokenlist.add(ast); 767 768 switch (gcurrentsqlstatement.sqlstatementtype) { // 769 case sstinformixCreateProcedure: 770 case sstinformixCreateFunction: 771 case sstcreatetrigger: 772 case sstinformixAlterProcedure: 773 case sstinformixAlterFunction: { 774 gst = EFindSqlStateType.ststoredprocedure; 775 break; 776 } 777 case sstmssqlbegintry: 778 case sstmssqlbegincatch: { 779 gst = EFindSqlStateType.sttrycatch; 780 lctrycatchlevel = 0; 781 break; 782 } 783 case sstmssqlgo: { 784 gst = EFindSqlStateType.stnormal; 785 break; 786 } 787 default: { 788 gst = EFindSqlStateType.stsql; 789 break; 790 } 791 } // case 792 793 }//TBaseType.assigned(lcnextsqlstatement) 794 else if ((ast.tokencode == TBaseType.rrw_begin)) { 795 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 796 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 797 ) { 798 // start block of if ( || while statement 799 gst = EFindSqlStateType.stblock; 800 lcblocklevel = 0; 801 gcurrentsqlstatement.sourcetokenlist.add(ast); 802 } else if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqldeclare) { 803 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 804 gcurrentsqlstatement = new TMssqlBlock(vendor); 805 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 806 gcurrentsqlstatement.sourcetokenlist.add(ast); 807 gst = EFindSqlStateType.stblock; 808 } else { 809 gcurrentsqlstatement.sourcetokenlist.add(ast); 810 } 811 } else if ((ast.tokencode == TBaseType.rrw_case)) { 812 case_end_nest++; 813 gcurrentsqlstatement.sourcetokenlist.add(ast); 814 } else if ((ast.tokencode == TBaseType.rrw_end)) { 815 if (case_end_nest > 0) { 816 case_end_nest--; 817 } 818 gcurrentsqlstatement.sourcetokenlist.add(ast); 819 } else if ((ast.tokencode == TBaseType.rrw_else)) { 820 gcurrentsqlstatement.sourcetokenlist.add(ast); 821 // if ( cond stmt ^else stmt 822 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 823 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 824 ) && (case_end_nest == 0)) 825 gcurrentsqlstatement.dummytag = 1; // reduce to 1 while stmt after else is found: if ( cond stmt ^else stmt 826 } else { 827 gcurrentsqlstatement.sourcetokenlist.add(ast); 828 } 829 break; 830 } 831 case ststoredprocedure: { 832 if (TBaseType.assigned(lcnextsqlstatement)) { 833 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 834 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 835 gcurrentsqlstatement = lcnextsqlstatement; 836 gcurrentsqlstatement.sourcetokenlist.add(ast); 837 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 838 gst = EFindSqlStateType.stnormal; 839 } else { 840 gst = EFindSqlStateType.ststoredprocedurebody; 841 gcurrentsqlstatement.sourcetokenlist.add(ast); 842 843 lcnextsqlstatement = null; 844 } 845 } 846 847 if (gst == EFindSqlStateType.ststoredprocedure) { 848 gcurrentsqlstatement.sourcetokenlist.add(ast); 849 if ((ast.tokencode == TBaseType.rrw_procedure) 850 || (ast.tokencode == TBaseType.rrw_function)) { 851 if (ast.searchToken(TBaseType.rrw_end, -1) != null) 852 gst = EFindSqlStateType.stsql; 853 } 854 } 855 break; 856 } //stplsql 857 case ststoredprocedurebody: { 858 if (TBaseType.assigned(lcnextsqlstatement)) { 859 switch (lcnextsqlstatement.sqlstatementtype) { // 860 case sstmssqlgo: { 861 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 862 gcurrentsqlstatement = lcnextsqlstatement; 863 gcurrentsqlstatement.sourcetokenlist.add(ast); 864 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 865 gst = EFindSqlStateType.stnormal; 866 break; 867 } 868 case sstinformixCreateProcedure: 869 case sstinformixCreateFunction: 870 case sstcreatetrigger: 871 case sstinformixAlterProcedure: 872 case sstinformixAlterFunction: { 873 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 874 gcurrentsqlstatement = lcnextsqlstatement; 875 gcurrentsqlstatement.sourcetokenlist.add(ast); 876 gst = EFindSqlStateType.ststoredprocedure; 877 break; 878 } 879 default: { 880 lcnextsqlstatement = null; 881 break; 882 } 883 }//case 884 } 885 886 if (gst == EFindSqlStateType.ststoredprocedurebody) 887 gcurrentsqlstatement.sourcetokenlist.add(ast); 888 } 889 break; 890 } //case 891 } //for 892 893 894 //last statement 895 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 896 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 897 } 898 899 // Set results in builder 900 builder.sqlStatements(this.sqlstatements); 901 builder.errorCode(errorcount); 902 builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount)); 903 904 return errorcount; 905 906 } 907 908 // ========== Statement Parsing ========== 909 910 /** 911 * Parse all raw statements. 912 * <p> 913 * Migrated from TGSqlParser.doparse() with Informix-specific handling. 914 */ 915 @Override 916 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 917 TCustomParser secondaryParser, TSourceTokenList tokens, 918 TStatementList rawStatements) { 919 // Store references 920 this.fparser = (TParserInformix) parser; 921 this.sourcetokenlist = tokens; 922 this.parserContext = context; 923 924 // Use the raw statements passed from AbstractSqlParser.parse() 925 this.sqlstatements = rawStatements; 926 927 // Initialize statement parsing infrastructure 928 this.sqlcmds = SqlCmdsFactory.get(vendor); 929 930 // CRITICAL: Inject sqlcmds into parser 931 this.fparser.sqlcmds = this.sqlcmds; 932 933 // Initialize global context for semantic analysis 934 initializeGlobalContext(); 935 936 // Parse each statement 937 for (int i = 0; i < sqlstatements.size(); i++) { 938 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 939 940 try { 941 stmt.setFrameStack(frameStack); 942 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 943 944 // Vendor-specific post-processing hook (default: no-op) 945 afterStatementParsed(stmt); 946 947 // Error recovery for CREATE TABLE statements 948 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 949 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 950 handleCreateTableErrorRecovery(stmt); 951 } 952 953 // Collect errors from statement 954 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 955 copyErrorsFromStatement(stmt); 956 } 957 958 } catch (Exception ex) { 959 // Use inherited exception handler 960 handleStatementParsingException(stmt, i, ex); 961 continue; 962 } 963 } 964 965 // Clean up frame stack 966 if (globalFrame != null) { 967 globalFrame.popMeFromStack(frameStack); 968 } 969 970 return this.sqlstatements; 971 } 972 973 /** 974 * Handle CREATE TABLE/INDEX error recovery for Informix. 975 * <p> 976 * Migrated from TGSqlParser.doparse() lines 16914-16971. 977 * <p> 978 * This method implements intelligent error recovery for CREATE TABLE statements 979 * that fail to parse due to vendor-specific table properties or options that 980 * appear after the column definition. 981 * <p> 982 * <b>Recovery Strategy:</b> 983 * <ol> 984 * <li>Find the closing parenthesis ')' of the column definitions</li> 985 * <li>Mark all tokens after this parenthesis (except semicolon) as sqlpluscmd</li> 986 * <li>This causes the parser to ignore unknown table properties/options</li> 987 * <li>Re-parse the statement with ignored tokens</li> 988 * </ol> 989 * <p> 990 * <b>Example:</b> Informix CREATE TABLE with FRAGMENT BY clause that may not 991 * be fully supported: 992 * <pre> 993 * CREATE TABLE customer (id INT, name CHAR(32)) 994 * FRAGMENT BY EXPRESSION ... -- This gets ignored if it causes parse errors 995 * </pre> 996 */ 997 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 998 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 999 || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) 1000 && (!TBaseType.c_createTableStrictParsing)) { 1001 1002 int nested = 0; 1003 boolean isIgnore = false, isFoundIgnoreToken = false; 1004 TSourceToken firstIgnoreToken = null; 1005 1006 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 1007 TSourceToken st = stmt.sourcetokenlist.get(k); 1008 if (isIgnore) { 1009 if (st.issolidtoken() && (st.tokencode != ';')) { 1010 isFoundIgnoreToken = true; 1011 if (firstIgnoreToken == null) { 1012 firstIgnoreToken = st; 1013 } 1014 } 1015 if (st.tokencode != ';') { 1016 st.tokencode = TBaseType.sqlpluscmd; 1017 } 1018 continue; 1019 } 1020 if (st.tokencode == (int) ')') { 1021 nested--; 1022 if (nested == 0) { 1023 // Check if this is CREATE TABLE AS (SELECT ...) 1024 // If so, don't ignore the SELECT part 1025 boolean isSelect = false; 1026 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 1027 if (st1 != null) { 1028 TSourceToken st2 = st.searchToken((int) '(', 2); 1029 if (st2 != null) { 1030 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 1031 isSelect = (st3 != null); 1032 } 1033 } 1034 if (!isSelect) isIgnore = true; 1035 } 1036 } else if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 1037 nested++; 1038 } 1039 } 1040 1041 // If we found tokens to ignore, clear errors and re-parse 1042 if (isFoundIgnoreToken) { 1043 stmt.clearError(); 1044 stmt.parsestatement(null, false); 1045 } 1046 } 1047 } 1048 1049 /** 1050 * Perform semantic analysis using TSQLResolver. 1051 * <p> 1052 * Inherited pattern from AbstractSqlParser with Informix-specific context. 1053 */ 1054 @Override 1055 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 1056 if (TBaseType.isEnableResolver() && getSyntaxErrors().isEmpty()) { 1057 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 1058 resolver.resolve(); 1059 } 1060 } 1061 1062 /** 1063 * Perform interpretation (AST evaluation). 1064 * <p> 1065 * Inherited pattern from AbstractSqlParser with Informix-specific context. 1066 */ 1067 @Override 1068 protected void performInterpreter(ParserContext context, TStatementList statements) { 1069 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 1070 TLog.clearLogs(); 1071 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 1072 TLog.enableInterpreterLogOnly(); 1073 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 1074 astEvaluator.eval(); 1075 } 1076 } 1077 1078 @Override 1079 public String toString() { 1080 return "InformixSqlParser{vendor=" + vendor + "}"; 1081 } 1082}