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 } 287 } else if (ast.tokencode == TBaseType.rrw_hana_unload) { 288 TSourceToken st1 = ast.nextSolidToken(); 289 if (st1.toString().equalsIgnoreCase("priority")) { 290 ast.tokencode = TBaseType.rrw_hana_unload2; 291 } 292 } 293 294 if (gst == EFindSqlStateType.ststoredprocedurebody) { 295 if (!((ast.tokencode == TBaseType.rrw_go) 296 || (ast.tokencode == TBaseType.rrw_create) 297 || (ast.tokencode == TBaseType.rrw_alter))) { 298 appendToken(gcurrentsqlstatement, ast); 299 continue; 300 } 301 } 302 303 TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 304 305 switch (gst) { 306 case sterror: { 307 if (TBaseType.assigned(lcnextsqlstatement)) { 308 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 309 gcurrentsqlstatement = lcnextsqlstatement; 310 appendToken(gcurrentsqlstatement, ast); 311 gst = EFindSqlStateType.stsql; 312 } else if ((ast.tokentype == ETokenType.ttsemicolon)) { 313 appendToken(gcurrentsqlstatement, ast); 314 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 315 gst = EFindSqlStateType.stnormal; 316 } else { 317 appendToken(gcurrentsqlstatement, ast); 318 } 319 break; 320 } 321 case stnormal: { 322 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 323 || (ast.tokencode == TBaseType.cmtslashstar) 324 || (ast.tokencode == TBaseType.lexspace) 325 || (ast.tokencode == TBaseType.lexnewline) 326 || (ast.tokentype == ETokenType.ttsemicolon)) { 327 if (TBaseType.assigned(gcurrentsqlstatement)) { 328 appendToken(gcurrentsqlstatement, ast); 329 } 330 331 if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) { 332 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 333 // ;;;; continuous semicolon,treat it as comment 334 ast.tokentype = ETokenType.ttsimplecomment; 335 ast.tokencode = TBaseType.cmtdoublehyphen; 336 } else { 337 } 338 339 } 340 341 continue; 342 } 343 344 gcurrentsqlstatement = lcnextsqlstatement; //isstoredprocedure(ast,dbvendor,gst,sqlstatements); 345 346 if (TBaseType.assigned(gcurrentsqlstatement)) { 347 switch (gcurrentsqlstatement.sqlstatementtype) { 348 case sstcreateprocedure: 349 case sstcreatefunction: 350 case sstcreatetrigger: 351 // case sstalterprocedure: 352 // case sstalterfunction: 353 case sstaltertrigger: { 354 appendToken(gcurrentsqlstatement, ast); 355 gst = EFindSqlStateType.ststoredprocedure; 356 break; 357 } 358 case sstmssqlgo: { 359 appendToken(gcurrentsqlstatement, ast); 360 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 361 gst = EFindSqlStateType.stnormal; 362 break; 363 } 364 default: { 365 appendToken(gcurrentsqlstatement, ast); 366 gst = EFindSqlStateType.stsql; 367 break; 368 } 369 } // case 370 } else { 371 if (ast.tokencode == TBaseType.rrw_begin) { 372 gcurrentsqlstatement = new TMssqlBlock(vendor); 373 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 374 appendToken(gcurrentsqlstatement, ast); 375 gst = EFindSqlStateType.stblock; 376 } else { 377 if (sqlstatements.size() == 0) { 378 //first statement of mssql batch, treat it as exec sp 379 gst = EFindSqlStateType.stsql; 380 gcurrentsqlstatement = new TMssqlExecute(vendor); 381 //tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 382// todo need previous line need to be implemented 383 appendToken(gcurrentsqlstatement, ast); 384 } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) { 385 // prev sql is go, treat it as exec sp 386 gst = EFindSqlStateType.stsql; 387 gcurrentsqlstatement = new TMssqlExecute(vendor); 388 //todo need to be implemented: tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 389 appendToken(gcurrentsqlstatement, ast); 390 } else { 391 } 392 } 393 } 394 395 396 if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found 397 { 398 399// errormessage = "error when tokenlize:"+ast.astext+"("+ast.lineNo +","+ast.columnNo +")"; 400// errorcount = 1; 401 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 402 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 403 404 ast.tokentype = ETokenType.tttokenlizererrortoken; 405 gst = EFindSqlStateType.sterror; 406 407 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 408 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 409 appendToken(gcurrentsqlstatement, ast); 410 } 411 break; 412 } 413 case stblock: { 414 if (TBaseType.assigned(lcnextsqlstatement)) { 415 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 416 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 417 gcurrentsqlstatement = lcnextsqlstatement; 418 appendToken(gcurrentsqlstatement, ast); 419 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 420 gst = EFindSqlStateType.stnormal; 421 } else { 422 lcnextsqlstatement = null; 423 } 424 } 425 426 if ((lcblocklevel == -1) && (ast.tokencode == ';')) { 427 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 428 gst = EFindSqlStateType.stnormal; 429 break; 430 } 431 432 if (gst == EFindSqlStateType.stblock) { 433 appendToken(gcurrentsqlstatement, ast); 434 if (ast.tokencode == TBaseType.rrw_begin) { 435 // { [distributed] transaxtion/trans statement 436 // { dialog [ conversation ] 437 // { conversation timer 438 // doesn't start block ({ .. }) 439 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 440 if (TBaseType.assigned(lcnextsolidtoken)) { 441 if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran") 442 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction"))) { 443 lcblocklevel++; 444 } 445 } else 446 lcblocklevel++; 447 448 } else if (ast.tokencode == TBaseType.rrw_case) // case ... } 449 lcblocklevel++; 450 else if (ast.tokencode == TBaseType.rrw_end) { 451 452 lcisendconversation = false; 453 454 455 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 456 457 if (!lcisendconversation) { 458 459 if (lcblocklevel == 0) { 460 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 461 if (TBaseType.assigned(lcnextsolidtoken)) { 462 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 463 // { .. } else 464 gst = EFindSqlStateType.stsql; 465 } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) { 466 lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false); 467 if (TBaseType.assigned(lcnnextsolidtoken)) { 468 if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) { 469 // { .. } else 470 gst = EFindSqlStateType.stsql; 471 } 472 } 473 } 474 } 475 } 476 477// if ( gst != EFindSqlStateType.stsql ) 478// { 479// onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 480// gst = EFindSqlStateType.stnormal; 481// } 482 483 } else { 484 lcblocklevel--; 485 } 486 487 } 488 } 489 } 490 break; 491 } 492 case stsql: { 493 if ((ast.tokentype == ETokenType.ttsemicolon)) { 494 lcstillinsql = false; 495 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 496 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 497 if (TBaseType.assigned(lcnextsolidtoken)) { 498 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 499 // if ( expr stmt; else 500 appendToken(gcurrentsqlstatement, ast); 501 lcstillinsql = true; 502 } 503 504 } 505 } 506 507 if (!lcstillinsql) { 508 gst = EFindSqlStateType.stnormal; 509 appendToken(gcurrentsqlstatement, ast); 510 gcurrentsqlstatement.semicolonended = ast; 511 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 512 } 513 514 } else if (TBaseType.assigned(lcnextsqlstatement)) { 515 516 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 517 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 518 gcurrentsqlstatement = lcnextsqlstatement; 519 appendToken(gcurrentsqlstatement, ast); 520 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 521 gst = EFindSqlStateType.stnormal; 522 continue; 523 } 524 525 switch (gcurrentsqlstatement.sqlstatementtype) { 526 case sstmssqlif: 527 case sstmssqlwhile: { 528 // if ( || while only contain one sql(not closed by {/} pair) 529 // so will still in if ( || while statement 530 if (gcurrentsqlstatement.dummytag == 1) { 531 // if ( cond ^stmt nextstmt (^ stands for current pos) 532 appendToken(gcurrentsqlstatement, ast); 533 534 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 535 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) 536 gcurrentsqlstatement.dummytag = 1; 537 else 538 gcurrentsqlstatement.dummytag = 0; 539 540 541 lcnextsqlstatement = null; 542 continue; 543 } 544 break; 545 }// 546 case sstcreateschema: { 547 appendToken(gcurrentsqlstatement, ast); 548 lcnextsqlstatement = null; 549 continue; 550 } 551 }//case 552 553 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 554 gcurrentsqlstatement = lcnextsqlstatement; 555 appendToken(gcurrentsqlstatement, ast); 556 557 switch (gcurrentsqlstatement.sqlstatementtype) { 558 case sstcreateprocedure: 559 case sstcreatefunction: 560 case sstcreatetrigger: 561 case sstalterprocedure: 562 case sstalterfunction: 563 case sstaltertrigger: { 564 gst = EFindSqlStateType.ststoredprocedure; 565 break; 566 } 567 default: { 568 gst = EFindSqlStateType.stsql; 569 break; 570 } 571 } // case 572 573 }//TBaseType.assigned(lcnextsqlstatement) 574 else if ((ast.tokencode == TBaseType.rrw_begin)) { 575 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 576 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) { 577 // start block of if ( || while statement 578 gst = EFindSqlStateType.stblock; 579 lcblocklevel = 0; 580 appendToken(gcurrentsqlstatement, ast); 581 } else { 582 appendToken(gcurrentsqlstatement, ast); 583 } 584 } else if ((ast.tokencode == TBaseType.rrw_case)) { 585 case_end_nest++; 586 appendToken(gcurrentsqlstatement, ast); 587 } else if ((ast.tokencode == TBaseType.rrw_end)) { 588 if (case_end_nest > 0) { 589 case_end_nest--; 590 } 591 appendToken(gcurrentsqlstatement, ast); 592 } else if ((ast.tokencode == TBaseType.rrw_else)) { 593 appendToken(gcurrentsqlstatement, ast); 594 // if ( cond stmt ^else stmt 595 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 596 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) && (case_end_nest == 0)) 597 gcurrentsqlstatement.dummytag = 1; // reduce to 1 while stmt after else is found: if ( cond stmt ^else stmt 598 } else { 599 appendToken(gcurrentsqlstatement, ast); 600 } 601 break; 602 } 603 case ststoredprocedure: { 604 if (TBaseType.assigned(lcnextsqlstatement)) { 605 606 607 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstalterfunction) 608 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) 609 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction) 610 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger) 611 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstalterprocedure) 612 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstaltertrigger)) { 613 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 614 gcurrentsqlstatement = lcnextsqlstatement; 615 appendToken(gcurrentsqlstatement, ast); 616 gst = EFindSqlStateType.ststoredprocedure; 617 break; 618 } else { 619 gst = EFindSqlStateType.ststoredprocedurebody; 620 appendToken(gcurrentsqlstatement, ast); 621 622 lcnextsqlstatement = null; 623 } 624 } 625 626 if (gst == EFindSqlStateType.ststoredprocedure) { 627 appendToken(gcurrentsqlstatement, ast); 628 if (ast.tokencode == TBaseType.rrw_begin) { 629 gst = EFindSqlStateType.ststoredprocedurebody; 630 } else if (ast.tokencode == TBaseType.rrw_hana_header) { 631 // HANA-specific: HEADER keyword starts procedure body 632 gst = EFindSqlStateType.ststoredprocedurebody; 633 } 634 } 635 break; 636 } //stplsql 637 case ststoredprocedurebody: { 638 if (TBaseType.assigned(lcnextsqlstatement)) { 639 switch (lcnextsqlstatement.sqlstatementtype) { 640 case sstmssqlgo: { 641 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 642 gcurrentsqlstatement = lcnextsqlstatement; 643 appendToken(gcurrentsqlstatement, ast); 644 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 645 gst = EFindSqlStateType.stnormal; 646 break; 647 } 648 case sstcreateprocedure: 649 case sstcreatefunction: 650 case sstcreatetrigger: 651 case sstalterprocedure: 652 case sstalterfunction: 653 case sstaltertrigger: { 654 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 655 gcurrentsqlstatement = lcnextsqlstatement; 656 appendToken(gcurrentsqlstatement, ast); 657 gst = EFindSqlStateType.ststoredprocedure; 658 break; 659 } 660 default: { 661 lcnextsqlstatement = null; 662 break; 663 } 664 }//case 665 } 666 667 if (gst == EFindSqlStateType.ststoredprocedurebody) 668 appendToken(gcurrentsqlstatement, ast); 669 } 670 break; 671 } //case 672 } //for 673 674 675 //last statement 676 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 677 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder); 678 } 679 680 // Populate builder with extracted statements 681 builder.sqlStatements(this.sqlstatements); 682 builder.errorCode(0); 683 builder.errorMessage(""); 684 } 685 686 /** 687 * Helper method to append a token to a statement. 688 * <p> 689 * Sets the token's statement reference and adds it to the statement's token list. 690 */ 691 private void appendToken(TCustomSqlStatement statement, TSourceToken token) { 692 if (statement == null || token == null) { 693 return; 694 } 695 token.stmt = statement; 696 statement.sourcetokenlist.add(token); 697 } 698 699 // ========== Phase 4: Statement Parsing ========== 700 701 @Override 702 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 703 TCustomParser secondaryParser, TSourceTokenList tokens, 704 TStatementList rawStatements) { 705 // Store references 706 this.fparser = (TParserHana) parser; 707 this.sourcetokenlist = tokens; 708 this.parserContext = context; 709 this.sqlstatements = rawStatements; 710 711 // Initialize sqlcmds 712 if (this.sqlcmds == null) { 713 this.sqlcmds = SqlCmdsFactory.get(vendor); 714 } 715 this.fparser.sqlcmds = this.sqlcmds; 716 717 // Initialize global context using inherited method 718 initializeGlobalContext(); 719 720 // Parse each statement 721 for (int i = 0; i < sqlstatements.size(); i++) { 722 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 723 try { 724 stmt.setFrameStack(frameStack); 725 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 726 727 // Error recovery 728 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 729 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 730 handleCreateTableErrorRecovery(stmt); 731 } 732 733 // Collect errors 734 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 735 copyErrorsFromStatement(stmt); 736 } 737 } catch (Exception ex) { 738 // Use inherited exception handler 739 handleStatementParsingException(stmt, i, ex); 740 continue; 741 } 742 } 743 744 // Clean up frame stack 745 if (globalFrame != null) { 746 globalFrame.popMeFromStack(frameStack); 747 } 748 749 return sqlstatements; 750 } 751 752 /** 753 * Handle CREATE TABLE error recovery. 754 * <p> 755 * Migrated from TGSqlParser.doparse() error recovery logic (lines 16914-16971). 756 * <p> 757 * Attempts to recover from parsing errors in CREATE TABLE statements by 758 * marking unparseable table properties as sqlpluscmd and retrying. 759 * This allows parsing CREATE TABLE statements with vendor-specific extensions 760 * that may not be in the grammar. 761 * <p> 762 * <b>Note:</b> HANA uses the generic error recovery logic without vendor-specific 763 * property validation (unlike Oracle which checks searchOracleTablePros). 764 */ 765 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 766 // Check if this is a CREATE TABLE or CREATE INDEX statement 767 if (!(stmt.sqlstatementtype == ESqlStatementType.sstcreatetable || 768 stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) { 769 return; 770 } 771 772 // Check if strict parsing is disabled 773 if (TBaseType.c_createTableStrictParsing) { 774 return; 775 } 776 777 TCustomSqlStatement errorSqlStatement = stmt; 778 779 int nested = 0; 780 boolean isIgnore = false; 781 boolean isFoundIgnoreToken = false; 782 TSourceToken firstIgnoreToken = null; 783 784 // Iterate through tokens to find the closing parenthesis of table definition 785 for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) { 786 TSourceToken st = errorSqlStatement.sourcetokenlist.get(k); 787 788 if (isIgnore) { 789 // We're past the table definition, mark tokens as ignoreable 790 if (st.issolidtoken() && (st.tokencode != ';')) { 791 isFoundIgnoreToken = true; 792 if (firstIgnoreToken == null) { 793 firstIgnoreToken = st; 794 } 795 } 796 // Mark all tokens (except semicolon) as sqlpluscmd to ignore them 797 if (st.tokencode != ';') { 798 st.tokencode = TBaseType.sqlpluscmd; 799 } 800 continue; 801 } 802 803 // Track closing parentheses 804 if (st.tokencode == (int) ')') { 805 nested--; 806 if (nested == 0) { 807 // Found the closing parenthesis of table definition 808 // Check if next tokens are "AS ( SELECT" (CTAS pattern) 809 boolean isSelect = false; 810 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 811 if (st1 != null) { 812 TSourceToken st2 = st.searchToken((int) '(', 2); 813 if (st2 != null) { 814 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 815 isSelect = (st3 != null); 816 } 817 } 818 // If not a CTAS, start ignoring subsequent tokens 819 if (!isSelect) { 820 isIgnore = true; 821 } 822 } 823 } 824 825 // Track opening parentheses 826 if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 827 nested++; 828 } 829 } 830 831 // If we found ignoreable tokens, clear errors and retry parsing 832 // Note: HANA uses generic recovery without vendor-specific property validation 833 // (unlike Oracle at line 16963 which checks searchOracleTablePros) 834 if (isFoundIgnoreToken) { 835 errorSqlStatement.clearError(); 836 int retryResult = errorSqlStatement.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree()); 837 } 838 } 839 840 // ========== Phase 5: Semantic Analysis ========== 841 842 @Override 843 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 844 if (!TBaseType.isEnableResolver()) { 845 return; 846 } 847 848 // Only run resolver if there are no syntax errors 849 if (getSyntaxErrors().isEmpty()) { 850 TSQLResolver resolver = new TSQLResolver(this.globalContext, this.sqlstatements); 851 resolver.resolve(); 852 } 853 } 854}