001package gudusoft.gsqlparser.sqlenv;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.ETableKind;
005import gudusoft.gsqlparser.nodes.TObjectName;
006import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement;
007
008import java.util.Collection;
009import java.util.Collections;
010
011/**
012 * Who can see a temporary table's definition, per vendor and declaration form.
013 *
014 * <p>This is the one place that decides it. resolver2 binds each table reference to the
015 * definition this rule makes visible there, and the lineage model shares a table model exactly
016 * when resolver2 bound two references to the same definition, so lookup and publish follow one
017 * rule.</p>
018 *
019 * <p>A table whose definition is {@linkplain #isTransient transient} belongs to the routine body
020 * (or the top-level script) that creates it. Parsing a routine is not running it, so another
021 * routine never sees that definition unless a call in the analyzed SQL proves it can:</p>
022 * <ul>
023 *   <li>{@link Scope#CALL}: the routine that creates the table and the routines it calls, until it
024 *   returns.</li>
025 *   <li>{@link Scope#SESSION}: as {@code CALL}, and the table also outlives the routine, so after
026 *   the call its caller sees it too.</li>
027 * </ul>
028 * <p>{@link Scope#CATALOG} and {@link Scope#GLOBAL} definitions are shared by every routine, as a
029 * permanent table's is.</p>
030 *
031 * <p>Vendor rules, from each vendor's documentation:</p>
032 * <ul>
033 *   <li>SQL Server, Azure SQL: {@code #name} is {@code CALL} ("Temporary tables" in CREATE
034 *   TABLE); {@code ##name} is {@code GLOBAL}, one live object for every session.</li>
035 *   <li>SAP ASE: {@code #name} is {@code CALL}; ASE has no {@code ##} form, so {@code ##name} is
036 *   another {@code #} table. {@code CREATE GLOBAL TEMPORARY TABLE} is {@code CATALOG}.</li>
037 *   <li>SAP IQ, SQL Anywhere: a {@code #name} table created in a procedure outlives it (running
038 *   the procedure again fails with "table already exists"), so it is {@code SESSION}, as is
039 *   {@code CREATE LOCAL TEMPORARY TABLE}. {@code CREATE GLOBAL TEMPORARY TABLE} is
040 *   {@code CATALOG}. {@code dbvsybase} names no single product and follows IQ.</li>
041 *   <li>SAP HANA: a local temporary table ({@code #name}) is {@code SESSION}; a global temporary
042 *   table is {@code CATALOG}.</li>
043 *   <li>PostgreSQL, Greenplum, Redshift: {@code TEMP}/{@code TEMPORARY} is {@code SESSION};
044 *   {@code GLOBAL} and {@code LOCAL} are accepted and ignored, so they are {@code SESSION} too.
045 *   Redshift {@code #name} is {@code SESSION}.</li>
046 *   <li>MySQL, BigQuery, Netezza: {@code TEMPORARY}/{@code TEMP} is {@code SESSION}.</li>
047 *   <li>Snowflake: {@code TEMPORARY} and its synonyms ({@code LOCAL}, {@code GLOBAL},
048 *   {@code VOLATILE}) are {@code SESSION}. A procedure's {@code CREATE PROCEDURE SCOPED TEMP TABLE}
049 *   is not classified: GSP does not parse it yet.</li>
050 *   <li>Teradata: {@code VOLATILE} is {@code SESSION}; {@code GLOBAL TEMPORARY} is
051 *   {@code CATALOG}.</li>
052 *   <li>Oracle: {@code GLOBAL TEMPORARY} is {@code CATALOG}; {@code PRIVATE TEMPORARY} is
053 *   {@code SESSION}.</li>
054 *   <li>Vertica: {@code LOCAL TEMPORARY} is {@code SESSION}; {@code GLOBAL TEMPORARY} is
055 *   {@code CATALOG}, and so is plain {@code TEMPORARY}, which is global under Vertica's default
056 *   {@code DefaultTempTableLocal = 0} (a server configured with 1 makes it local).</li>
057 *   <li>Db2: {@code CREATE GLOBAL TEMPORARY TABLE} is {@code CATALOG}.</li>
058 *   <li>GaussDB, EDB: {@code TEMP}/{@code LOCAL TEMP} is {@code SESSION}; {@code GLOBAL
059 *   TEMPORARY} is {@code CATALOG}.</li>
060 * </ul>
061 * <p>Any other vendor or form is not classified ({@code null}): its tables are analyzed as
062 * permanent tables are.</p>
063 */
064public final class TemporaryTableSemantics {
065
066    /** Who can see a temporary table's definition. */
067    public enum Scope {
068        /** The routine that creates it and the routines it calls, until it returns. */
069        CALL,
070        /** As {@link #CALL}, and it outlives the routine: after the call, the caller sees it too. */
071        SESSION,
072        /** A persistent catalog definition that every routine and session shares; rows are per session. */
073        CATALOG,
074        /** One live object that every routine and session shares. */
075        GLOBAL
076    }
077
078    private TemporaryTableSemantics() {
079    }
080
081    /** Whether a definition of this scope belongs to the body that creates it. */
082    public static boolean isTransient(Scope scope) {
083        return scope == Scope.CALL || scope == Scope.SESSION;
084    }
085
086    /** The scope a table's name alone gives it, from its SQL-lexical object segment. */
087    public static Scope ofName(EDbVendor vendor, TObjectName name) {
088        return name == null ? null : ofNameSegment(vendor, name.getObjectString());
089    }
090
091    /** The scope a table's name alone gives it, from one SQL-lexical object segment. */
092    public static Scope ofNameSegment(EDbVendor vendor, String lexicalObjectSegment) {
093        if (vendor == null || lexicalObjectSegment == null || lexicalObjectSegment.indexOf('#') < 0) {
094            return null;
095        }
096        // The decoded object's prefix is what the vendor reads, whether or not it was delimited.
097        return ofStoredName(vendor, IdentifierCodec.decodeLexical(IdentifierProfile.forVendor(vendor,
098                IdentifierProfile.VendorFlags.defaults()), ESQLDataObjectType.dotTable, lexicalObjectSegment));
099    }
100
101    /** The scope a stored (decoded) table name alone gives it, or null when the name says nothing. */
102    public static Scope ofStoredName(EDbVendor vendor, String storedName) {
103        if (vendor == null || storedName == null || !storedName.startsWith("#")) {
104            return null;
105        }
106        switch (vendor) {
107            case dbvmssql:
108            case dbvazuresql:
109                return storedName.startsWith("##") ? Scope.GLOBAL : Scope.CALL;
110            case dbvsybasease:
111                return Scope.CALL;
112            case dbvsybase:
113            case dbvsybaseiq:
114            case dbvsqlanywhere:
115            case dbvhana:
116            case dbvredshift:
117                return Scope.SESSION;
118            default:
119                return null;
120        }
121    }
122
123    /** The scope the declaration keywords ({@code TEMPORARY}, {@code VOLATILE} ...) give a table. */
124    public static Scope ofKinds(EDbVendor vendor, Collection<ETableKind> kinds) {
125        if (vendor == null || kinds == null || kinds.isEmpty()) {
126            return null;
127        }
128        boolean global = kinds.contains(ETableKind.etkGlobalTemporary) || kinds.contains(ETableKind.etkGlobalTemp);
129        boolean local = kinds.contains(ETableKind.etkLocalTemporary) || kinds.contains(ETableKind.etkLocalTemp);
130        boolean plain = kinds.contains(ETableKind.etkTemporary) || kinds.contains(ETableKind.etkTemp);
131        boolean volatileKind = kinds.contains(ETableKind.etkVolatile) || kinds.contains(ETableKind.etkLocalVolatile)
132                || kinds.contains(ETableKind.etkGlobalVolatile);
133        switch (vendor) {
134            case dbvpostgresql:
135            case dbvgreenplum:
136            case dbvredshift:
137                return global || local || plain ? Scope.SESSION : null;
138            case dbvmysql:
139            case dbvbigquery:
140            case dbvnetezza:
141                return local || plain ? Scope.SESSION : null;
142            case dbvsnowflake:
143                return global || local || plain || volatileKind ? Scope.SESSION : null;
144            case dbvteradata:
145                if (volatileKind) return Scope.SESSION;
146                return global ? Scope.CATALOG : null;
147            case dbvoracle:
148                if (kinds.contains(ETableKind.etkPrivateTemporary)) return Scope.SESSION;
149                return global ? Scope.CATALOG : null;
150            case dbvvertica:
151                if (local) return Scope.SESSION;
152                return global || plain ? Scope.CATALOG : null;
153            case dbvdb2:
154            case dbvsybasease:
155                return global ? Scope.CATALOG : null;
156            case dbvhana:
157            case dbvgaussdb:
158            case dbvedb:
159            case dbvsybase:
160            case dbvsybaseiq:
161            case dbvsqlanywhere:
162                if (global) return Scope.CATALOG;
163                return local || plain ? Scope.SESSION : null;
164            default:
165                return null;
166        }
167    }
168
169    /** The scope of the table a CREATE TABLE statement declares, or null for a permanent table. */
170    public static Scope ofDeclaration(TCreateTableSqlStatement create) {
171        if (create == null || create.getTableName() == null) {
172            return null;
173        }
174        Scope byName = ofName(create.dbvendor, create.getTableName());
175        return byName != null ? byName : ofKinds(create.dbvendor, create.getTableKinds());
176    }
177
178    /**
179     * The scope of the table a {@code SELECT ... INTO} target creates ({@code INTO #t},
180     * {@code INTO TEMP t}), or null for a permanent table. SAP HANA's {@code SELECT ... INTO}
181     * assigns variables and creates no table, so a HANA target never has one.
182     */
183    public static Scope ofSelectInto(EDbVendor vendor, TObjectName target) {
184        if (target == null || vendor == EDbVendor.dbvhana) {
185            return null;
186        }
187        Scope byName = ofName(vendor, target);
188        if (byName != null || target.getTableKind() == null) {
189            return byName;
190        }
191        return ofKinds(vendor, Collections.singleton(target.getTableKind()));
192    }
193
194    /** Whether a table name alone makes it transient ({@code #name} in the vendors that read it so). */
195    public static boolean isTransientName(EDbVendor vendor, TObjectName name) {
196        return isTransient(ofName(vendor, name));
197    }
198
199    /** Whether {@code vendor} has any transient temporary tables, so routine bodies need their own scope. */
200    public static boolean hasTransientTables(EDbVendor vendor) {
201        if (vendor == null) {
202            return false;
203        }
204        switch (vendor) {
205            case dbvmssql:
206            case dbvazuresql:
207            case dbvsybase:
208            case dbvsybasease:
209            case dbvsybaseiq:
210            case dbvsqlanywhere:
211            case dbvhana:
212            case dbvredshift:
213            case dbvpostgresql:
214            case dbvgreenplum:
215            case dbvmysql:
216            case dbvbigquery:
217            case dbvnetezza:
218            case dbvsnowflake:
219            case dbvteradata:
220            case dbvoracle:
221            case dbvvertica:
222            case dbvgaussdb:
223            case dbvedb:
224                return true;
225            default:
226                return false;
227        }
228    }
229}