001package gudusoft.gsqlparser.sqlenv.compat; 002 003import gudusoft.gsqlparser.catalog.runtime.CatalogEntry; 004import gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind; 005import gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName; 006import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 007import gudusoft.gsqlparser.sqlenv.TSQLEnv; 008import gudusoft.gsqlparser.sqlenv.TSQLFunction; 009import gudusoft.gsqlparser.sqlenv.TSQLOraclePackage; 010import gudusoft.gsqlparser.sqlenv.TSQLProcedure; 011import gudusoft.gsqlparser.sqlenv.TSQLSchemaObject; 012import gudusoft.gsqlparser.sqlenv.TSQLTable; 013 014import java.util.Collections; 015import java.util.List; 016 017/** 018 * Materializes a runtime {@link CatalogEntry} into the legacy {@link TSQLSchemaObject} 019 * shape consumed by {@code TSQLResolver2}. 020 * 021 * <p>Plan §6 / §7.4. Mapping is driven by {@link CatalogObjectKind}; the mapper 022 * delegates to the existing public {@link TSQLEnv} mutation API (per spike T0.3 023 * inventory) so the eager bridge uses no reflection and no private-field access.</p> 024 * 025 * <p>Sequences are not first-class on {@link TSQLEnv} (no {@code dotSequence} type 026 * in {@link ESQLDataObjectType}), so {@link #toTSQLSequence} returns {@code null}. 027 * The runtime layer still represents sequences in {@link gudusoft.gsqlparser.catalog.runtime.CatalogSnapshot}; 028 * callers that need them should consult the runtime directly.</p> 029 */ 030public final class CatalogEntryToSQLEnvMapper { 031 032 public CatalogEntryToSQLEnvMapper() { 033 } 034 035 /** 036 * Dispatch helper: route a {@link CatalogEntry} to the right typed mapper based on its 037 * {@link CatalogObjectKind}. Kinds that do not have a {@link TSQLEnv} representation 038 * (CATALOG, SCHEMA, COLUMN, CONSTRAINT, INDEX, TYPE, MATERIALIZED_VIEW collapsed to 039 * VIEW, SEQUENCE) return {@code null}; the loader and bridge treat that as "skip". 040 */ 041 public TSQLSchemaObject toSQLSchemaObject(CatalogEntry entry, TSQLEnv targetEnv) { 042 return toSQLSchemaObject(entry, targetEnv, Collections.<CatalogEntry>emptyList()); 043 } 044 045 /** 046 * Same as {@link #toSQLSchemaObject(CatalogEntry, TSQLEnv)} but lets the caller 047 * supply a list of column entries that will be attached to a TABLE / VIEW / 048 * MATERIALIZED_VIEW result via {@link TSQLTable#addColumn(String)}. The legacy 049 * resolver reads {@code TSQLTable.getColumnList()} directly during the column 050 * push-down phase, so a lazy materialization that drops columns silently 051 * regresses column resolution. The lazy bridge fills this list from 052 * {@code CatalogRuntime.findChildren(parentId, COLUMN)}. 053 */ 054 public TSQLSchemaObject toSQLSchemaObject(CatalogEntry entry, TSQLEnv targetEnv, 055 List<CatalogEntry> columnChildren) { 056 requireArgs(entry, targetEnv); 057 switch (entry.kind()) { 058 case TABLE: 059 return toTSQLTable(entry, targetEnv, columnChildren); 060 case VIEW: 061 case MATERIALIZED_VIEW: 062 return toTSQLView(entry, targetEnv, columnChildren); 063 case FUNCTION: 064 return toTSQLFunction(entry, targetEnv); 065 case PROCEDURE: 066 return toTSQLProcedure(entry, targetEnv); 067 case PACKAGE: 068 return toTSQLOraclePackage(entry, targetEnv); 069 case ROUTINE: 070 // Generic routine — TSQLEnv has no first-class type. Route to procedure 071 // since the resolver looks up routines through the procedure path. 072 return toTSQLProcedure(entry, targetEnv); 073 case SYNONYM: 074 return toTSQLSynonym(entry, targetEnv); 075 case SEQUENCE: 076 return toTSQLSequence(entry, targetEnv); 077 case TRIGGER: 078 return toTSQLTrigger(entry, targetEnv); 079 default: 080 // CATALOG / SCHEMA / COLUMN / CONSTRAINT / INDEX / TYPE — no direct 081 // TSQLEnv schema-object representation. Loader handles them via the 082 // parent table / schema mutation paths. 083 return null; 084 } 085 } 086 087 public TSQLTable toTSQLTable(CatalogEntry entry, TSQLEnv targetEnv) { 088 return toTSQLTable(entry, targetEnv, Collections.<CatalogEntry>emptyList()); 089 } 090 091 /** 092 * Materialize a TABLE entry and attach the supplied column entries. The lazy bridge 093 * uses this overload so the produced {@link TSQLTable} carries a non-empty 094 * {@code getColumnList()}; the resolver's column push-down path reads that list and 095 * silently fails on empty results otherwise. 096 */ 097 public TSQLTable toTSQLTable(CatalogEntry entry, TSQLEnv targetEnv, 098 List<CatalogEntry> columnChildren) { 099 requireArgs(entry, targetEnv); 100 requireKind(entry, CatalogObjectKind.TABLE); 101 TSQLTable t = targetEnv.addTable(rawQualified(entry.name()), false); 102 attachColumns(t, columnChildren); 103 return t; 104 } 105 106 /** Views are represented in the legacy model as {@link TSQLTable} with view metadata. */ 107 public TSQLTable toTSQLView(CatalogEntry entry, TSQLEnv targetEnv) { 108 return toTSQLView(entry, targetEnv, Collections.<CatalogEntry>emptyList()); 109 } 110 111 /** 112 * Same as {@link #toTSQLView(CatalogEntry, TSQLEnv)} but with explicit column entries 113 * for the lazy bridge path. 114 */ 115 public TSQLTable toTSQLView(CatalogEntry entry, TSQLEnv targetEnv, 116 List<CatalogEntry> columnChildren) { 117 requireArgs(entry, targetEnv); 118 if (entry.kind() != CatalogObjectKind.VIEW 119 && entry.kind() != CatalogObjectKind.MATERIALIZED_VIEW) { 120 throw new IllegalArgumentException( 121 "toTSQLView requires VIEW or MATERIALIZED_VIEW; got " + entry.kind()); 122 } 123 TSQLTable v = targetEnv.addView(rawQualified(entry.name()), false); 124 if (v != null) { 125 Object def = entry.properties().get("definition"); 126 if (def instanceof String) { 127 v.setDefinition((String) def); 128 } 129 attachColumns(v, columnChildren); 130 } 131 return v; 132 } 133 134 private static void attachColumns(TSQLTable table, List<CatalogEntry> columnChildren) { 135 if (table == null || columnChildren == null || columnChildren.isEmpty()) { 136 return; 137 } 138 for (CatalogEntry col : columnChildren) { 139 if (col == null || col.name() == null) continue; 140 // Use the local (last) segment of the qualified name as the column name — 141 // matches what the eager loader does via TSQLTable.addColumn(name). 142 String colName = col.name().localName(); 143 if (colName == null || colName.isEmpty()) continue; 144 table.addColumn(colName); 145 } 146 } 147 148 public TSQLFunction toTSQLFunction(CatalogEntry entry, TSQLEnv targetEnv) { 149 requireArgs(entry, targetEnv); 150 requireKind(entry, CatalogObjectKind.FUNCTION); 151 return targetEnv.addFunction(rawQualified(entry.name()), false); 152 } 153 154 public TSQLProcedure toTSQLProcedure(CatalogEntry entry, TSQLEnv targetEnv) { 155 requireArgs(entry, targetEnv); 156 if (entry.kind() != CatalogObjectKind.PROCEDURE 157 && entry.kind() != CatalogObjectKind.ROUTINE) { 158 throw new IllegalArgumentException( 159 "toTSQLProcedure requires PROCEDURE or ROUTINE; got " + entry.kind()); 160 } 161 return targetEnv.addProcedure(rawQualified(entry.name()), false); 162 } 163 164 public TSQLOraclePackage toTSQLOraclePackage(CatalogEntry entry, TSQLEnv targetEnv) { 165 requireArgs(entry, targetEnv); 166 requireKind(entry, CatalogObjectKind.PACKAGE); 167 // addOraclePackage returns TSQLProcedure in the legacy API but the underlying 168 // schema object is a TSQLOraclePackage. Route through doAddSchemaObject for the 169 // correct concrete type. 170 TSQLSchemaObject obj = targetEnv.doAddSchemaObject( 171 rawQualified(entry.name()), ESQLDataObjectType.dotOraclePackage); 172 return obj instanceof TSQLOraclePackage ? (TSQLOraclePackage) obj : null; 173 } 174 175 public TSQLSchemaObject toTSQLSynonym(CatalogEntry entry, TSQLEnv targetEnv) { 176 requireArgs(entry, targetEnv); 177 requireKind(entry, CatalogObjectKind.SYNONYM); 178 return targetEnv.doAddSchemaObject(rawQualified(entry.name()), ESQLDataObjectType.dotSynonyms); 179 } 180 181 /** 182 * Sequences are not modeled in {@link TSQLEnv} (no {@code dotSequence} member of 183 * {@link ESQLDataObjectType}). Returns {@code null} so callers can skip; the 184 * runtime layer still represents sequences in {@link gudusoft.gsqlparser.catalog.runtime.CatalogSnapshot}. 185 */ 186 public TSQLSchemaObject toTSQLSequence(CatalogEntry entry, TSQLEnv targetEnv) { 187 requireArgs(entry, targetEnv); 188 requireKind(entry, CatalogObjectKind.SEQUENCE); 189 return null; 190 } 191 192 public TSQLSchemaObject toTSQLTrigger(CatalogEntry entry, TSQLEnv targetEnv) { 193 requireArgs(entry, targetEnv); 194 requireKind(entry, CatalogObjectKind.TRIGGER); 195 return targetEnv.addTrigger(rawQualified(entry.name()), false); 196 } 197 198 /** 199 * Build a dotted "[catalog.]schema.object" string from the raw segments of a 200 * {@link CatalogQualifiedName}. The legacy {@link TSQLEnv} mutation API takes the raw 201 * (un-normalized) form because it normalizes internally via {@code SQLUtil}; passing 202 * normalized segments would double-fold and confuse vendor-specific casing. 203 */ 204 static String rawQualified(CatalogQualifiedName name) { 205 List<String> segs = name.raw(); 206 if (segs.size() == 1) { 207 return segs.get(0); 208 } 209 StringBuilder sb = new StringBuilder(); 210 for (int i = 0; i < segs.size(); i++) { 211 if (i > 0) sb.append('.'); 212 sb.append(segs.get(i)); 213 } 214 return sb.toString(); 215 } 216 217 private static void requireArgs(CatalogEntry entry, TSQLEnv env) { 218 if (entry == null) { 219 throw new IllegalArgumentException("CatalogEntryToSQLEnvMapper: entry is required"); 220 } 221 if (env == null) { 222 throw new IllegalArgumentException("CatalogEntryToSQLEnvMapper: targetEnv is required"); 223 } 224 // Defensive checks for arbitrary CatalogEntry implementations: the factory 225 // (CatalogEntries.builder()) enforces these invariants, but third-party impls 226 // may not. Fail fast with a clear message instead of NPE deep inside the mapper. 227 if (entry.kind() == null) { 228 throw new IllegalArgumentException("CatalogEntryToSQLEnvMapper: entry.kind() is null"); 229 } 230 if (entry.name() == null) { 231 throw new IllegalArgumentException("CatalogEntryToSQLEnvMapper: entry.name() is null"); 232 } 233 } 234 235 private static void requireKind(CatalogEntry entry, CatalogObjectKind expected) { 236 if (entry.kind() != expected) { 237 throw new IllegalArgumentException( 238 "CatalogEntryToSQLEnvMapper expected " + expected + "; got " + entry.kind()); 239 } 240 } 241}