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 * MySQL 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 MySQL-specific logic incrementally</li>
016 * </ul>
017 *
018 * @see SqlParser
019 * @see SqlParserFactory
020 * @since 3.2.0.0
021 */
022public class MySqlParser implements SqlParser {
023
024    private final EDbVendor vendor = EDbVendor.dbvmysql;
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            TGSqlParser legacyParser = new TGSqlParser(vendor);
046            transferContext(legacyParser, context);
047
048            // Exactly ONE pass over the input. This used to tokenize and then
049            // parse (or raw-split) the same legacy parser, which reads the
050            // source twice -- fatal for a stream, which is not repeatable: the
051            // second pass saw EOF and reported success with zero statements.
052            long opStart = System.currentTimeMillis();
053            int result;
054            String failurePrefix;
055            if (fullParse) {
056                result = legacyParser.parse();
057                failurePrefix = "Parsing failed: ";
058                resultBuilder.parsingTimeMs(System.currentTimeMillis() - opStart);
059            } else {
060                result = legacyParser.getrawsqlstatements();
061                failurePrefix = "Tokenization failed: ";
062                resultBuilder.tokenizationTimeMs(System.currentTimeMillis() - opStart);
063            }
064            if (result != 0) {
065                resultBuilder.errorCode(result);
066                String legacyMessage = legacyParser.getErrormessage();
067                // Keep a refusal message verbatim so callers matching on it
068                // still recognise it; only decorate ordinary failures.
069                resultBuilder.errorMessage(TBaseType.isTrialSizeMessage(legacyMessage)
070                        ? legacyMessage : failurePrefix + legacyMessage);
071            } else {
072                resultBuilder.errorCode(0);
073                resultBuilder.errorMessage("");
074            }
075
076            resultBuilder.sourceTokenList(legacyParser.sourcetokenlist);
077            resultBuilder.sqlStatements(legacyParser.sqlstatements);
078            resultBuilder.lexer(legacyParser.getFlexer());
079
080        } catch (Exception e) {
081            resultBuilder.errorCode(1);
082            resultBuilder.errorMessage("Operation failed: " + e.getMessage());
083        }
084
085        return resultBuilder.build();
086    }
087
088    private void transferContext(TGSqlParser legacyParser, ParserContext context) {
089        if (context.getSqlText() != null && !context.getSqlText().isEmpty()) {
090            legacyParser.sqltext = context.getSqlText();
091        }
092        if (context.getSqlFilename() != null && !context.getSqlFilename().isEmpty()) {
093            legacyParser.sqlfilename = context.getSqlFilename();
094        }
095        if (context.getSqlCharset() != null && !context.getSqlCharset().isEmpty()) {
096            legacyParser.setSqlCharset(context.getSqlCharset());
097        }
098        if (context.getSqlInputStream() != null) {
099            // Forward the stream itself: BOM and encoding detection belong to
100            // readsql(), not here. Safe now that this adapter makes exactly one
101            // pass over the source. Without this the stream was dropped and the
102            // adapter silently parsed EMPTY input in every edition.
103            legacyParser.setSqlInputStream(context.getSqlInputStream());
104        }
105
106        legacyParser.setEnablePartialParsing(context.isEnablePartialParsing());
107        legacyParser.setSinglePLBlock(context.isSinglePLBlock());
108        legacyParser.setOnlyNeedRawParseTree(context.isOnlyNeedRawParseTree());
109        
110        if (context.getTeradataUtilityType() != null) {
111            legacyParser.setTeradataUtilityType(context.getTeradataUtilityType());
112        }
113        if (context.getMetaDatabase() != null) {
114            legacyParser.setMetaDatabase(context.getMetaDatabase());
115        }
116        if (context.getSqlEnv() != null) {
117            legacyParser.setSqlEnv(context.getSqlEnv());
118        }
119        if (context.isDumpResolverLog()) {
120            TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE = true;
121        }
122    }
123
124    @Override
125    public String toString() {
126        return "MySqlParser{vendor=" + vendor + "}";
127    }
128}