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}