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}