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}