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}