001package gudusoft.gsqlparser.sqlcmds;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.EFindSqlStateType;
005import gudusoft.gsqlparser.ESqlStatementType;
006import gudusoft.gsqlparser.ETokenType;
007import gudusoft.gsqlparser.TCustomSqlStatement;
008import gudusoft.gsqlparser.TSourceToken;
009import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
010
011import java.util.EnumSet;
012
013/**
014 * Exasol SQL command resolver.
015 *
016 * <p>Exasol shares the MSSQL lexer/parser (see TGSqlParser delegation), so
017 * T-SQL statement recognition is inherited from {@link TSqlCmdsMssql}.
018 * Exasol-specific administrative commands (EXPORT, IMPORT, CREATE CONNECTION,
019 * COMMENT ON, ALTER SESSION/SYSTEM, ...) are recognized here by first-token
020 * <em>text</em> so they work even though those words are plain identifiers in
021 * the MSSQL lexer. Matched statements are routed to
022 * {@link TUnknownSqlStatement} with a dedicated statement type: split and
023 * typed, consumed as a raw token stream without invoking the yacc parser —
024 * the same route TSqlCmdsSybase uses for Sybase IQ administrative commands.
025 */
026public class TSqlCmdsExasol extends TSqlCmdsMssql {
027
028    /** Statement types recognized here that swallow tokens until the next
029     *  top-level semicolon: while one is open, no new statement may start
030     *  (protects EXPORT (SELECT ...), ALTER SESSION SET ..., CREATE SCRIPT
031     *  bodies from being split at inner keywords). */
032    private static final EnumSet<ESqlStatementType> EXASOL_STUB_TYPES = EnumSet.of(
033            ESqlStatementType.sstexasolalterconnection,
034            ESqlStatementType.sstexasolaltersession,
035            ESqlStatementType.sstexasolaltersystem,
036            ESqlStatementType.sstexasolaltervirtualschema,
037            ESqlStatementType.sstexasolalterconsumergroup,
038            ESqlStatementType.sstexasolcreateconnection,
039            ESqlStatementType.sstexasolcreateconsumergroup,
040            ESqlStatementType.sstexasolcreatescript,
041            ESqlStatementType.sstexasolcreatevirtualschema,
042            ESqlStatementType.sstexasoldropconnection,
043            ESqlStatementType.sstexasoldropconsumergroup,
044            ESqlStatementType.sstexasoldropscript,
045            ESqlStatementType.sstexasoldropvirtualschema,
046            ESqlStatementType.sstexasolcommenton,
047            ESqlStatementType.sstexasoldescribe,
048            ESqlStatementType.sstexasolexecutescript,
049            ESqlStatementType.sstexasolexport,
050            ESqlStatementType.sstexasolimport,
051            ESqlStatementType.sstexasolflushstatistics,
052            ESqlStatementType.sstexasolimpersonate,
053            ESqlStatementType.sstexasolopenschema,
054            ESqlStatementType.sstexasolcloseschema,
055            ESqlStatementType.sstexasolpreload,
056            ESqlStatementType.sstexasolrecompress,
057            ESqlStatementType.sstexasolrename,
058            ESqlStatementType.sstexasolreorganize,
059            ESqlStatementType.sstexasoltruncateauditlogs);
060
061    public TSqlCmdsExasol() {
062        super();
063        setVendor(EDbVendor.dbvexasol);
064    }
065
066    @Override
067    public TCustomSqlStatement issql(TSourceToken token, EFindSqlStateType state, TCustomSqlStatement currentStatement) {
068        if (token.tokentype == ETokenType.ttsimplecomment
069                || token.tokentype == ETokenType.ttbracketedcomment
070                || token.tokentype == ETokenType.ttwhitespace
071                || token.tokentype == ETokenType.ttreturn
072                || token.tokentype == ETokenType.ttsemicolon) {
073            return super.issql(token, state, currentStatement);
074        }
075
076        // Inside an Exasol administrative statement, never start a new
077        // statement: it runs to the next top-level semicolon.
078        if (state != EFindSqlStateType.stnormal && currentStatement != null
079                && EXASOL_STUB_TYPES.contains(currentStatement.sqlstatementtype)) {
080            gnewsqlstatementtype = ESqlStatementType.sstinvalid;
081            return null;
082        }
083
084        // Exasol has no procedural IF statement, so a mid-statement
085        // "IF [NOT] EXISTS" is always a continuation of the current DDL
086        // (DROP SCHEMA IF EXISTS, CREATE SCHEMA IF NOT EXISTS,
087        // ALTER TABLE ... ADD COLUMN IF NOT EXISTS ...), never a new
088        // T-SQL IF statement.
089        if (state != EFindSqlStateType.stnormal && matchesWord(token, "if")) {
090            TSourceToken next = token.nextSolidToken();
091            if (matchesWord(next, "not") || matchesWord(next, "exists")) {
092                gnewsqlstatementtype = ESqlStatementType.sstinvalid;
093                return null;
094            }
095        }
096
097        // Inside ALTER TABLE, "SET|DROP DEFAULT" / "SET|DROP IDENTITY"
098        // continue the ALTER COLUMN clause (Exasol), they are not new T-SQL
099        // statements.
100        if (state != EFindSqlStateType.stnormal && currentStatement != null
101                && currentStatement.sqlstatementtype == ESqlStatementType.sstaltertable
102                && (matchesWord(token, "drop") || matchesWord(token, "set"))) {
103            TSourceToken next = token.nextSolidToken();
104            if (matchesWord(next, "default") || matchesWord(next, "identity")) {
105                gnewsqlstatementtype = ESqlStatementType.sstinvalid;
106                return null;
107            }
108        }
109
110        // Statement-start recognition of Exasol-specific commands.
111        if (state == EFindSqlStateType.stnormal) {
112            ESqlStatementType t = matchExasolCommand(token);
113            if (t != null) {
114                TUnknownSqlStatement ret = new TUnknownSqlStatement(vendor);
115                ret.sqlstatementtype = t;
116                gnewsqlstatementtype = t;
117                return ret;
118            }
119        }
120
121        return super.issql(token, state, currentStatement);
122    }
123
124    /**
125     * Match an Exasol command by the leading words of the statement.
126     * Returns the statement type, or null when the token does not start an
127     * Exasol-specific command (T-SQL recognition then applies).
128     */
129    private ESqlStatementType matchExasolCommand(TSourceToken t1) {
130        TSourceToken t2 = t1.nextSolidToken();
131
132        if (matchesWord(t1, "alter")) {
133            if (matchesWord(t2, "connection")) return ESqlStatementType.sstexasolalterconnection;
134            if (matchesWord(t2, "session")) return ESqlStatementType.sstexasolaltersession;
135            if (matchesWord(t2, "system")) return ESqlStatementType.sstexasolaltersystem;
136            if (matchesWord(t2, "virtual") && matchesWord(next(t2), "schema")) return ESqlStatementType.sstexasolaltervirtualschema;
137            if (matchesWord(t2, "consumer") && matchesWord(next(t2), "group")) return ESqlStatementType.sstexasolalterconsumergroup;
138            return null;
139        }
140        if (matchesWord(t1, "create")) {
141            // CREATE [OR REPLACE] <exasol object>
142            TSourceToken obj = t2;
143            if (matchesWord(t2, "or") && matchesWord(next(t2), "replace")) {
144                obj = next(next(t2));
145            }
146            if (matchesWord(obj, "connection")) return ESqlStatementType.sstexasolcreateconnection;
147            if (matchesWord(obj, "consumer") && matchesWord(next(obj), "group")) return ESqlStatementType.sstexasolcreateconsumergroup;
148            if (matchesWord(obj, "script")) return ESqlStatementType.sstexasolcreatescript;
149            if (matchesWord(obj, "lua") || matchesWord(obj, "python") || matchesWord(obj, "java") || matchesWord(obj, "r")) {
150                TSourceToken after = next(obj);
151                // CREATE [OR REPLACE] LUA|PYTHON|JAVA|R [SCALAR|SET] SCRIPT
152                if (matchesWord(after, "scalar") || matchesWord(after, "set")) after = next(after);
153                if (matchesWord(after, "script")) return ESqlStatementType.sstexasolcreatescript;
154            }
155            if (matchesWord(obj, "adapter") && matchesWord(next(obj), "script")) return ESqlStatementType.sstexasolcreatescript;
156            if (matchesWord(obj, "virtual") && matchesWord(next(obj), "schema")) return ESqlStatementType.sstexasolcreatevirtualschema;
157            return null;
158        }
159        if (matchesWord(t1, "drop")) {
160            if (matchesWord(t2, "connection")) return ESqlStatementType.sstexasoldropconnection;
161            if (matchesWord(t2, "consumer") && matchesWord(next(t2), "group")) return ESqlStatementType.sstexasoldropconsumergroup;
162            if (matchesWord(t2, "script") || (matchesWord(t2, "adapter") && matchesWord(next(t2), "script"))) return ESqlStatementType.sstexasoldropscript;
163            if ((matchesWord(t2, "virtual") || matchesWord(t2, "force")) && matchesWord(next(t2), "schema")) return ESqlStatementType.sstexasoldropvirtualschema;
164            return null;
165        }
166        if (matchesWord(t1, "comment") && matchesWord(t2, "on")) return ESqlStatementType.sstexasolcommenton;
167        if (matchesWord(t1, "desc") || matchesWord(t1, "describe")) return ESqlStatementType.sstexasoldescribe;
168        if ((matchesWord(t1, "execute") || matchesWord(t1, "exec")) && matchesWord(t2, "script")) return ESqlStatementType.sstexasolexecutescript;
169        if (matchesWord(t1, "export")) return ESqlStatementType.sstexasolexport;
170        if (matchesWord(t1, "import")) return ESqlStatementType.sstexasolimport;
171        if (matchesWord(t1, "flush") && matchesWord(t2, "statistics")) return ESqlStatementType.sstexasolflushstatistics;
172        if (matchesWord(t1, "impersonate")) return ESqlStatementType.sstexasolimpersonate;
173        if (matchesWord(t1, "open") && matchesWord(t2, "schema")) return ESqlStatementType.sstexasolopenschema;
174        if (matchesWord(t1, "close") && matchesWord(t2, "schema")) return ESqlStatementType.sstexasolcloseschema;
175        if (matchesWord(t1, "preload")) return ESqlStatementType.sstexasolpreload;
176        if (matchesWord(t1, "recompress")) return ESqlStatementType.sstexasolrecompress;
177        if (matchesWord(t1, "rename")) return ESqlStatementType.sstexasolrename;
178        if (matchesWord(t1, "reorganize")) return ESqlStatementType.sstexasolreorganize;
179        if (matchesWord(t1, "truncate") && matchesWord(t2, "audit")) return ESqlStatementType.sstexasoltruncateauditlogs;
180        return null;
181    }
182
183    private static TSourceToken next(TSourceToken t) {
184        return t == null ? null : t.nextSolidToken();
185    }
186
187    /** Case-insensitive command-word comparison (keywords, not identifiers). */
188    private static boolean matchesWord(TSourceToken t, String word) {
189        return t != null && t.toString().equalsIgnoreCase(word);
190    }
191}