001package gudusoft.gsqlparser.nodes.structured; 002 003import gudusoft.gsqlparser.EDbVendor; 004 005import java.util.Collections; 006import java.util.EnumMap; 007import java.util.EnumSet; 008import java.util.Map; 009import java.util.Set; 010 011/** 012 * What one dialect lets a query do with nested values (STRUCT fields, ARRAY elements, MAP entries), as the axes the 013 * nested-access models branch on. This is the one table of vendor rules for nested columns: the parser's access-path 014 * normalizer, resolver2 and lineage ask it instead of comparing {@link EDbVendor} values, so adding a dialect is one 015 * entry here (docs/refactor/nested_column_access_unification_plan.md, section 4.6). 016 * <p> 017 * A Trino analysis parses with the Presto grammar, so its nodes carry {@link EDbVendor#dbvpresto} while the analysis 018 * vendor is {@link EDbVendor#dbvtrino}; every axis gives the two the same answer except {@link #usesPrestoUnnestPath()}, 019 * which is asked with the analysis vendor. 020 */ 021public final class NestedAccessProfile { 022 023 /** How a column's DDL declares a nested type. */ 024 public enum DeclaredTypeSyntax { 025 /** {@code STRUCT<a:STRING>}, {@code ARRAY<T>}, {@code MAP<K,V>}: Hive, Impala, Spark SQL, Databricks, Athena. */ 026 ANGLE_COLON, 027 /** {@code STRUCT<a STRING>}, {@code ARRAY<T>}: BigQuery. */ 028 ANGLE_SPACE, 029 /** {@code ROW(a VARCHAR)}, {@code ARRAY(T)}, {@code MAP(K,V)}: Presto, Trino. */ 030 ROW_PAREN, 031 /** {@code CREATE TYPE t AS (...)}: PostgreSQL (extension point). */ 032 COMPOSITE_TYPE, 033 /** {@code VARIANT}, {@code OBJECT}, {@code SUPER}: no declared fields (Snowflake, Redshift). */ 034 SEMI_STRUCTURED, 035 NONE 036 } 037 038 /** The parse shapes a nested read takes in a dialect's grammar (plan section 3, gap G1). */ 039 public enum AccessShape { 040 /** One name with a {@code TIndirection}: Databricks, the Trino family, PostgreSQL, Redshift, Snowflake. */ 041 INDIRECTION, 042 /** {@code field_access_t} / {@code array_access_expr_t} around the name: Hive, Impala. */ 043 EXPRESSION_CHAIN, 044 /** A dotted name in the name slots: every dialect. */ 045 DOTTED, 046 /** BigQuery's {@code array_t} expression over a subscripted name. */ 047 BIGQUERY_ARRAY_EXPR, 048 /** PostgreSQL {@code (customer).name} (extension point). */ 049 PARENTHESISED, 050 /** Snowflake {@code customer:address.city} (extension point). */ 051 COLON_PATH 052 } 053 054 /** Which element a bare subscript ({@code items[1]}) of a column of unknown type reads. */ 055 public enum SubscriptRule { 056 /** A subscript reads the element node, with or without a field after it. */ 057 ALWAYS, 058 /** A subscript reads the column whole. */ 059 NEVER 060 } 061 062 /** What a table-generating construct of the dialect produces from an array. */ 063 public enum UnnestExpansion { 064 /** BigQuery {@code UNNEST(x) AS i}: one value column, the element. */ 065 VALUE_TABLE, 066 /** Trino {@code UNNEST(a, b) AS t(x, y)}: aliases pair with the inputs by position. */ 067 POSITIONAL_EXPANSION, 068 /** Hive {@code LATERAL VIEW explode(x) lv AS e}: a table-generating function. */ 069 GENERATOR, 070 /** Snowflake {@code FLATTEN}: fixed output columns (extension point). */ 071 FIXED_COLUMNS, 072 NONE 073 } 074 075 private static final Map<EDbVendor, NestedAccessProfile> PROFILES = 076 new EnumMap<EDbVendor, NestedAccessProfile>(EDbVendor.class); 077 078 static { 079 for (EDbVendor vendor : EDbVendor.values()) { 080 PROFILES.put(vendor, new NestedAccessProfile(vendor)); 081 } 082 } 083 084 private final EDbVendor vendor; 085 private final boolean trinoFamily; 086 private final boolean readsFieldChains; 087 private final boolean structFieldVendor; 088 private final boolean readsAttributePaths; 089 private final boolean publishesPropertyNames; 090 private final boolean buildsDeclaredTree; 091 private final SubscriptRule subscriptRule; 092 private final UnnestExpansion unnestExpansion; 093 private final boolean namedStructConstructor; 094 private final boolean rowCastConstructor; 095 private final DeclaredTypeSyntax declaredTypeSyntax; 096 private final Set<AccessShape> accessShapes; 097 098 private NestedAccessProfile(EDbVendor vendor) { 099 this.vendor = vendor; 100 this.trinoFamily = vendor == EDbVendor.dbvathena || vendor == EDbVendor.dbvpresto 101 || vendor == EDbVendor.dbvtrino; 102 boolean hiveFamily = vendor == EDbVendor.dbvhive || vendor == EDbVendor.dbvsparksql 103 || vendor == EDbVendor.dbvdatabricks; 104 // Impala reads payload.old_field as the field of a STRUCT column, as Hive does (Mantis 4917) 105 this.readsFieldChains = trinoFamily || hiveFamily || vendor == EDbVendor.dbvimpala; 106 this.structFieldVendor = vendor == EDbVendor.dbvbigquery || vendor == EDbVendor.dbvsnowflake; 107 this.readsAttributePaths = vendor == EDbVendor.dbvredshift || vendor == EDbVendor.dbvoracle; 108 this.publishesPropertyNames = vendor == EDbVendor.dbvbigquery || vendor == EDbVendor.dbvredshift; 109 // Impala declares STRUCT, ARRAY and MAP columns as Hive does; its fields were published as columns before the 110 // tree (Mantis 4884) 111 this.buildsDeclaredTree = vendor == EDbVendor.dbvbigquery || readsFieldChains || vendor == EDbVendor.dbvimpala; 112 // plan rule R6 (approved 2026-10-07): a subscript reads the element in every tree dialect, BigQuery's 113 // items[OFFSET(0)] and Hive's items[0] included, not only when a field follows 114 this.subscriptRule = buildsDeclaredTree ? SubscriptRule.ALWAYS : SubscriptRule.NEVER; 115 if (vendor == EDbVendor.dbvbigquery) { 116 this.unnestExpansion = UnnestExpansion.VALUE_TABLE; 117 } else if (trinoFamily) { 118 this.unnestExpansion = UnnestExpansion.POSITIONAL_EXPANSION; 119 } else if (hiveFamily || vendor == EDbVendor.dbvimpala) { 120 this.unnestExpansion = UnnestExpansion.GENERATOR; 121 } else if (vendor == EDbVendor.dbvsnowflake) { 122 this.unnestExpansion = UnnestExpansion.FIXED_COLUMNS; 123 } else { 124 this.unnestExpansion = UnnestExpansion.NONE; 125 } 126 this.namedStructConstructor = hiveFamily; 127 this.rowCastConstructor = trinoFamily || hiveFamily; 128 if (vendor == EDbVendor.dbvbigquery) { 129 this.declaredTypeSyntax = DeclaredTypeSyntax.ANGLE_SPACE; 130 } else if (vendor == EDbVendor.dbvpresto || vendor == EDbVendor.dbvtrino) { 131 this.declaredTypeSyntax = DeclaredTypeSyntax.ROW_PAREN; 132 } else if (hiveFamily || vendor == EDbVendor.dbvimpala || vendor == EDbVendor.dbvathena) { 133 this.declaredTypeSyntax = DeclaredTypeSyntax.ANGLE_COLON; 134 } else if (vendor == EDbVendor.dbvpostgresql) { 135 this.declaredTypeSyntax = DeclaredTypeSyntax.COMPOSITE_TYPE; 136 } else if (vendor == EDbVendor.dbvsnowflake || vendor == EDbVendor.dbvredshift) { 137 this.declaredTypeSyntax = DeclaredTypeSyntax.SEMI_STRUCTURED; 138 } else { 139 this.declaredTypeSyntax = DeclaredTypeSyntax.NONE; 140 } 141 EnumSet<AccessShape> shapes = EnumSet.of(AccessShape.DOTTED); 142 if (vendor == EDbVendor.dbvhive || vendor == EDbVendor.dbvimpala || vendor == EDbVendor.dbvsparksql) { 143 shapes.add(AccessShape.EXPRESSION_CHAIN); 144 } 145 if (vendor == EDbVendor.dbvdatabricks || trinoFamily || vendor == EDbVendor.dbvpostgresql 146 || vendor == EDbVendor.dbvredshift || vendor == EDbVendor.dbvsnowflake) { 147 shapes.add(AccessShape.INDIRECTION); 148 } 149 if (vendor == EDbVendor.dbvbigquery) { 150 shapes.add(AccessShape.BIGQUERY_ARRAY_EXPR); 151 shapes.add(AccessShape.INDIRECTION); 152 } 153 if (vendor == EDbVendor.dbvpostgresql) { 154 shapes.add(AccessShape.PARENTHESISED); 155 } 156 if (vendor == EDbVendor.dbvsnowflake) { 157 shapes.add(AccessShape.COLON_PATH); 158 } 159 this.accessShapes = Collections.unmodifiableSet(shapes); 160 } 161 162 /** The profile of {@code vendor}; a null vendor gets the profile of a dialect without nested access. */ 163 public static NestedAccessProfile forVendor(EDbVendor vendor) { 164 if (vendor == null) { 165 return PROFILES.get(EDbVendor.dbvgeneric); 166 } 167 return PROFILES.get(vendor); 168 } 169 170 public EDbVendor getVendor() { 171 return vendor; 172 } 173 174 /** Athena, Presto, Trino: one grammar, Trino's name-reading rule (Mantis 4811, 4822). */ 175 public boolean isTrinoFamily() { 176 return trinoFamily; 177 } 178 179 /** 180 * A dotted name is read by what its qualifiers name: a qualifier that names a relation in scope is that 181 * relation, otherwise the first part is a column and every later part a field (the Trino family; Hive, Spark 182 * SQL, Databricks since Mantis 4821). 183 */ 184 public boolean readsFieldChains() { 185 return readsFieldChains; 186 } 187 188 /** resolver2 reads a two-part name whose head is no relation as a column with a field: BigQuery, Snowflake. */ 189 public boolean isStructFieldVendor() { 190 return structFieldVendor; 191 } 192 193 /** An attribute path after a relation-qualified column ({@code o.customer.address.city}): Redshift, Oracle. */ 194 public boolean readsAttributePaths() { 195 return readsAttributePaths; 196 } 197 198 /** Lineage names a field read of a flat column by its whole dotted path: BigQuery, Redshift (legacy naming). */ 199 public boolean publishesPropertyNames() { 200 return publishesPropertyNames; 201 } 202 203 /** Lineage models a column with a declared STRUCT, ARRAY or MAP type as a tree of field nodes. */ 204 public boolean buildsDeclaredTree() { 205 return buildsDeclaredTree; 206 } 207 208 /** Which node a subscript of a column of unknown type reads; see {@link SubscriptRule}. */ 209 public SubscriptRule getSubscriptRule() { 210 return subscriptRule; 211 } 212 213 public UnnestExpansion getUnnestExpansion() { 214 return unnestExpansion; 215 } 216 217 /** 218 * Presto's own UNNEST analysis, kept as a compatibility adapter: asked with the analysis vendor, true for 219 * {@link EDbVendor#dbvpresto} only (a Trino or Athena analysis uses the positional-expansion path). 220 */ 221 public boolean usesPrestoUnnestPath() { 222 return vendor == EDbVendor.dbvpresto; 223 } 224 225 /** {@code named_struct('a', x, ...)} builds a STRUCT: Hive, Spark SQL, Databricks. */ 226 public boolean hasNamedStructConstructor() { 227 return namedStructConstructor; 228 } 229 230 /** {@code CAST(ROW(...) AS ROW(a T, ...))} builds a STRUCT with named fields: the tree dialects but BigQuery. */ 231 public boolean hasRowCastConstructor() { 232 return rowCastConstructor; 233 } 234 235 public DeclaredTypeSyntax getDeclaredTypeSyntax() { 236 return declaredTypeSyntax; 237 } 238 239 public Set<AccessShape> getAccessShapes() { 240 return accessShapes; 241 } 242}