001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.EErrorType; 005import gudusoft.gsqlparser.EFindSqlStateType; 006import gudusoft.gsqlparser.ESqlStatementType; 007import gudusoft.gsqlparser.ETokenStatus; 008import gudusoft.gsqlparser.ETokenType; 009import gudusoft.gsqlparser.TBaseType; 010import gudusoft.gsqlparser.TCustomLexer; 011import gudusoft.gsqlparser.TCustomParser; 012import gudusoft.gsqlparser.TCustomSqlStatement; 013import gudusoft.gsqlparser.TLexerHana; 014import gudusoft.gsqlparser.TParserHana; 015import gudusoft.gsqlparser.TSourceToken; 016import gudusoft.gsqlparser.TSourceTokenList; 017import gudusoft.gsqlparser.TStatementList; 018import gudusoft.gsqlparser.TSyntaxError; 019import gudusoft.gsqlparser.TLog; 020import gudusoft.gsqlparser.compiler.TASTEvaluator; 021import gudusoft.gsqlparser.compiler.TContext; 022import gudusoft.gsqlparser.compiler.TFrame; 023import gudusoft.gsqlparser.compiler.TGlobalScope; 024import gudusoft.gsqlparser.resolver.TSQLResolver; 025import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 026import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 027import gudusoft.gsqlparser.sqlenv.TSQLEnv; 028import gudusoft.gsqlparser.stmt.TCommonBlock; 029import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement; 030import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock; 031import gudusoft.gsqlparser.stmt.mssql.TMssqlExecute; 032import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 033 034import java.util.Stack; 035 036/** 037 * SAP HANA SQL parser implementation. 038 * 039 * <p>This parser handles HANA-specific SQL syntax including: 040 * <ul> 041 * <li>HANA-specific SQL statements and DDL</li> 042 * <li>Stored procedures and functions</li> 043 * <li>Special HANA keywords and constructs (WITH STRUCTURED, WITH CACHE, etc.)</li> 044 * <li>HANA date/time/timestamp constants</li> 045 * <li>HANA-specific operators and expressions</li> 046 * </ul> 047 * 048 * <p><b>Implementation Status:</b> MIGRATED 049 * <ul> 050 * <li><b>Completed:</b> Migrated from TGSqlParser to AbstractSqlParser</li> 051 * <li><b>Current:</b> Fully self-contained HANA parser</li> 052 * </ul> 053 * 054 * <p><b>Design Notes:</b> 055 * <ul> 056 * <li>Extends {@link AbstractSqlParser} using template method pattern</li> 057 * <li>Uses single parser: {@link TParserHana}</li> 058 * <li>Primary delimiter: semicolon (;)</li> 059 * <li>Handles HANA-specific token transformations during raw extraction</li> 060 * </ul> 061 * 062 * @see SqlParser 063 * @see AbstractSqlParser 064 * @see TLexerHana 065 * @see TParserHana 066 * @since 3.2.0.0 067 */ 068public class HanaSqlParser extends AbstractSqlParser { 069 070 // ========== Lexer and Parser ========== 071 072 /** The HANA lexer used for tokenization */ 073 public TLexerHana flexer; 074 075 /** The HANA parser used for parsing */ 076 private TParserHana fparser; 077 078 // ========== Statement Extraction State ========== 079 080 /** Current statement being built during raw extraction */ 081 private TCustomSqlStatement gcurrentsqlstatement; 082 083 /** 084 * Construct HANA SQL parser. 085 * <p> 086 * Configures the parser for SAP HANA database with semicolon (;) as the default delimiter. 087 */ 088 public HanaSqlParser() { 089 super(EDbVendor.dbvhana); 090 this.delimiterChar = ';'; 091 this.defaultDelimiterStr = ";"; 092 093 // Create lexer once - will be reused for all parsing operations 094 this.flexer = new TLexerHana(); 095 this.flexer.delimiterchar = this.delimiterChar; 096 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 097 098 // Set parent's lexer reference for shared tokenization logic 099 this.lexer = this.flexer; 100 101 // Create parser once - will be reused for all parsing operations 102 this.fparser = new TParserHana(null); 103 this.fparser.lexer = this.flexer; 104 } 105 106 // ========== Abstract Method Implementations ========== 107 108 @Override 109 protected TCustomLexer getLexer(ParserContext context) { 110 return this.flexer; 111 } 112 113 @Override 114 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 115 this.fparser.sourcetokenlist = tokens; 116 return this.fparser; 117 } 118 119 @Override 120 protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) { 121 // HANA does not have a secondary parser (unlike Oracle with PL/SQL) 122 return null; 123 } 124 125 // ========== Phase 2: Tokenization (Hook Pattern) ========== 126 127 @Override 128 protected void tokenizeVendorSql() { 129 dohanasqltexttotokenlist(); 130 } 131 132 /** 133 * Tokenize HANA SQL text into source tokens. 134 * <p> 135 * Migrated from TGSqlParser.dohanasqltexttotokenlist() (lines 4539-4556). 136 * <p> 137 * This is a simple tokenization process that just collects all tokens from the lexer 138 * without any special transformations. 139 */ 140 private void dohanasqltexttotokenlist() { 141 TSourceToken asourcetoken, lcprevst; 142 int yychar; 143 144 asourcetoken = getanewsourcetoken(); 145 if (asourcetoken == null) return; 146 yychar = asourcetoken.tokencode; 147 148 while (yychar > 0) { 149 sourcetokenlist.add(asourcetoken); 150 asourcetoken = getanewsourcetoken(); 151 if (asourcetoken == null) break; 152 yychar = asourcetoken.tokencode; 153 } 154 } 155 156 // ========== Phase 3: Raw Statement Extraction (Hook Pattern) ========== 157 158 @Override 159 protected void setupVendorParsersForExtraction() { 160 this.fparser.sqlcmds = this.sqlcmds; 161 this.fparser.sourcetokenlist = this.sourcetokenlist; 162 } 163 164 @Override 165 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 166 dohanagetrawsqlstatements(builder); 167 } 168 169 /** 170 * Extract raw SQL statements from the token list. 171 * <p> 172 * Migrated from TGSqlParser.dohanagetrawsqlstatements() (lines 14063-14657). 173 * <p> 174 * Handles HANA-specific token transformations: 175 * <ul> 176 * <li>MINUS keyword disambiguation</li> 177 * <li>MERGE vs MERGE JOIN detection</li> 178 * <li>AS ... OF construct detection</li> 179 * <li>DATE/TIME/TIMESTAMP constants</li> 180 * <li>WITH keyword variants (WITH STRUCTURED, WITH CACHE, WITH CHECK, etc.)</li> 181 * <li>UNLOAD vs UNLOAD PRIORITY</li> 182 * <li>Stored procedure body detection (HEADER keyword)</li> 183 * </ul> 184 */ 185 private void dohanagetrawsqlstatements(SqlParseResult.Builder builder) { 186 int errorcount = 0; 187 int case_end_nest = 0; 188 189 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 190 if (!TBaseType.assigned(sourcetokenlist)) return; 191 192 gcurrentsqlstatement = null; 193 EFindSqlStateType gst = EFindSqlStateType.stnormal; 194 int lcblocklevel = 0; 195 int lctrycatchlevel = 0; 196 TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken; 197 TSourceToken ast = null; 198 int i, lcMergeInSelectNested = 0; 199 boolean lcisendconversation, lcstillinsql, lcMergeInSelect = false; 200 201 for (i = 0; i < sourcetokenlist.size(); i++) { 202 203 if ((ast != null) && (ast.issolidtoken())) 204 lcprevsolidtoken = ast; 205 206 ast = sourcetokenlist.get(i); 207 sourcetokenlist.curpos = i; 208 209 if (lcMergeInSelect) { 210 if (ast.tokencode == '(') lcMergeInSelectNested++; 211 if (ast.tokencode == ')') { 212 lcMergeInSelectNested--; 213 if (lcMergeInSelectNested == 0) { 214 lcMergeInSelect = false; 215 } 216 } 217 appendToken(gcurrentsqlstatement, ast); 218 continue; 219 } 220 if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) { 221 //tsignoredbygetrawstatement is set when cte is found in dbcmds.findcte function 222 appendToken(gcurrentsqlstatement, ast); 223 continue; 224 } 225 226 // HANA-specific token transformations 227 if (ast.tokencode == TBaseType.rrw_minus) { 228 TSourceToken st1 = ast.searchToken('(', 1); 229 if (st1 == null) { 230 st1 = ast.searchToken(TBaseType.rrw_select, 1); 231 if (st1 == null) { 232 ast.tokencode = TBaseType.ident; 233 } 234 } 235 } else if (ast.tokencode == TBaseType.rrw_merge) { 236 TSourceToken st1 = ast.nextSolidToken(); 237 if (st1.tokencode == TBaseType.rrw_join) { 238 ast.tokencode = TBaseType.rrw_merge2_sqlserver; 239 } 240 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '(')) { 241 lcMergeInSelect = true; 242 lcMergeInSelectNested++; 243 appendToken(gcurrentsqlstatement, ast); 244 continue; 245 } 246 } else if (ast.tokencode == TBaseType.rrw_as) { 247 TSourceToken st1 = ast.nextSolidToken(); 248 if (st1.tokencode == TBaseType.rrw_hana_of) { 249 ast.tokencode = TBaseType.rrw_as_before_of; 250 } 251 } else if (ast.tokencode == TBaseType.rrw_date) { 252 TSourceToken st1 = ast.nextSolidToken(); 253 if (st1.tokencode == TBaseType.sconst) { 254 ast.tokencode = TBaseType.rrw_hana_date_const; 255 } 256 } else if (ast.tokencode == TBaseType.rrw_time) { 257 TSourceToken st1 = ast.nextSolidToken(); 258 if (st1.tokencode == TBaseType.sconst) { 259 ast.tokencode = TBaseType.rrw_hana_time_const; 260 } 261 } else if (ast.tokencode == TBaseType.rrw_timestamp) { 262 TSourceToken st1 = ast.nextSolidToken(); 263 if (st1.tokencode == TBaseType.sconst) { 264 ast.tokencode = TBaseType.rrw_hana_timestamp_const; 265 } 266 } else if (ast.tokencode == TBaseType.rrw_with) { 267 TSourceToken st1 = ast.nextSolidToken(); 268 if (st1.toString().equalsIgnoreCase("structured")) { 269 ast.tokencode = TBaseType.rrw_hana_with_structured; 270 } else if (st1.toString().equalsIgnoreCase("cache")) { 271 ast.tokencode = TBaseType.rrw_hana_with_cache; 272 } else if (st1.toString().equalsIgnoreCase("static")) { 273 ast.tokencode = TBaseType.rrw_hana_with_cache; 274 } else if (st1.toString().equalsIgnoreCase("dynamic")) { 275 ast.tokencode = TBaseType.rrw_hana_with_cache; 276 } else if (st1.toString().equalsIgnoreCase("check")) { 277 ast.tokencode = TBaseType.rrw_hana_with_check; 278 } else if (st1.toString().equalsIgnoreCase("mask")) { 279 ast.tokencode = TBaseType.rrw_hana_with_mask; 280 } else if (st1.toString().equalsIgnoreCase("expression")) { 281 ast.tokencode = TBaseType.rrw_hana_with_expression; 282 } else if (st1.toString().equalsIgnoreCase("anonymization")) { 283 ast.tokencode = TBaseType.rrw_hana_with_anonymization; 284 } else if (st1.toString().equalsIgnoreCase("hint")) { 285 ast.tokencode = TBaseType.rrw_hana_with_hint; 286 } else if (st1.toString().equalsIgnoreCase("range_restriction")) { 287 ast.tokencode = TBaseType.rrw_hana_with_range_restriction; 288 } 289 } else if (ast.tokencode == TBaseType.rrw_hana_unload) { 290 TSourceToken st1 = ast.nextSolidToken(); 291 if (st1.toString().equalsIgnoreCase("priority")) { 292 ast.tokencode = TBaseType.rrw_hana_unload2; 293 } 294 } 295 296 if (gst == EFindSqlStateType.ststoredprocedurebody) { 297 if (!((ast.tokencode == TBaseType.rrw_go) 298 || (ast.tokencode == TBaseType.rrw_create) 299 || (ast.tokencode == TBaseType.rrw_alter))) { 300 appendToken(gcurrentsqlstatement, ast); 301 continue; 302 } 303 } 304 305 TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 306 307 switch (gst) { 308 case sterror: { 309 if (TBaseType.assigned(lcnextsqlstatement)) { 310 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 311 gcurrentsqlstatement = lcnextsqlstatement; 312 appendToken(gcurrentsqlstatement, ast); 313 gst = EFindSqlStateType.stsql; 314 } else if ((ast.tokentype == ETokenType.ttsemicolon)) { 315 appendToken(gcurrentsqlstatement, ast); 316 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 317 gst = EFindSqlStateType.stnormal; 318 } else { 319 appendToken(gcurrentsqlstatement, ast); 320 } 321 break; 322 } 323 case stnormal: { 324 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 325 || (ast.tokencode == TBaseType.cmtslashstar) 326 || (ast.tokencode == TBaseType.lexspace) 327 || (ast.tokencode == TBaseType.lexnewline) 328 || (ast.tokentype == ETokenType.ttsemicolon)) { 329 if (TBaseType.assigned(gcurrentsqlstatement)) { 330 appendToken(gcurrentsqlstatement, ast); 331 } 332 333 if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) { 334 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 335 // ;;;; continuous semicolon,treat it as comment 336 ast.tokentype = ETokenType.ttsimplecomment; 337 ast.tokencode = TBaseType.cmtdoublehyphen; 338 } else { 339 } 340 341 } 342 343 continue; 344 } 345 346 gcurrentsqlstatement = lcnextsqlstatement; //isstoredprocedure(ast,dbvendor,gst,sqlstatements); 347 348 if (TBaseType.assigned(gcurrentsqlstatement)) { 349 switch (gcurrentsqlstatement.sqlstatementtype) { 350 case sstcreateprocedure: 351 case sstcreatefunction: 352 case sstcreatetrigger: 353 // case sstalterprocedure: 354 // case sstalterfunction: 355 case sstaltertrigger: { 356 appendToken(gcurrentsqlstatement, ast); 357 gst = EFindSqlStateType.ststoredprocedure; 358 break; 359 } 360 case sstmssqlgo: { 361 appendToken(gcurrentsqlstatement, ast); 362 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 363 gst = EFindSqlStateType.stnormal; 364 break; 365 } 366 default: { 367 appendToken(gcurrentsqlstatement, ast); 368 gst = EFindSqlStateType.stsql; 369 break; 370 } 371 } // case 372 } else { 373 if (ast.tokencode == TBaseType.rrw_begin) { 374 gcurrentsqlstatement = new TMssqlBlock(vendor); 375 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 376 appendToken(gcurrentsqlstatement, ast); 377 gst = EFindSqlStateType.stblock; 378 } else { 379 if (sqlstatements.size() == 0) { 380 //first statement of mssql batch, treat it as exec sp 381 gst = EFindSqlStateType.stsql; 382 gcurrentsqlstatement = new TMssqlExecute(vendor); 383 //tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 384// todo need previous line need to be implemented 385 appendToken(gcurrentsqlstatement, ast); 386 } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) { 387 // prev sql is go, treat it as exec sp 388 gst = EFindSqlStateType.stsql; 389 gcurrentsqlstatement = new TMssqlExecute(vendor); 390 //todo need to be implemented: tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 391 appendToken(gcurrentsqlstatement, ast); 392 } else { 393 } 394 } 395 } 396 397 398 if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found 399 { 400 401// errormessage = "error when tokenlize:"+ast.astext+"("+ast.lineNo +","+ast.columnNo +")"; 402// errorcount = 1; 403 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 404 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 405 406 ast.tokentype = ETokenType.tttokenlizererrortoken; 407 gst = EFindSqlStateType.sterror; 408 409 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 410 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 411 appendToken(gcurrentsqlstatement, ast); 412 } 413 break; 414 } 415 case stblock: { 416 if (TBaseType.assigned(lcnextsqlstatement)) { 417 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 418 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 419 gcurrentsqlstatement = lcnextsqlstatement; 420 appendToken(gcurrentsqlstatement, ast); 421 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 422 gst = EFindSqlStateType.stnormal; 423 } else { 424 lcnextsqlstatement = null; 425 } 426 } 427 428 if ((lcblocklevel == -1) && (ast.tokencode == ';')) { 429 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 430 gst = EFindSqlStateType.stnormal; 431 break; 432 } 433 434 if (gst == EFindSqlStateType.stblock) { 435 appendToken(gcurrentsqlstatement, ast); 436 if (ast.tokencode == TBaseType.rrw_begin) { 437 // { [distributed] transaxtion/trans statement 438 // { dialog [ conversation ] 439 // { conversation timer 440 // doesn't start block ({ .. }) 441 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 442 if (TBaseType.assigned(lcnextsolidtoken)) { 443 if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran") 444 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction"))) { 445 lcblocklevel++; 446 } 447 } else 448 lcblocklevel++; 449 450 } else if (ast.tokencode == TBaseType.rrw_case) // case ... } 451 lcblocklevel++; 452 else if (ast.tokencode == TBaseType.rrw_end) { 453 454 lcisendconversation = false; 455 456 457 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 458 459 if (!lcisendconversation) { 460 461 if (lcblocklevel == 0) { 462 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 463 if (TBaseType.assigned(lcnextsolidtoken)) { 464 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 465 // { .. } else 466 gst = EFindSqlStateType.stsql; 467 } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) { 468 lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false); 469 if (TBaseType.assigned(lcnnextsolidtoken)) { 470 if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) { 471 // { .. } else 472 gst = EFindSqlStateType.stsql; 473 } 474 } 475 } 476 } 477 } 478 479// if ( gst != EFindSqlStateType.stsql ) 480// { 481// onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 482// gst = EFindSqlStateType.stnormal; 483// } 484 485 } else { 486 lcblocklevel--; 487 } 488 489 } 490 } 491 } 492 break; 493 } 494 case stsql: { 495 if ((ast.tokentype == ETokenType.ttsemicolon)) { 496 lcstillinsql = false; 497 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 498 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 499 if (TBaseType.assigned(lcnextsolidtoken)) { 500 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 501 // if ( expr stmt; else 502 appendToken(gcurrentsqlstatement, ast); 503 lcstillinsql = true; 504 } 505 506 } 507 } 508 509 if (!lcstillinsql) { 510 gst = EFindSqlStateType.stnormal; 511 appendToken(gcurrentsqlstatement, ast); 512 gcurrentsqlstatement.semicolonended = ast; 513 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 514 } 515 516 } else if (TBaseType.assigned(lcnextsqlstatement)) { 517 518 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 519 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 520 gcurrentsqlstatement = lcnextsqlstatement; 521 appendToken(gcurrentsqlstatement, ast); 522 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 523 gst = EFindSqlStateType.stnormal; 524 continue; 525 } 526 527 switch (gcurrentsqlstatement.sqlstatementtype) { 528 case sstmssqlif: 529 case sstmssqlwhile: { 530 // if ( || while only contain one sql(not closed by {/} pair) 531 // so will still in if ( || while statement 532 if (gcurrentsqlstatement.dummytag == 1) { 533 // if ( cond ^stmt nextstmt (^ stands for current pos) 534 appendToken(gcurrentsqlstatement, ast); 535 536 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 537 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) 538 gcurrentsqlstatement.dummytag = 1; 539 else 540 gcurrentsqlstatement.dummytag = 0; 541 542 543 lcnextsqlstatement = null; 544 continue; 545 } 546 break; 547 }// 548 case sstcreateschema: { 549 appendToken(gcurrentsqlstatement, ast); 550 lcnextsqlstatement = null; 551 continue; 552 } 553 }//case 554 555 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 556 gcurrentsqlstatement = lcnextsqlstatement; 557 appendToken(gcurrentsqlstatement, ast); 558 559 switch (gcurrentsqlstatement.sqlstatementtype) { 560 case sstcreateprocedure: 561 case sstcreatefunction: 562 case sstcreatetrigger: 563 case sstalterprocedure: 564 case sstalterfunction: 565 case sstaltertrigger: { 566 gst = EFindSqlStateType.ststoredprocedure; 567 break; 568 } 569 default: { 570 gst = EFindSqlStateType.stsql; 571 break; 572 } 573 } // case 574 575 }//TBaseType.assigned(lcnextsqlstatement) 576 else if ((ast.tokencode == TBaseType.rrw_begin)) { 577 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 578 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) { 579 // start block of if ( || while statement 580 gst = EFindSqlStateType.stblock; 581 lcblocklevel = 0; 582 appendToken(gcurrentsqlstatement, ast); 583 } else { 584 appendToken(gcurrentsqlstatement, ast); 585 } 586 } else if ((ast.tokencode == TBaseType.rrw_case)) { 587 case_end_nest++; 588 appendToken(gcurrentsqlstatement, ast); 589 } else if ((ast.tokencode == TBaseType.rrw_end)) { 590 if (case_end_nest > 0) { 591 case_end_nest--; 592 } 593 appendToken(gcurrentsqlstatement, ast); 594 } else if ((ast.tokencode == TBaseType.rrw_else)) { 595 appendToken(gcurrentsqlstatement, ast); 596 // if ( cond stmt ^else stmt 597 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 598 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) && (case_end_nest == 0)) 599 gcurrentsqlstatement.dummytag = 1; // reduce to 1 while stmt after else is found: if ( cond stmt ^else stmt 600 } else { 601 appendToken(gcurrentsqlstatement, ast); 602 } 603 break; 604 } 605 case ststoredprocedure: { 606 if (TBaseType.assigned(lcnextsqlstatement)) { 607 608 609 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstalterfunction) 610 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) 611 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction) 612 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger) 613 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstalterprocedure) 614 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstaltertrigger)) { 615 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 616 gcurrentsqlstatement = lcnextsqlstatement; 617 appendToken(gcurrentsqlstatement, ast); 618 gst = EFindSqlStateType.ststoredprocedure; 619 break; 620 } else { 621 gst = EFindSqlStateType.ststoredprocedurebody; 622 appendToken(gcurrentsqlstatement, ast); 623 624 lcnextsqlstatement = null; 625 } 626 } 627 628 if (gst == EFindSqlStateType.ststoredprocedure) { 629 appendToken(gcurrentsqlstatement, ast); 630 if (ast.tokencode == TBaseType.rrw_begin) { 631 gst = EFindSqlStateType.ststoredprocedurebody; 632 } else if (ast.tokencode == TBaseType.rrw_hana_header) { 633 // HANA-specific: HEADER keyword starts procedure body 634 gst = EFindSqlStateType.ststoredprocedurebody; 635 } 636 } 637 break; 638 } //stplsql 639 case ststoredprocedurebody: { 640 if (TBaseType.assigned(lcnextsqlstatement)) { 641 switch (lcnextsqlstatement.sqlstatementtype) { 642 case sstmssqlgo: { 643 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 644 gcurrentsqlstatement = lcnextsqlstatement; 645 appendToken(gcurrentsqlstatement, ast); 646 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 647 gst = EFindSqlStateType.stnormal; 648 break; 649 } 650 case sstcreateprocedure: 651 case sstcreatefunction: 652 case sstcreatetrigger: 653 case sstalterprocedure: 654 case sstalterfunction: 655 case sstaltertrigger: { 656 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 657 gcurrentsqlstatement = lcnextsqlstatement; 658 appendToken(gcurrentsqlstatement, ast); 659 gst = EFindSqlStateType.ststoredprocedure; 660 break; 661 } 662 default: { 663 lcnextsqlstatement = null; 664 break; 665 } 666 }//case 667 } 668 669 if (gst == EFindSqlStateType.ststoredprocedurebody) 670 appendToken(gcurrentsqlstatement, ast); 671 } 672 break; 673 } //case 674 } //for 675 676 677 //last statement 678 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 679 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder); 680 } 681 682 // Populate builder with extracted statements 683 builder.sqlStatements(this.sqlstatements); 684 builder.errorCode(0); 685 builder.errorMessage(""); 686 } 687 688 /** 689 * Helper method to append a token to a statement. 690 * <p> 691 * Sets the token's statement reference and adds it to the statement's token list. 692 */ 693 private void appendToken(TCustomSqlStatement statement, TSourceToken token) { 694 if (statement == null || token == null) { 695 return; 696 } 697 token.stmt = statement; 698 statement.sourcetokenlist.add(token); 699 } 700 701 // ========== Phase 4: Statement Parsing ========== 702 703 @Override 704 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 705 TCustomParser secondaryParser, TSourceTokenList tokens, 706 TStatementList rawStatements) { 707 // Store references 708 this.fparser = (TParserHana) parser; 709 this.sourcetokenlist = tokens; 710 this.parserContext = context; 711 this.sqlstatements = rawStatements; 712 713 // Initialize sqlcmds 714 if (this.sqlcmds == null) { 715 this.sqlcmds = SqlCmdsFactory.get(vendor); 716 } 717 this.fparser.sqlcmds = this.sqlcmds; 718 719 // Initialize global context using inherited method 720 initializeGlobalContext(); 721 722 // Parse each statement 723 for (int i = 0; i < sqlstatements.size(); i++) { 724 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 725 try { 726 stmt.setFrameStack(frameStack); 727 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 728 729 // Error recovery 730 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 731 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 732 handleCreateTableErrorRecovery(stmt); 733 } 734 735 // Collect errors 736 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 737 copyErrorsFromStatement(stmt); 738 } 739 } catch (Exception ex) { 740 // Use inherited exception handler 741 handleStatementParsingException(stmt, i, ex); 742 continue; 743 } 744 } 745 746 // Clean up frame stack 747 if (globalFrame != null) { 748 globalFrame.popMeFromStack(frameStack); 749 } 750 751 return sqlstatements; 752 } 753 754 /** 755 * Handle CREATE TABLE error recovery. 756 * <p> 757 * Migrated from TGSqlParser.doparse() error recovery logic (lines 16914-16971). 758 * <p> 759 * Attempts to recover from parsing errors in CREATE TABLE statements by 760 * marking unparseable table properties as sqlpluscmd and retrying. 761 * This allows parsing CREATE TABLE statements with vendor-specific extensions 762 * that may not be in the grammar. 763 * <p> 764 * <b>Note:</b> HANA uses the generic error recovery logic without vendor-specific 765 * property validation (unlike Oracle which checks searchOracleTablePros). 766 */ 767 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 768 // Check if this is a CREATE TABLE or CREATE INDEX statement 769 if (!(stmt.sqlstatementtype == ESqlStatementType.sstcreatetable || 770 stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) { 771 return; 772 } 773 774 // Check if strict parsing is disabled 775 if (TBaseType.c_createTableStrictParsing) { 776 return; 777 } 778 779 TCustomSqlStatement errorSqlStatement = stmt; 780 781 int nested = 0; 782 boolean isIgnore = false; 783 boolean isFoundIgnoreToken = false; 784 TSourceToken firstIgnoreToken = null; 785 786 // Iterate through tokens to find the closing parenthesis of table definition 787 for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) { 788 TSourceToken st = errorSqlStatement.sourcetokenlist.get(k); 789 790 if (isIgnore) { 791 // We're past the table definition, mark tokens as ignoreable 792 if (st.issolidtoken() && (st.tokencode != ';')) { 793 isFoundIgnoreToken = true; 794 if (firstIgnoreToken == null) { 795 firstIgnoreToken = st; 796 } 797 } 798 // Mark all tokens (except semicolon) as sqlpluscmd to ignore them 799 if (st.tokencode != ';') { 800 st.tokencode = TBaseType.sqlpluscmd; 801 } 802 continue; 803 } 804 805 // Track closing parentheses 806 if (st.tokencode == (int) ')') { 807 nested--; 808 if (nested == 0) { 809 // Found the closing parenthesis of table definition 810 // Check if next tokens are "AS ( SELECT" (CTAS pattern) 811 boolean isSelect = false; 812 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 813 if (st1 != null) { 814 TSourceToken st2 = st.searchToken((int) '(', 2); 815 if (st2 != null) { 816 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 817 isSelect = (st3 != null); 818 } 819 } 820 // If not a CTAS, start ignoring subsequent tokens 821 if (!isSelect) { 822 isIgnore = true; 823 } 824 } 825 } 826 827 // Track opening parentheses 828 if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 829 nested++; 830 } 831 } 832 833 // If we found ignoreable tokens, clear errors and retry parsing 834 // Note: HANA uses generic recovery without vendor-specific property validation 835 // (unlike Oracle at line 16963 which checks searchOracleTablePros) 836 if (isFoundIgnoreToken) { 837 errorSqlStatement.clearError(); 838 int retryResult = errorSqlStatement.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree()); 839 } 840 } 841 842 // ========== Phase 5: Semantic Analysis ========== 843 844 @Override 845 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 846 if (!TBaseType.isEnableResolver()) { 847 return; 848 } 849 850 // Only run resolver if there are no syntax errors 851 if (getSyntaxErrors().isEmpty()) { 852 TSQLResolver resolver = new TSQLResolver(this.globalContext, this.sqlstatements); 853 resolver.resolve(); 854 } 855 } 856}