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