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 * HANA document-store JSON literals: the lexer scans a '{' ... '}' region 171 * as ONE identifier token but stops at the FIRST closing brace, leaving 172 * the extra closing braces of nested objects behind as bare error tokens 173 * (e.g. INSERT INTO c VALUES( {"k1" : { "k2" : 30 } } ) yields 174 * [{"k1" : { "k2" : 30 }] followed by an orphan '}'). Hide exactly the 175 * unbalanced closing braces so the blob parses as a single value. 176 */ 177 @Override 178 protected void processTokensBeforeParse(ParserContext context, TSourceTokenList tokens) { 179 super.processTokensBeforeParse(context, tokens); 180 int pendingCloseBraces = 0; 181 for (int i = 0; i < tokens.size(); i++) { 182 TSourceToken st = tokens.get(i); 183 if (st.tokentype == ETokenType.ttwhitespace || st.tokentype == ETokenType.ttreturn 184 || st.tokentype == ETokenType.ttsimplecomment || st.tokentype == ETokenType.ttbracketedcomment) { 185 continue; 186 } 187 String text = st.toString(); 188 if (st.tokentype == ETokenType.ttidentifier && !text.isEmpty() && text.charAt(0) == '{') { 189 int depth = 0; 190 for (int k = 0; k < text.length(); k++) { 191 char c = text.charAt(k); 192 if (c == '{') depth++; 193 else if (c == '}') depth--; 194 } 195 if (depth > 0) { 196 pendingCloseBraces += depth; 197 } 198 continue; 199 } 200 if (pendingCloseBraces > 0 && "}".equals(text)) { 201 st.tokentype = ETokenType.ttsimplecomment; 202 st.tokencode = TBaseType.cmtdoublehyphen; 203 pendingCloseBraces--; 204 continue; 205 } 206 if (st.tokentype == ETokenType.ttsemicolon) { 207 pendingCloseBraces = 0; 208 } 209 } 210 } 211 212 /** 213 * Extract raw SQL statements from the token list. 214 * <p> 215 * Migrated from TGSqlParser.dohanagetrawsqlstatements() (lines 14063-14657). 216 * <p> 217 * Handles HANA-specific token transformations: 218 * <ul> 219 * <li>MINUS keyword disambiguation</li> 220 * <li>MERGE vs MERGE JOIN detection</li> 221 * <li>AS ... OF construct detection</li> 222 * <li>DATE/TIME/TIMESTAMP constants</li> 223 * <li>WITH keyword variants (WITH STRUCTURED, WITH CACHE, WITH CHECK, etc.)</li> 224 * <li>UNLOAD vs UNLOAD PRIORITY</li> 225 * <li>Stored procedure body detection (HEADER keyword)</li> 226 * </ul> 227 */ 228 private void dohanagetrawsqlstatements(SqlParseResult.Builder builder) { 229 int errorcount = 0; 230 int case_end_nest = 0; 231 232 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 233 if (!TBaseType.assigned(sourcetokenlist)) return; 234 235 gcurrentsqlstatement = null; 236 EFindSqlStateType gst = EFindSqlStateType.stnormal; 237 int lcblocklevel = 0; 238 int lctrycatchlevel = 0; 239 TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken; 240 TSourceToken ast = null; 241 int i, lcMergeInSelectNested = 0; 242 boolean lcisendconversation, lcstillinsql, lcMergeInSelect = false; 243 244 for (i = 0; i < sourcetokenlist.size(); i++) { 245 246 if ((ast != null) && (ast.issolidtoken())) 247 lcprevsolidtoken = ast; 248 249 ast = sourcetokenlist.get(i); 250 sourcetokenlist.curpos = i; 251 252 if (lcMergeInSelect) { 253 if (ast.tokencode == '(') lcMergeInSelectNested++; 254 if (ast.tokencode == ')') { 255 lcMergeInSelectNested--; 256 if (lcMergeInSelectNested == 0) { 257 lcMergeInSelect = false; 258 } 259 } 260 appendToken(gcurrentsqlstatement, ast); 261 continue; 262 } 263 if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) { 264 //tsignoredbygetrawstatement is set when cte is found in dbcmds.findcte function 265 appendToken(gcurrentsqlstatement, ast); 266 continue; 267 } 268 269 // HANA-specific token transformations 270 if (ast.tokencode == TBaseType.rrw_minus) { 271 TSourceToken st1 = ast.searchToken('(', 1); 272 if (st1 == null) { 273 st1 = ast.searchToken(TBaseType.rrw_select, 1); 274 if (st1 == null) { 275 ast.tokencode = TBaseType.ident; 276 } 277 } 278 } else if (ast.tokencode == TBaseType.rrw_merge) { 279 TSourceToken st1 = ast.nextSolidToken(); 280 if (st1.tokencode == TBaseType.rrw_join) { 281 ast.tokencode = TBaseType.rrw_merge2_sqlserver; 282 } 283 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '(')) { 284 lcMergeInSelect = true; 285 lcMergeInSelectNested++; 286 appendToken(gcurrentsqlstatement, ast); 287 continue; 288 } 289 } else if (ast.tokencode == TBaseType.rrw_as) { 290 TSourceToken st1 = ast.nextSolidToken(); 291 if (st1 != null && st1.tokencode == TBaseType.rrw_hana_of) { 292 ast.tokencode = TBaseType.rrw_as_before_of; 293 } 294 } else if (ast.tokencode == TBaseType.rrw_date) { 295 TSourceToken st1 = ast.nextSolidToken(); 296 if (st1 != null && st1.tokencode == TBaseType.sconst) { 297 ast.tokencode = TBaseType.rrw_hana_date_const; 298 } 299 } else if (ast.tokencode == TBaseType.rrw_time) { 300 TSourceToken st1 = ast.nextSolidToken(); 301 if (st1 != null && st1.tokencode == TBaseType.sconst) { 302 ast.tokencode = TBaseType.rrw_hana_time_const; 303 } 304 } else if (ast.tokencode == TBaseType.rrw_timestamp) { 305 TSourceToken st1 = ast.nextSolidToken(); 306 if (st1 != null && st1.tokencode == TBaseType.sconst) { 307 ast.tokencode = TBaseType.rrw_hana_timestamp_const; 308 } 309 } else if (ast.tokencode == TBaseType.rrw_with) { 310 TSourceToken st1 = ast.nextSolidToken(); 311 if (st1.toString().equalsIgnoreCase("structured")) { 312 ast.tokencode = TBaseType.rrw_hana_with_structured; 313 } else if (st1.toString().equalsIgnoreCase("cache")) { 314 ast.tokencode = TBaseType.rrw_hana_with_cache; 315 } else if (st1.toString().equalsIgnoreCase("static")) { 316 ast.tokencode = TBaseType.rrw_hana_with_cache; 317 } else if (st1.toString().equalsIgnoreCase("dynamic")) { 318 ast.tokencode = TBaseType.rrw_hana_with_cache; 319 } else if (st1.toString().equalsIgnoreCase("check")) { 320 ast.tokencode = TBaseType.rrw_hana_with_check; 321 } else if (st1.toString().equalsIgnoreCase("mask")) { 322 ast.tokencode = TBaseType.rrw_hana_with_mask; 323 } else if (st1.toString().equalsIgnoreCase("expression")) { 324 ast.tokencode = TBaseType.rrw_hana_with_expression; 325 } else if (st1.toString().equalsIgnoreCase("anonymization")) { 326 ast.tokencode = TBaseType.rrw_hana_with_anonymization; 327 } else if (st1.toString().equalsIgnoreCase("hint")) { 328 ast.tokencode = TBaseType.rrw_hana_with_hint; 329 } else if (st1.toString().equalsIgnoreCase("range_restriction")) { 330 ast.tokencode = TBaseType.rrw_hana_with_range_restriction; 331 } 332 } else if (ast.tokencode == TBaseType.rrw_hana_unload) { 333 TSourceToken st1 = ast.nextSolidToken(); 334 if (st1.toString().equalsIgnoreCase("priority")) { 335 ast.tokencode = TBaseType.rrw_hana_unload2; 336 } 337 } 338 339 if (gst == EFindSqlStateType.ststoredprocedurebody) { 340 if (!((ast.tokencode == TBaseType.rrw_go) 341 || (ast.tokencode == TBaseType.rrw_create) 342 || (ast.tokencode == TBaseType.rrw_alter))) { 343 appendToken(gcurrentsqlstatement, ast); 344 continue; 345 } 346 } 347 348 TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 349 350 switch (gst) { 351 case sterror: { 352 if (TBaseType.assigned(lcnextsqlstatement)) { 353 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 354 gcurrentsqlstatement = lcnextsqlstatement; 355 appendToken(gcurrentsqlstatement, ast); 356 gst = EFindSqlStateType.stsql; 357 } else if ((ast.tokentype == ETokenType.ttsemicolon)) { 358 appendToken(gcurrentsqlstatement, ast); 359 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 360 gst = EFindSqlStateType.stnormal; 361 } else { 362 appendToken(gcurrentsqlstatement, ast); 363 } 364 break; 365 } 366 case stnormal: { 367 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 368 || (ast.tokencode == TBaseType.cmtslashstar) 369 || (ast.tokencode == TBaseType.lexspace) 370 || (ast.tokencode == TBaseType.lexnewline) 371 || (ast.tokentype == ETokenType.ttsemicolon)) { 372 if (TBaseType.assigned(gcurrentsqlstatement)) { 373 appendToken(gcurrentsqlstatement, ast); 374 } 375 376 if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) { 377 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 378 // ;;;; continuous semicolon,treat it as comment 379 ast.tokentype = ETokenType.ttsimplecomment; 380 ast.tokencode = TBaseType.cmtdoublehyphen; 381 } else { 382 } 383 384 } 385 386 continue; 387 } 388 389 gcurrentsqlstatement = lcnextsqlstatement; //isstoredprocedure(ast,dbvendor,gst,sqlstatements); 390 391 if (TBaseType.assigned(gcurrentsqlstatement)) { 392 switch (gcurrentsqlstatement.sqlstatementtype) { 393 case sstcreateprocedure: 394 case sstcreatefunction: 395 case sstcreatetrigger: 396 case ssthanacreatelibrary: 397 case ssthanaalterlibrary: 398 // case sstalterprocedure: 399 // case sstalterfunction: 400 case sstaltertrigger: { 401 appendToken(gcurrentsqlstatement, ast); 402 gst = EFindSqlStateType.ststoredprocedure; 403 break; 404 } 405 case sstmssqlgo: { 406 appendToken(gcurrentsqlstatement, ast); 407 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 408 gst = EFindSqlStateType.stnormal; 409 break; 410 } 411 default: { 412 appendToken(gcurrentsqlstatement, ast); 413 gst = EFindSqlStateType.stsql; 414 break; 415 } 416 } // case 417 } else { 418 if (ast.tokencode == TBaseType.rrw_begin) { 419 gcurrentsqlstatement = new TMssqlBlock(vendor); 420 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 421 appendToken(gcurrentsqlstatement, ast); 422 gst = EFindSqlStateType.stblock; 423 } else { 424 if (sqlstatements.size() == 0) { 425 //first statement of mssql batch, treat it as exec sp 426 gst = EFindSqlStateType.stsql; 427 gcurrentsqlstatement = new TMssqlExecute(vendor); 428 //tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 429// todo need previous line need to be implemented 430 appendToken(gcurrentsqlstatement, ast); 431 } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) { 432 // prev sql is go, treat it as exec sp 433 gst = EFindSqlStateType.stsql; 434 gcurrentsqlstatement = new TMssqlExecute(vendor); 435 //todo need to be implemented: tmssqlexecute(gcurrentsqlstatement).exectype = metnoexeckeyword; 436 appendToken(gcurrentsqlstatement, ast); 437 } else { 438 } 439 } 440 } 441 442 443 if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found 444 { 445 446// errormessage = "error when tokenlize:"+ast.astext+"("+ast.lineNo +","+ast.columnNo +")"; 447// errorcount = 1; 448 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 449 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 450 451 ast.tokentype = ETokenType.tttokenlizererrortoken; 452 gst = EFindSqlStateType.sterror; 453 454 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 455 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 456 appendToken(gcurrentsqlstatement, ast); 457 } 458 break; 459 } 460 case stblock: { 461 if (TBaseType.assigned(lcnextsqlstatement)) { 462 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 463 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 464 gcurrentsqlstatement = lcnextsqlstatement; 465 appendToken(gcurrentsqlstatement, ast); 466 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 467 gst = EFindSqlStateType.stnormal; 468 } else { 469 lcnextsqlstatement = null; 470 } 471 } 472 473 if ((lcblocklevel == -1) && (ast.tokencode == ';')) { 474 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 475 gst = EFindSqlStateType.stnormal; 476 break; 477 } 478 479 if (gst == EFindSqlStateType.stblock) { 480 appendToken(gcurrentsqlstatement, ast); 481 if (ast.tokencode == TBaseType.rrw_begin) { 482 // { [distributed] transaxtion/trans statement 483 // { dialog [ conversation ] 484 // { conversation timer 485 // doesn't start block ({ .. }) 486 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 487 if (TBaseType.assigned(lcnextsolidtoken)) { 488 if (!(TBaseType.mysametext(lcnextsolidtoken.getAstext(), "tran") 489 || TBaseType.mysametext(lcnextsolidtoken.getAstext(), "transaction"))) { 490 lcblocklevel++; 491 } 492 } else 493 lcblocklevel++; 494 495 } else if (ast.tokencode == TBaseType.rrw_case) // case ... } 496 lcblocklevel++; 497 else if (ast.tokencode == TBaseType.rrw_end) { 498 499 lcisendconversation = false; 500 501 502 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 503 504 if (!lcisendconversation) { 505 506 if (lcblocklevel == 0) { 507 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 508 if (TBaseType.assigned(lcnextsolidtoken)) { 509 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 510 // { .. } else 511 gst = EFindSqlStateType.stsql; 512 } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) { 513 lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false); 514 if (TBaseType.assigned(lcnnextsolidtoken)) { 515 if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) { 516 // { .. } else 517 gst = EFindSqlStateType.stsql; 518 } 519 } 520 } 521 } 522 } 523 524// if ( gst != EFindSqlStateType.stsql ) 525// { 526// onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 527// gst = EFindSqlStateType.stnormal; 528// } 529 530 } else { 531 lcblocklevel--; 532 } 533 534 } 535 } 536 } 537 break; 538 } 539 case stsql: { 540 if ((ast.tokentype == ETokenType.ttsemicolon)) { 541 lcstillinsql = false; 542 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 543 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 544 if (TBaseType.assigned(lcnextsolidtoken)) { 545 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 546 // if ( expr stmt; else 547 appendToken(gcurrentsqlstatement, ast); 548 lcstillinsql = true; 549 } 550 551 } 552 } 553 554 if (!lcstillinsql) { 555 gst = EFindSqlStateType.stnormal; 556 appendToken(gcurrentsqlstatement, ast); 557 gcurrentsqlstatement.semicolonended = ast; 558 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 559 } 560 561 } else if (TBaseType.assigned(lcnextsqlstatement)) { 562 563 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 564 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 565 gcurrentsqlstatement = lcnextsqlstatement; 566 appendToken(gcurrentsqlstatement, ast); 567 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 568 gst = EFindSqlStateType.stnormal; 569 continue; 570 } 571 572 switch (gcurrentsqlstatement.sqlstatementtype) { 573 case sstmssqlif: 574 case sstmssqlwhile: { 575 // if ( || while only contain one sql(not closed by {/} pair) 576 // so will still in if ( || while statement 577 if (gcurrentsqlstatement.dummytag == 1) { 578 // if ( cond ^stmt nextstmt (^ stands for current pos) 579 appendToken(gcurrentsqlstatement, ast); 580 581 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 582 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) 583 gcurrentsqlstatement.dummytag = 1; 584 else 585 gcurrentsqlstatement.dummytag = 0; 586 587 588 lcnextsqlstatement = null; 589 continue; 590 } 591 break; 592 }// 593 case sstcreateschema: { 594 appendToken(gcurrentsqlstatement, ast); 595 lcnextsqlstatement = null; 596 continue; 597 } 598 }//case 599 600 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 601 gcurrentsqlstatement = lcnextsqlstatement; 602 appendToken(gcurrentsqlstatement, ast); 603 604 switch (gcurrentsqlstatement.sqlstatementtype) { 605 case sstcreateprocedure: 606 case sstcreatefunction: 607 case sstcreatetrigger: 608 case ssthanacreatelibrary: 609 case ssthanaalterlibrary: 610 case sstalterprocedure: 611 case sstalterfunction: 612 case sstaltertrigger: { 613 gst = EFindSqlStateType.ststoredprocedure; 614 break; 615 } 616 default: { 617 gst = EFindSqlStateType.stsql; 618 break; 619 } 620 } // case 621 622 }//TBaseType.assigned(lcnextsqlstatement) 623 else if ((ast.tokencode == TBaseType.rrw_begin)) { 624 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 625 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) { 626 // start block of if ( || while statement 627 gst = EFindSqlStateType.stblock; 628 lcblocklevel = 0; 629 appendToken(gcurrentsqlstatement, ast); 630 } else { 631 appendToken(gcurrentsqlstatement, ast); 632 } 633 } else if ((ast.tokencode == TBaseType.rrw_case)) { 634 case_end_nest++; 635 appendToken(gcurrentsqlstatement, ast); 636 } else if ((ast.tokencode == TBaseType.rrw_end)) { 637 if (case_end_nest > 0) { 638 case_end_nest--; 639 } 640 appendToken(gcurrentsqlstatement, ast); 641 } else if ((ast.tokencode == TBaseType.rrw_else)) { 642 appendToken(gcurrentsqlstatement, ast); 643 // if ( cond stmt ^else stmt 644 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 645 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) && (case_end_nest == 0)) 646 gcurrentsqlstatement.dummytag = 1; // reduce to 1 while stmt after else is found: if ( cond stmt ^else stmt 647 } else { 648 appendToken(gcurrentsqlstatement, ast); 649 } 650 break; 651 } 652 case ststoredprocedure: { 653 if (TBaseType.assigned(lcnextsqlstatement)) { 654 655 656 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstalterfunction) 657 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure) 658 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction) 659 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger) 660 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstalterprocedure) 661 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstaltertrigger)) { 662 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 663 gcurrentsqlstatement = lcnextsqlstatement; 664 appendToken(gcurrentsqlstatement, ast); 665 gst = EFindSqlStateType.ststoredprocedure; 666 break; 667 } else { 668 gst = EFindSqlStateType.ststoredprocedurebody; 669 appendToken(gcurrentsqlstatement, ast); 670 671 lcnextsqlstatement = null; 672 } 673 } 674 675 if (gst == EFindSqlStateType.ststoredprocedure) { 676 appendToken(gcurrentsqlstatement, ast); 677 if (ast.tokencode == TBaseType.rrw_begin) { 678 gst = EFindSqlStateType.ststoredprocedurebody; 679 } else if (ast.tokencode == TBaseType.rrw_hana_header) { 680 // HANA-specific: HEADER keyword starts procedure body 681 gst = EFindSqlStateType.ststoredprocedurebody; 682 } 683 } 684 break; 685 } //stplsql 686 case ststoredprocedurebody: { 687 if (TBaseType.assigned(lcnextsqlstatement)) { 688 switch (lcnextsqlstatement.sqlstatementtype) { 689 case sstmssqlgo: { 690 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 691 gcurrentsqlstatement = lcnextsqlstatement; 692 appendToken(gcurrentsqlstatement, ast); 693 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 694 gst = EFindSqlStateType.stnormal; 695 break; 696 } 697 case sstcreateprocedure: 698 case sstcreatefunction: 699 case sstcreatetrigger: 700 case ssthanacreatelibrary: 701 case ssthanaalterlibrary: 702 case sstalterprocedure: 703 case sstalterfunction: 704 case sstaltertrigger: { 705 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder); 706 gcurrentsqlstatement = lcnextsqlstatement; 707 appendToken(gcurrentsqlstatement, ast); 708 gst = EFindSqlStateType.ststoredprocedure; 709 break; 710 } 711 default: { 712 lcnextsqlstatement = null; 713 break; 714 } 715 }//case 716 } 717 718 if (gst == EFindSqlStateType.ststoredprocedurebody) 719 appendToken(gcurrentsqlstatement, ast); 720 } 721 break; 722 } //case 723 } //for 724 725 726 //last statement 727 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 728 onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder); 729 } 730 731 // Populate builder with extracted statements 732 builder.sqlStatements(this.sqlstatements); 733 builder.errorCode(0); 734 builder.errorMessage(""); 735 } 736 737 /** 738 * Helper method to append a token to a statement. 739 * <p> 740 * Sets the token's statement reference and adds it to the statement's token list. 741 */ 742 private void appendToken(TCustomSqlStatement statement, TSourceToken token) { 743 if (statement == null || token == null) { 744 return; 745 } 746 token.stmt = statement; 747 statement.sourcetokenlist.add(token); 748 } 749 750 // ========== Phase 4: Statement Parsing ========== 751 752 @Override 753 protected TStatementList performParsing(ParserContext context, TCustomParser parser, 754 TCustomParser secondaryParser, TSourceTokenList tokens, 755 TStatementList rawStatements) { 756 // Store references 757 this.fparser = (TParserHana) parser; 758 this.sourcetokenlist = tokens; 759 this.parserContext = context; 760 this.sqlstatements = rawStatements; 761 762 // Initialize sqlcmds 763 if (this.sqlcmds == null) { 764 this.sqlcmds = SqlCmdsFactory.get(vendor); 765 } 766 this.fparser.sqlcmds = this.sqlcmds; 767 768 // Initialize global context using inherited method 769 initializeGlobalContext(); 770 771 // Parse each statement 772 for (int i = 0; i < sqlstatements.size(); i++) { 773 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 774 try { 775 stmt.setFrameStack(frameStack); 776 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 777 778 // Error recovery 779 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 780 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 781 handleCreateTableErrorRecovery(stmt); 782 } 783 784 // Collect errors 785 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 786 copyErrorsFromStatement(stmt); 787 } 788 } catch (Exception ex) { 789 // Use inherited exception handler 790 handleStatementParsingException(stmt, i, ex); 791 continue; 792 } 793 } 794 795 // Clean up frame stack 796 if (globalFrame != null) { 797 globalFrame.popMeFromStack(frameStack); 798 } 799 800 return sqlstatements; 801 } 802 803 /** 804 * Handle CREATE TABLE error recovery. 805 * <p> 806 * Migrated from TGSqlParser.doparse() error recovery logic (lines 16914-16971). 807 * <p> 808 * Attempts to recover from parsing errors in CREATE TABLE statements by 809 * marking unparseable table properties as sqlpluscmd and retrying. 810 * This allows parsing CREATE TABLE statements with vendor-specific extensions 811 * that may not be in the grammar. 812 * <p> 813 * <b>Note:</b> HANA uses the generic error recovery logic without vendor-specific 814 * property validation (unlike Oracle which checks searchOracleTablePros). 815 */ 816 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 817 // Check if this is a CREATE TABLE or CREATE INDEX statement 818 if (!(stmt.sqlstatementtype == ESqlStatementType.sstcreatetable || 819 stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) { 820 return; 821 } 822 823 // Check if strict parsing is disabled 824 if (TBaseType.c_createTableStrictParsing) { 825 return; 826 } 827 828 TCustomSqlStatement errorSqlStatement = stmt; 829 830 int nested = 0; 831 boolean isIgnore = false; 832 boolean isFoundIgnoreToken = false; 833 TSourceToken firstIgnoreToken = null; 834 835 // Iterate through tokens to find the closing parenthesis of table definition 836 for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) { 837 TSourceToken st = errorSqlStatement.sourcetokenlist.get(k); 838 839 if (isIgnore) { 840 // We're past the table definition, mark tokens as ignoreable 841 if (st.issolidtoken() && (st.tokencode != ';')) { 842 isFoundIgnoreToken = true; 843 if (firstIgnoreToken == null) { 844 firstIgnoreToken = st; 845 } 846 } 847 // Mark all tokens (except semicolon) as sqlpluscmd to ignore them 848 if (st.tokencode != ';') { 849 st.tokencode = TBaseType.sqlpluscmd; 850 } 851 continue; 852 } 853 854 // Track closing parentheses 855 if (st.tokencode == (int) ')') { 856 nested--; 857 if (nested == 0) { 858 // Found the closing parenthesis of table definition 859 // Check if next tokens are "AS ( SELECT" (CTAS pattern) 860 boolean isSelect = false; 861 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 862 if (st1 != null) { 863 TSourceToken st2 = st.searchToken((int) '(', 2); 864 if (st2 != null) { 865 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 866 isSelect = (st3 != null); 867 } 868 } 869 // If not a CTAS, start ignoring subsequent tokens 870 if (!isSelect) { 871 isIgnore = true; 872 } 873 } 874 } 875 876 // Track opening parentheses 877 if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) { 878 nested++; 879 } 880 } 881 882 // If we found ignoreable tokens, clear errors and retry parsing 883 // Note: HANA uses generic recovery without vendor-specific property validation 884 // (unlike Oracle at line 16963 which checks searchOracleTablePros) 885 if (isFoundIgnoreToken) { 886 errorSqlStatement.clearError(); 887 int retryResult = errorSqlStatement.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree()); 888 } 889 } 890 891 // ========== Phase 5: Semantic Analysis ========== 892 893 @Override 894 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 895 if (!TBaseType.isEnableResolver()) { 896 return; 897 } 898 899 // Only run resolver if there are no syntax errors 900 if (getSyntaxErrors().isEmpty()) { 901 TSQLResolver resolver = new TSQLResolver(this.globalContext, this.sqlstatements); 902 resolver.resolve(); 903 } 904 } 905}