001package gudusoft.gsqlparser.sqlenv.compat; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.catalog.diagnostic.CatalogDiagnostic; 005import gudusoft.gsqlparser.catalog.diagnostic.CatalogDiagnosticCode; 006import gudusoft.gsqlparser.catalog.diagnostic.CatalogDiagnosticSeverity; 007import gudusoft.gsqlparser.catalog.input.CatalogLoadingMode; 008import gudusoft.gsqlparser.catalog.input.model.IdentifierConfig; 009import gudusoft.gsqlparser.catalog.runtime.CatalogEntries; 010import gudusoft.gsqlparser.catalog.runtime.CatalogIdentifierPolicy; 011import gudusoft.gsqlparser.catalog.runtime.CatalogObjectId; 012import gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind; 013import gudusoft.gsqlparser.catalog.runtime.CatalogProviderConfig; 014import gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName; 015import gudusoft.gsqlparser.catalog.runtime.CatalogRuntime; 016import gudusoft.gsqlparser.catalog.runtime.CatalogSnapshot; 017import gudusoft.gsqlparser.catalog.runtime.InMemoryCatalogProvider; 018import gudusoft.gsqlparser.catalog.runtime.InMemoryCatalogSnapshot; 019import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 020import gudusoft.gsqlparser.sqlenv.TSQLCatalog; 021import gudusoft.gsqlparser.sqlenv.TSQLColumn; 022import gudusoft.gsqlparser.sqlenv.TSQLEnv; 023import gudusoft.gsqlparser.sqlenv.TSQLSchema; 024import gudusoft.gsqlparser.sqlenv.TSQLSchemaObject; 025import gudusoft.gsqlparser.sqlenv.TSQLTable; 026 027/** 028 * Reverse bridge: read-only {@link CatalogRuntime} view over a hand-built {@link TSQLEnv}. 029 * 030 * <p>Plan §6 / §8.5. Lets new consumers (SQLGuard, Semantic IR, anything that targets 031 * the new SPI) accept a hand-built {@link TSQLEnv} from upstream callers without 032 * re-implementing the catalog traversal each time.</p> 033 * 034 * <p>The resulting runtime is read-only at the snapshot level: subsequent mutations to 035 * the underlying {@link TSQLEnv} are not reflected, since {@link #adapt} takes a 036 * point-in-time snapshot. Callers that need fresh views should call {@link #adapt} 037 * again. The mutable {@link gudusoft.gsqlparser.catalog.runtime.CatalogOverlay} is 038 * still available on the runtime for new consumers (DDL discovered downstream).</p> 039 */ 040public final class SQLEnvToCatalogRuntimeAdapter { 041 042 public SQLEnvToCatalogRuntimeAdapter() { 043 } 044 045 /** 046 * Build a {@link CatalogRuntime} that exposes the contents of {@code env} as an 047 * immutable {@link CatalogSnapshot}. The snapshot covers every catalog → schema → 048 * table/view/routine/synonym walk reachable from {@link TSQLEnv#getCatalogList()} 049 * including columns on tables and views. 050 */ 051 public CatalogRuntime adapt(TSQLEnv env) { 052 if (env == null) { 053 throw new IllegalArgumentException("SQLEnvToCatalogRuntimeAdapter.adapt: env is required"); 054 } 055 EDbVendor vendor = env.getDBVendor(); 056 IdentifierConfig cfg = IdentifierConfig.defaultsFor(vendor); 057 CatalogSnapshot snapshot = buildSnapshot(env, cfg, vendor); 058 059 InMemoryCatalogProvider provider = new InMemoryCatalogProvider(snapshot); 060 provider.open(CatalogProviderConfig.empty()); 061 return CatalogRuntime.builder() 062 .provider(provider) 063 .vendor(vendor) 064 // EAGER: the snapshot is fully materialized; the resolver answers without 065 // ever calling provider.snapshot(...) again. 066 .loadingMode(CatalogLoadingMode.EAGER) 067 .initialSnapshot(snapshot) 068 .build(); 069 } 070 071 /** 072 * Same as {@link #adapt(TSQLEnv)} but with an explicit {@link IdentifierConfig} 073 * override so the snapshot keys are built under a non-default policy (e.g. MSSQL 074 * collation, MySQL {@code lower_case_table_names}). Vendor must match {@code env}. 075 */ 076 public CatalogRuntime adapt(TSQLEnv env, IdentifierConfig identifierConfig) { 077 if (env == null) { 078 throw new IllegalArgumentException("SQLEnvToCatalogRuntimeAdapter.adapt: env is required"); 079 } 080 if (identifierConfig == null) { 081 throw new IllegalArgumentException( 082 "SQLEnvToCatalogRuntimeAdapter.adapt: identifierConfig is required"); 083 } 084 if (identifierConfig.vendor() != env.getDBVendor()) { 085 throw new IllegalArgumentException( 086 "SQLEnvToCatalogRuntimeAdapter.adapt: identifierConfig.vendor=" 087 + identifierConfig.vendor() + " does not match env.vendor=" + env.getDBVendor()); 088 } 089 EDbVendor vendor = env.getDBVendor(); 090 CatalogSnapshot snapshot = buildSnapshot(env, identifierConfig, vendor); 091 092 InMemoryCatalogProvider provider = new InMemoryCatalogProvider(snapshot); 093 provider.open(CatalogProviderConfig.empty()); 094 return CatalogRuntime.builder() 095 .provider(provider) 096 .vendor(vendor) 097 .loadingMode(CatalogLoadingMode.EAGER) 098 .initialSnapshot(snapshot) 099 .build(); 100 } 101 102 /** 103 * Build the snapshot directly without wrapping it in a runtime. Useful when callers 104 * already manage their own provider lifecycle and just want the catalog-tree walk. 105 */ 106 public CatalogSnapshot snapshot(TSQLEnv env) { 107 if (env == null) { 108 throw new IllegalArgumentException( 109 "SQLEnvToCatalogRuntimeAdapter.snapshot: env is required"); 110 } 111 EDbVendor vendor = env.getDBVendor(); 112 IdentifierConfig cfg = IdentifierConfig.defaultsFor(vendor); 113 return buildSnapshot(env, cfg, vendor); 114 } 115 116 // ---- internals ------------------------------------------------------- 117 118 private static CatalogSnapshot buildSnapshot(TSQLEnv env, IdentifierConfig cfg, 119 EDbVendor vendor) { 120 InMemoryCatalogSnapshot.Builder b = InMemoryCatalogSnapshot.builder().vendor(vendor); 121 122 for (TSQLCatalog catalog : env.getCatalogList()) { 123 String catalogName = catalog.getName(); 124 CatalogQualifiedName cq = CatalogIdentifierPolicy.parse( 125 catalogName, CatalogObjectKind.CATALOG, cfg, vendor); 126 CatalogObjectId catalogId = CatalogEntries.derivedIdFor(cq); 127 b.put(CatalogEntries.builder() 128 .id(catalogId).name(cq).kind(CatalogObjectKind.CATALOG).build(), null); 129 130 for (TSQLSchema schema : catalog.getSchemaList()) { 131 String schemaName = schema.getName(); 132 String schemaQ = catalogName + "." + schemaName; 133 CatalogQualifiedName sq = CatalogIdentifierPolicy.parse( 134 schemaQ, CatalogObjectKind.SCHEMA, cfg, vendor); 135 CatalogObjectId schemaId = CatalogEntries.derivedIdFor(sq); 136 b.put(CatalogEntries.builder() 137 .id(schemaId).name(sq).kind(CatalogObjectKind.SCHEMA).build(), catalogId); 138 139 for (TSQLSchemaObject obj : schema.getSchemaObjectList()) { 140 addObject(b, obj, schemaQ, schemaId, cfg, vendor); 141 } 142 } 143 } 144 return b.materializedAtMillis(System.currentTimeMillis()).build(); 145 } 146 147 private static void addObject(InMemoryCatalogSnapshot.Builder b, 148 TSQLSchemaObject obj, String schemaQ, 149 CatalogObjectId schemaId, IdentifierConfig cfg, 150 EDbVendor vendor) { 151 ESQLDataObjectType dot = obj.getDataObjectType(); 152 String objectQ = schemaQ + "." + obj.getName(); 153 switch (dot) { 154 case dotTable: 155 if (obj instanceof TSQLTable) { 156 TSQLTable t = (TSQLTable) obj; 157 CatalogObjectKind kind = t.isView() 158 ? CatalogObjectKind.VIEW : CatalogObjectKind.TABLE; 159 CatalogQualifiedName tq = CatalogIdentifierPolicy.parse( 160 objectQ, kind, cfg, vendor); 161 CatalogObjectId tid = CatalogEntries.derivedIdFor(tq); 162 CatalogEntries.Builder eb = CatalogEntries.builder() 163 .id(tid).name(tq).kind(kind); 164 if (t.isView() && t.getDefinition() != null) { 165 eb.property("definition", t.getDefinition()); 166 } 167 b.put(eb.build(), schemaId); 168 for (TSQLColumn col : t.getColumnList()) { 169 CatalogQualifiedName colQ = CatalogIdentifierPolicy.parse( 170 objectQ + "." + col.getName(), 171 CatalogObjectKind.COLUMN, cfg, vendor); 172 CatalogEntries.Builder cb = CatalogEntries.builder() 173 .id(CatalogEntries.derivedIdFor(colQ)) 174 .name(colQ).kind(CatalogObjectKind.COLUMN); 175 if (col.getColumnDataType() != null) { 176 cb.property("dataType", col.getColumnDataType().toString()); 177 } 178 b.put(cb.build(), tid); 179 } 180 } 181 break; 182 case dotFunction: 183 emitTopLevelObject(b, objectQ, CatalogObjectKind.FUNCTION, 184 schemaId, cfg, vendor); 185 break; 186 case dotProcedure: 187 emitTopLevelObject(b, objectQ, CatalogObjectKind.PROCEDURE, 188 schemaId, cfg, vendor); 189 break; 190 case dotOraclePackage: 191 emitTopLevelObject(b, objectQ, CatalogObjectKind.PACKAGE, 192 schemaId, cfg, vendor); 193 break; 194 case dotTrigger: 195 emitTopLevelObject(b, objectQ, CatalogObjectKind.TRIGGER, 196 schemaId, cfg, vendor); 197 break; 198 case dotSynonyms: 199 emitTopLevelObject(b, objectQ, CatalogObjectKind.SYNONYM, 200 schemaId, cfg, vendor); 201 break; 202 case dotRoutine: 203 case dotDblink: 204 case dotDataType: 205 // These types exist in TSQLSchema but have no first-class CatalogObjectKind 206 // counterpart yet (Q16 in the design plan defers DOMAIN, EXTENSION, 207 // FOREIGN_TABLE etc. to per-stream additions). Emit an INFO diagnostic so 208 // consumers can see that something was dropped rather than silently 209 // disappearing. WARN would be too aggressive: it's expected that the 210 // first iteration of the catalog SPI doesn't represent every TSQLEnv 211 // sub-type. 212 b.addDiagnostic(CatalogDiagnostic.builder() 213 .severity(CatalogDiagnosticSeverity.INFO) 214 .code(CatalogDiagnosticCode.CATALOG_LOAD_UNSUPPORTED_KIND) 215 .message("TSQLEnv object '" + objectQ + "' of type " + dot 216 + " has no CatalogObjectKind counterpart; not surfaced in snapshot") 217 .build()); 218 break; 219 default: 220 // dotCatalog / dotSchema / dotColumn / dotParameter / dotUnknown — 221 // not first-class children of a TSQLSchema in this context. Skip 222 // silently; the runtime layer is a snapshot view, not an exhaustive 223 // enumerator of every TSQLObject subtype. 224 break; 225 } 226 } 227 228 private static void emitTopLevelObject(InMemoryCatalogSnapshot.Builder b, 229 String objectQ, CatalogObjectKind kind, 230 CatalogObjectId schemaId, IdentifierConfig cfg, 231 EDbVendor vendor) { 232 CatalogQualifiedName q = CatalogIdentifierPolicy.parse(objectQ, kind, cfg, vendor); 233 b.put(CatalogEntries.builder() 234 .id(CatalogEntries.derivedIdFor(q)) 235 .name(q).kind(kind).build(), 236 schemaId); 237 } 238}