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.TLexerAthena;
014import gudusoft.gsqlparser.TParserAthena;
015import gudusoft.gsqlparser.TSourceToken;
016import gudusoft.gsqlparser.TSourceTokenList;
017import gudusoft.gsqlparser.TStatementList;
018import gudusoft.gsqlparser.TSyntaxError;
019import gudusoft.gsqlparser.compiler.TASTEvaluator;
020import gudusoft.gsqlparser.compiler.TContext;
021import gudusoft.gsqlparser.compiler.TFrame;
022import gudusoft.gsqlparser.compiler.TGlobalScope;
023import gudusoft.gsqlparser.resolver.TSQLResolver;
024import gudusoft.gsqlparser.sqlcmds.ISqlCmds;
025import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
026import gudusoft.gsqlparser.sqlenv.TSQLEnv;
027import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement;
028import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
029import gudusoft.gsqlparser.stmt.oracle.TSqlplusCmdStatement;
030
031import java.util.Stack;
032
033/**
034 * AWS Athena SQL parser implementation.
035 *
036 * <p>This parser handles Athena-specific SQL syntax including:
037 * <ul>
038 *   <li>Presto/Trino-based SQL syntax</li>
039 *   <li>MySQL-style comment handling</li>
040 *   <li>PL/SQL-like procedural blocks (BEGIN/END)</li>
041 *   <li>Dynamic delimiter support</li>
042 * </ul>
043 *
044 * <p><b>Implementation Status:</b> MIGRATED
045 * <ul>
046 *   <li><b>Completed:</b> Migrated from TGSqlParser to AbstractSqlParser</li>
047 *   <li><b>Current:</b> Fully self-contained Athena parser</li>
048 * </ul>
049 *
050 * <p><b>Design Notes:</b>
051 * <ul>
052 *   <li>Extends {@link AbstractSqlParser} using template method pattern</li>
053 *   <li>Uses single parser: {@link TParserAthena}</li>
054 *   <li>Primary delimiter: semicolon (;)</li>
055 *   <li>Supports PL/SQL-like blocks with BEGIN/END</li>
056 * </ul>
057 *
058 * @see SqlParser
059 * @see AbstractSqlParser
060 * @see TLexerAthena
061 * @see TParserAthena
062 * @since 3.2.0.0
063 */
064public class AthenaSqlParser extends AbstractSqlParser {
065
066    // ========== Lexer and Parser ==========
067
068    /** The Athena lexer used for tokenization (note: lowercase 'a' in generated class name) */
069    public TLexerAthena flexer;
070
071    /** The Athena parser used for parsing */
072    private TParserAthena fparser;
073
074    // ========== Statement Extraction State ==========
075
076    /** Current statement being built during raw extraction */
077    private TCustomSqlStatement gcurrentsqlstatement;
078
079    /**
080     * Construct Athena SQL parser.
081     * <p>
082     * Configures the parser for Athena database with semicolon (;) as the default delimiter.
083     */
084    public AthenaSqlParser() {
085        super(EDbVendor.dbvathena);
086        this.delimiterChar = ';';
087        this.defaultDelimiterStr = ";";
088
089        // Create lexer once - will be reused for all parsing operations
090        this.flexer = new TLexerAthena();
091        this.flexer.delimiterchar = this.delimiterChar;
092        this.flexer.defaultDelimiterStr = this.defaultDelimiterStr;
093
094        // Set parent's lexer reference for shared tokenization logic
095        this.lexer = this.flexer;
096
097        // Create parser once - will be reused for all parsing operations
098        this.fparser = new TParserAthena(null);
099        this.fparser.lexer = this.flexer;
100    }
101
102    // ========== Abstract Method Implementations ==========
103
104    @Override
105    protected TCustomLexer getLexer(ParserContext context) {
106        return this.flexer;
107    }
108
109    @Override
110    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
111        this.fparser.sourcetokenlist = tokens;
112        return this.fparser;
113    }
114
115    @Override
116    protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) {
117        // Athena does not have a secondary parser
118        return null;
119    }
120
121    // ========== Phase 2: Tokenization (Hook Pattern) ==========
122
123    @Override
124    protected void tokenizeVendorSql() {
125        doathenatexttotokenlist();
126    }
127
128    /**
129     * Tokenize Athena SQL text into source tokens.
130     * <p>
131     * Migrated from TGSqlParser.doathenatexttotokenlist() (lines 4664-4694).
132     * <p>
133     * Handles:
134     * <ul>
135     *   <li>MySQL-style comment validation</li>
136     *   <li>Dynamic delimiter changes (like MySQL DELIMITER command)</li>
137     * </ul>
138     */
139    private void doathenatexttotokenlist() {
140        TSourceToken asourcetoken;
141        int yychar;
142        boolean startDelimiter = false;
143
144        flexer.tmpDelimiter = "";
145
146        asourcetoken = getanewsourcetoken();
147        if (asourcetoken == null) return;
148        yychar = asourcetoken.tokencode;
149
150        while (yychar > 0) {
151            sourcetokenlist.add(asourcetoken);
152            asourcetoken = getanewsourcetoken();
153            if (asourcetoken == null) break;
154            checkMySQLCommentToken(asourcetoken);
155
156            if ((asourcetoken.tokencode == TBaseType.lexnewline) && (startDelimiter)) {
157                startDelimiter = false;
158                flexer.tmpDelimiter = sourcetokenlist.get(sourcetokenlist.size() - 1).getAstext();
159            }
160
161            yychar = asourcetoken.tokencode;
162        }
163    }
164
165    /**
166     * Check MySQL-style comment tokens.
167     * <p>
168     * Migrated from TGSqlParser.checkMySQLCommentToken() (lines 4604-4619).
169     * <p>
170     * Note: This method is mostly a no-op as the validation logic is commented out
171     * in the original implementation.
172     */
173    private void checkMySQLCommentToken(TSourceToken cmtToken) {
174        // Original implementation is commented out - keeping as no-op
175        // This matches the original TGSqlParser behavior
176    }
177
178    // ========== Phase 3: Raw Statement Extraction (Hook Pattern) ==========
179
180    @Override
181    protected void setupVendorParsersForExtraction() {
182        this.fparser.sqlcmds = this.sqlcmds;
183        this.fparser.sourcetokenlist = this.sourcetokenlist;
184    }
185
186    @Override
187    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
188        doathenagetrawsqlstatements(builder);
189    }
190
191    /**
192     * Extract raw SQL statements from token list.
193     * <p>
194     * Migrated from TGSqlParser.doathenagetrawsqlstatements() (lines 6724+).
195     * <p>
196     * This method implements a state machine to identify statement boundaries:
197     * <ul>
198     *   <li>Regular SQL statements terminated by semicolon</li>
199     *   <li>PL/SQL-like blocks with BEGIN/END pairs</li>
200     *   <li>Slash (/) and period (.) terminators for procedural blocks</li>
201     * </ul>
202     */
203    private void doathenagetrawsqlstatements(SqlParseResult.Builder builder) {
204        int waitingEnd = 0;
205        boolean foundEnd = false;
206
207        if (TBaseType.assigned(sqlstatements)) sqlstatements.clear();
208        if (!TBaseType.assigned(sourcetokenlist)) {
209            builder.errorCode(-1);
210            return;
211        }
212
213        gcurrentsqlstatement = null;
214        EFindSqlStateType gst = EFindSqlStateType.stnormal;
215        TSourceToken lcprevsolidtoken = null;
216        TSourceToken ast = null;
217
218        for (int i = 0; i < sourcetokenlist.size(); i++) {
219
220            if ((ast != null) && (ast.issolidtoken()))
221                lcprevsolidtoken = ast;
222
223            ast = sourcetokenlist.get(i);
224            sourcetokenlist.curpos = i;
225
226            switch (gst) {
227                case sterror: {
228                    if (ast.tokentype == ETokenType.ttsemicolon) {
229                        appendToken(gcurrentsqlstatement, ast);
230                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
231                        gst = EFindSqlStateType.stnormal;
232                    } else {
233                        appendToken(gcurrentsqlstatement, ast);
234                    }
235                    break;
236                } // sterror
237
238                case stnormal: {
239                    if ((ast.tokencode == TBaseType.cmtdoublehyphen)
240                            || (ast.tokencode == TBaseType.cmtslashstar)
241                            || (ast.tokencode == TBaseType.lexspace)
242                            || (ast.tokencode == TBaseType.lexnewline)
243                            || (ast.tokentype == ETokenType.ttsemicolon)) {
244                        if (gcurrentsqlstatement != null) {
245                            appendToken(gcurrentsqlstatement, ast);
246                        }
247
248                        if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
249                            if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
250                                // ;;;; continuous semicolon, treat it as comment
251                                ast.tokentype = ETokenType.ttsimplecomment;
252                                ast.tokencode = TBaseType.cmtdoublehyphen;
253                            }
254                        }
255
256                        continue;
257                    }
258
259                    // find a tokentext to start sql or plsql mode
260                    gcurrentsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement);
261
262                    if (gcurrentsqlstatement != null) {
263                        if (gcurrentsqlstatement.isathenaplsql()) {
264                            gst = EFindSqlStateType.ststoredprocedure;
265                            appendToken(gcurrentsqlstatement, ast);
266                            foundEnd = false;
267                            if ((ast.tokencode == TBaseType.rrw_begin)
268                                    || (ast.tokencode == TBaseType.rrw_package)
269                                    || (ast.searchToken(TBaseType.rrw_package, 4) != null)) {
270                                waitingEnd = 1;
271                            }
272                        } else {
273                            gst = EFindSqlStateType.stsql;
274                            appendToken(gcurrentsqlstatement, ast);
275                        }
276                    } else {
277                        // error tokentext found
278                        this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo),
279                                "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
280
281                        ast.tokentype = ETokenType.tttokenlizererrortoken;
282                        gst = EFindSqlStateType.sterror;
283
284                        gcurrentsqlstatement = new TUnknownSqlStatement(vendor);
285                        gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
286                        appendToken(gcurrentsqlstatement, ast);
287                    }
288
289                    break;
290                } // stnormal
291
292                case stsql: {
293                    if (ast.tokentype == ETokenType.ttsemicolon) {
294                        gst = EFindSqlStateType.stnormal;
295                        appendToken(gcurrentsqlstatement, ast);
296                        gcurrentsqlstatement.semicolonended = ast;
297                        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
298                            System.out.println("  [RAW] Found semicolon, completing statement. Token count=" + gcurrentsqlstatement.sourcetokenlist.size());
299                        }
300                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
301                        continue;
302                    }
303
304                    if (sourcetokenlist.sqlplusaftercurtoken()) { // most probably is / cmd
305                        gst = EFindSqlStateType.stnormal;
306                        appendToken(gcurrentsqlstatement, ast);
307                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
308                        continue;
309                    }
310                    appendToken(gcurrentsqlstatement, ast);
311                    break;
312                } // case stsql
313
314                case ststoredprocedure: {
315                    if (ast.tokencode == TBaseType.rrw_begin) {
316                        waitingEnd++;
317                    } else if (ast.tokencode == TBaseType.rrw_if) {
318                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
319                            // this is not if after END
320                            waitingEnd++;
321                        }
322                    } else if (ast.tokencode == TBaseType.rrw_case) {
323                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
324                            // this is not case after END
325                            waitingEnd++;
326                        }
327                    } else if (ast.tokencode == TBaseType.rrw_loop) {
328                        if (ast.searchToken(TBaseType.rrw_end, -1) == null) {
329                            // this is not loop after END
330                            waitingEnd++;
331                        }
332                    } else if (ast.tokencode == TBaseType.rrw_end) {
333                        foundEnd = true;
334                        waitingEnd--;
335                        if (waitingEnd < 0) {
336                            waitingEnd = 0;
337                        }
338                    }
339
340                    if ((ast.tokentype == ETokenType.ttslash) && (ast.tokencode == TBaseType.sqlpluscmd)) {
341                        // Slash terminator
342                        ast.tokenstatus = ETokenStatus.tsignorebyyacc;
343                        gst = EFindSqlStateType.stnormal;
344                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
345
346                        // make / a sqlplus cmd
347                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
348                        appendToken(gcurrentsqlstatement, ast);
349                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
350                    } else if ((ast.tokentype == ETokenType.ttperiod) && (sourcetokenlist.returnaftercurtoken(false)) && (sourcetokenlist.returnbeforecurtoken(false))) {
351                        // single dot at a separate line
352                        ast.tokenstatus = ETokenStatus.tsignorebyyacc;
353                        gst = EFindSqlStateType.stnormal;
354                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
355
356                        // make ttperiod a sqlplus cmd
357                        gcurrentsqlstatement = new TSqlplusCmdStatement(vendor);
358                        appendToken(gcurrentsqlstatement, ast);
359                        onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
360                    } else {
361                        appendToken(gcurrentsqlstatement, ast);
362                        if ((ast.tokentype == ETokenType.ttsemicolon) && (waitingEnd == 0) && (foundEnd)) {
363                            gst = EFindSqlStateType.stnormal;
364                            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
365                        }
366                    }
367
368                    if (ast.tokencode == TBaseType.sqlpluscmd) {
369                        // change tokencode back to keyword or ident, because sqlplus cmd
370                        // in a sql statement (almost is plsql block) is not really a sqlplus cmd
371                        int m = flexer.getkeywordvalue(ast.getAstext());
372                        if (m != 0) {
373                            ast.tokencode = m;
374                        } else {
375                            ast.tokencode = TBaseType.ident;
376                        }
377                    }
378
379                    break;
380                } // case ststoredprocedure
381            }
382        }
383
384        // Handle incomplete statement at end of file
385        if ((gcurrentsqlstatement != null) &&
386                ((gst == EFindSqlStateType.stsql) || (gst == EFindSqlStateType.ststoredprocedure) || (gst == EFindSqlStateType.sterror))) {
387            if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
388                System.out.println("  [RAW] Incomplete statement at EOF. Token count=" + gcurrentsqlstatement.sourcetokenlist.size());
389            }
390            onRawStatementComplete(this.parserContext, gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
391        }
392
393        // Populate builder with extracted statements (CRITICAL: this was missing!)
394        builder.sqlStatements(this.sqlstatements);
395        builder.errorCode(0);
396        builder.errorMessage("");
397    }
398
399    /**
400     * Helper method to append a token to a statement.
401     * <p>
402     * Sets the token's statement reference and adds it to the statement's token list.
403     */
404    private void appendToken(TCustomSqlStatement statement, TSourceToken token) {
405        if (statement == null || token == null) {
406            return;
407        }
408        token.stmt = statement;
409        statement.sourcetokenlist.add(token);
410    }
411
412    // ========== Phase 4: Statement Parsing ==========
413
414    @Override
415    protected TStatementList performParsing(ParserContext context, TCustomParser parser,
416                                           TCustomParser secondaryParser, TSourceTokenList tokens,
417                                           TStatementList rawStatements) {
418        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
419            System.out.println("AthenaSqlParser.performParsing() CALLED with " +
420                (rawStatements != null ? rawStatements.size() : 0) + " statements");
421        }
422
423        // Store references
424        this.fparser = (TParserAthena) parser;
425        this.sourcetokenlist = tokens;
426        this.parserContext = context;
427
428        // Initialize sqlcmds
429        this.sqlcmds = SqlCmdsFactory.get(vendor);
430        this.fparser.sqlcmds = this.sqlcmds;
431
432        // Initialize global context using inherited method
433        initializeGlobalContext();
434
435        // Parse each statement
436        for (int i = 0; i < sqlstatements.size(); i++) {
437            TCustomSqlStatement stmt = sqlstatements.getRawSql(i);
438            try {
439                stmt.setFrameStack(frameStack);
440                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
441
442                // Error recovery
443                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
444                if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) {
445                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
446                        System.out.println("AthenaSqlParser: Triggering error recovery for " + stmt.sqlstatementtype);
447                        System.out.println("  parseResult=" + parseResult + ", errorCount=" + stmt.getErrorCount());
448                        if (stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) {
449                            TCreateTableSqlStatement ct = (TCreateTableSqlStatement)stmt;
450                            System.out.println("  columns before recovery: " + ct.getColumnList().size());
451                        }
452                    }
453                    handleCreateTableErrorRecovery(stmt);
454                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE && stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) {
455                        TCreateTableSqlStatement ct = (TCreateTableSqlStatement)stmt;
456                        System.out.println("  columns after recovery: " + ct.getColumnList().size());
457                    }
458                }
459
460                // Collect errors
461                if ((parseResult != 0) || (stmt.getErrorCount() > 0)) {
462                    copyErrorsFromStatement(stmt);
463                }
464            } catch (Exception ex) {
465                // Use inherited exception handler
466                handleStatementParsingException(stmt, i, ex);
467                continue;
468            }
469        }
470
471        // Clean up frame stack
472        if (globalFrame != null) {
473            globalFrame.popMeFromStack(frameStack);
474        }
475
476        // Final debug check before returning
477        if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
478            System.out.println("AthenaSqlParser: total statements = " + sqlstatements.size());
479            for (int i = 0; i < sqlstatements.size(); i++) {
480                TCustomSqlStatement stmt = sqlstatements.get(i);
481                System.out.println("  Statement[" + i + "]: " + stmt.sqlstatementtype + " @ " + System.identityHashCode(stmt));
482                if (stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) {
483                    TCreateTableSqlStatement ct = (TCreateTableSqlStatement)stmt;
484                    System.out.println("    columns = " + ct.getColumnList().size());
485                }
486            }
487        }
488
489        return sqlstatements;
490    }
491
492    /**
493     * Handle CREATE TABLE error recovery.
494     * <p>
495     * Migrated from TGSqlParser.doparse() (lines 16914-16971).
496     * <p>
497     * Attempts to recover from parsing errors in CREATE TABLE statements by
498     * marking unparseable table properties as sqlpluscmd and retrying.
499     * This allows parsing CREATE TABLE statements with vendor-specific extensions
500     * that may not be in the grammar.
501     */
502    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
503        // Check if this is a CREATE TABLE or CREATE INDEX statement
504        if (!(stmt.sqlstatementtype == ESqlStatementType.sstcreatetable ||
505              stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) {
506            return;
507        }
508
509        // Check if strict parsing is disabled
510        if (TBaseType.c_createTableStrictParsing) {
511            return;
512        }
513
514        TCustomSqlStatement errorSqlStatement = stmt;
515
516        int nested = 0;
517        boolean isIgnore = false;
518        boolean isFoundIgnoreToken = false;
519        TSourceToken firstIgnoreToken = null;
520
521        // Iterate through tokens to find the closing parenthesis of table definition
522        for (int k = 0; k < errorSqlStatement.sourcetokenlist.size(); k++) {
523            TSourceToken st = errorSqlStatement.sourcetokenlist.get(k);
524
525            if (isIgnore) {
526                // We're past the table definition, mark tokens as ignoreable
527                if (st.issolidtoken() && (st.tokencode != ';')) {
528                    isFoundIgnoreToken = true;
529                    if (firstIgnoreToken == null) {
530                        firstIgnoreToken = st;
531                    }
532                }
533                // Mark all tokens (except semicolon) as sqlpluscmd to ignore them
534                if (st.tokencode != ';') {
535                    st.tokencode = TBaseType.sqlpluscmd;
536                }
537                continue;
538            }
539
540            // Track closing parentheses
541            if (st.tokencode == (int) ')') {
542                nested--;
543                if (nested == 0) {
544                    // Found the closing parenthesis of table definition
545                    // Check if next tokens are "AS ( SELECT" (CTAS pattern)
546                    boolean isSelect = false;
547                    TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1);
548                    if (st1 != null) {
549                        TSourceToken st2 = st.searchToken((int) '(', 2);
550                        if (st2 != null) {
551                            TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3);
552                            isSelect = (st3 != null);
553                        }
554                    }
555                    // If not a CTAS, start ignoring subsequent tokens
556                    if (!isSelect) {
557                        isIgnore = true;
558                    }
559                }
560            }
561
562            // Track opening parentheses
563            if ((st.tokencode == (int) '(') || (st.tokencode == TBaseType.left_parenthesis_2)) {
564                nested++;
565            }
566        }
567
568        // For Oracle, validate that the first ignored token is a valid table property
569        // For Athena, we don't have this validation, so skip this check
570        // (Athena doesn't use searchOracleTablePros)
571
572        // If we found ignoreable tokens, clear errors and retry parsing
573        if (isFoundIgnoreToken) {
574            if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
575                System.out.println("  Found ignoreable tokens, clearing errors and re-parsing...");
576            }
577            errorSqlStatement.clearError();
578            int retryResult = errorSqlStatement.parsestatement(null, false, parserContext.isOnlyNeedRawParseTree());
579            if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
580                System.out.println("  Retry parse result: " + retryResult);
581                if (stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) {
582                    TCreateTableSqlStatement ct = (TCreateTableSqlStatement)stmt;
583                    System.out.println("  Final column count after retry: " + ct.getColumnList().size());
584                }
585            }
586        }
587    }
588
589    // ========== Phase 5: Semantic Analysis & Interpretation ==========
590
591    @Override
592    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
593        if (!TBaseType.isEnableResolver()) {
594            return;
595        }
596
597        if (getSyntaxErrors().isEmpty()) {
598            TSQLResolver resolver = new TSQLResolver(this.globalContext, statements);
599            resolver.resolve();
600        }
601    }
602
603    @Override
604    protected void performInterpreter(ParserContext context, TStatementList statements) {
605        if (!TBaseType.ENABLE_INTERPRETER) {
606            return;
607        }
608
609        if (getSyntaxErrors().isEmpty()) {
610            TGlobalScope interpreterScope = new TGlobalScope(sqlEnv);
611            TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope);
612            astEvaluator.eval();
613        }
614    }
615
616    @Override
617    public EDbVendor getVendor() {
618        return vendor;
619    }
620
621    @Override
622    public String toString() {
623        return "AthenaSqlParser{vendor=" + vendor + "}";
624    }
625}