001package gudusoft.gsqlparser.parser;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.TGSqlParser;
006import gudusoft.gsqlparser.TSourceTokenList;
007import gudusoft.gsqlparser.TStatementList;
008
009/**
010 * Trino SQL parser implementation.
011 *
012 * <p><b>Implementation Status:</b> DELEGATION PHASE
013 * <ul>
014 *   <li><b>Current:</b> Delegates to legacy TGSqlParser</li>
015 *   <li><b>Future:</b> Will extract Trino-specific logic incrementally</li>
016 * </ul>
017 *
018 * @see SqlParser
019 * @see SqlParserFactory
020 * @since 3.2.0.0
021 */
022public class TrinoSqlParser implements SqlParser {
023
024    private final EDbVendor vendor = EDbVendor.dbvtrino;
025
026    @Override
027    public EDbVendor getVendor() {
028        return vendor;
029    }
030
031    @Override
032    public SqlParseResult parse(ParserContext context) {
033        return delegateToLegacy(context, true);
034    }
035
036    @Override
037    public SqlParseResult tokenize(ParserContext context) {
038        return delegateToLegacy(context, false);
039    }
040
041    private SqlParseResult delegateToLegacy(ParserContext context, boolean fullParse) {
042        SqlParseResult.Builder resultBuilder = new SqlParseResult.Builder();
043
044        try {
045            // CRITICAL: Use dbvpresto (not dbvtrino) to avoid infinite recursion.
046            // TGSqlParser(dbvtrino) -> TrinoSqlParser.tokenize() -> delegateToLegacy()
047            // would create another TGSqlParser(dbvtrino), causing StackOverflowError.
048            // Presto has the actual parser implementation that Trino delegates to.
049            TGSqlParser legacyParser = new TGSqlParser(EDbVendor.dbvpresto);
050            transferContext(legacyParser, context);
051
052            // Exactly ONE pass over the input. This used to tokenize and then
053            // parse (or raw-split) the same legacy parser, which reads the
054            // source twice -- fatal for a stream, which is not repeatable: the
055            // second pass saw EOF and reported success with zero statements.
056            long opStart = System.currentTimeMillis();
057            int result;
058            String failurePrefix;
059            if (fullParse) {
060                result = legacyParser.parse();
061                failurePrefix = "Parsing failed: ";
062                resultBuilder.parsingTimeMs(System.currentTimeMillis() - opStart);
063            } else {
064                result = legacyParser.getrawsqlstatements();
065                failurePrefix = "Tokenization failed: ";
066                resultBuilder.tokenizationTimeMs(System.currentTimeMillis() - opStart);
067            }
068            if (result != 0) {
069                resultBuilder.errorCode(result);
070                String legacyMessage = legacyParser.getErrormessage();
071                // Keep a refusal message verbatim so callers matching on it
072                // still recognise it; only decorate ordinary failures.
073                resultBuilder.errorMessage(TBaseType.isTrialSizeMessage(legacyMessage)
074                        ? legacyMessage : failurePrefix + legacyMessage);
075            } else {
076                resultBuilder.errorCode(0);
077                resultBuilder.errorMessage("");
078            }
079
080            resultBuilder.sourceTokenList(legacyParser.sourcetokenlist);
081            resultBuilder.sqlStatements(legacyParser.sqlstatements);
082            resultBuilder.lexer(legacyParser.getFlexer());
083
084        } catch (Exception e) {
085            resultBuilder.errorCode(1);
086            resultBuilder.errorMessage("Operation failed: " + e.getMessage());
087        }
088
089        return resultBuilder.build();
090    }
091
092    private void transferContext(TGSqlParser legacyParser, ParserContext context) {
093        if (context.getSqlText() != null && !context.getSqlText().isEmpty()) {
094            legacyParser.sqltext = context.getSqlText();
095        }
096        if (context.getSqlFilename() != null && !context.getSqlFilename().isEmpty()) {
097            legacyParser.sqlfilename = context.getSqlFilename();
098        }
099        if (context.getSqlCharset() != null && !context.getSqlCharset().isEmpty()) {
100            legacyParser.setSqlCharset(context.getSqlCharset());
101        }
102        if (context.getSqlInputStream() != null) {
103            // Forward the stream itself: BOM and encoding detection belong to
104            // readsql(), not here. Safe now that this adapter makes exactly one
105            // pass over the source. Without this the stream was dropped and the
106            // adapter silently parsed EMPTY input in every edition.
107            legacyParser.setSqlInputStream(context.getSqlInputStream());
108        }
109
110        legacyParser.setEnablePartialParsing(context.isEnablePartialParsing());
111        legacyParser.setSinglePLBlock(context.isSinglePLBlock());
112        legacyParser.setOnlyNeedRawParseTree(context.isOnlyNeedRawParseTree());
113        
114        if (context.getTeradataUtilityType() != null) {
115            legacyParser.setTeradataUtilityType(context.getTeradataUtilityType());
116        }
117        if (context.getMetaDatabase() != null) {
118            legacyParser.setMetaDatabase(context.getMetaDatabase());
119        }
120        if (context.getSqlEnv() != null) {
121            legacyParser.setSqlEnv(context.getSqlEnv());
122        }
123        if (context.isDumpResolverLog()) {
124            TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE = true;
125        }
126    }
127
128    @Override
129    public String toString() {
130        return "TrinoSqlParser{vendor=" + vendor + "}";
131    }
132}