001package gudusoft.gsqlparser.sqlenv.compat; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.catalog.input.CatalogLoadOptions; 005import gudusoft.gsqlparser.catalog.runtime.CatalogRuntime; 006import gudusoft.gsqlparser.sqlenv.TSQLCatalog; 007import gudusoft.gsqlparser.sqlenv.TSQLEnv; 008import gudusoft.gsqlparser.sqlenv.catalog.ICatalogProvider; 009 010/** 011 * Lazy bridge: a {@link TSQLEnv} subclass whose only job is to install 012 * {@link CatalogBackedCatalogProvider} via the legacy {@link ICatalogProvider} hook. 013 * 014 * <p>Plan §5.3 / §8.3. Per spike {@code bridge-access-trace.md} no method override is 015 * required: {@link TSQLEnv#doSearchSchemaObject} already dispatches to 016 * {@link ICatalogProvider#findObject} for fully-qualified lookups, and the legacy 017 * mutation paths ({@code addFunction}, {@code setDefaultCatalogName}, {@code addObject}) 018 * land naturally on the wrapped delegate.</p> 019 * 020 * <p>The constructor calls {@code super(vendor)} (which builds the default 021 * {@code CatalogStoreProvider}), captures that as the delegate, and then installs the 022 * {@link CatalogBackedCatalogProvider} wrapper via the protected 023 * {@code setCatalogProviderForCompatibility} hook added in P1D.1. Subsequent 024 * {@link #getCatalogProvider()} calls return the bridged provider; lookups missing 025 * from the legacy overlay will round-trip through the runtime resolver, materialize 026 * via {@link CatalogEntryToSQLEnvMapper}, and be cached back into the delegate so the 027 * next lookup is O(1).</p> 028 * 029 * <p>{@link #initSQLEnv()} is a no-op: the runtime is the metadata source, the bridge 030 * is just a thin compatibility veneer. Construction also applies defaults from 031 * {@link CatalogLoadOptions} (default catalog / schema / server) so the bridge plays the 032 * same active-context role as the corresponding eager-loaded {@link TSQLEnv}.</p> 033 */ 034public final class CatalogRuntimeToSQLEnvBridge extends TSQLEnv { 035 036 private final CatalogRuntime runtime; 037 private final CatalogLoadOptions options; 038 039 /** 040 * Construct a bridge for the given runtime. Vendor is read from the runtime; the 041 * options carry the default catalog/schema/server applied to {@link TSQLEnv}. 042 * 043 * @param runtime the runtime that owns the snapshot, overlay, and lazy provider. 044 * @param options optional load options; {@code null} is allowed — defaults are 045 * read from the runtime alone in that case. 046 */ 047 public CatalogRuntimeToSQLEnvBridge(CatalogRuntime runtime, CatalogLoadOptions options) { 048 super(requireRuntime(runtime).vendor()); 049 this.runtime = runtime; 050 this.options = options; 051 applyDefaults(options); 052 ICatalogProvider delegate = getCatalogProvider(); 053 CatalogBackedCatalogProvider bridged = new CatalogBackedCatalogProvider( 054 runtime, delegate, new CatalogEntryToSQLEnvMapper(), this, 055 options != null ? options.identifierConfig() : null, 056 options != null ? options.diagnosticSink() : null); 057 setCatalogProviderForCompatibility(bridged); 058 } 059 060 /** Construct a bridge for the given runtime and explicit vendor. Vendor must match. */ 061 public CatalogRuntimeToSQLEnvBridge(EDbVendor vendor, CatalogRuntime runtime, 062 CatalogLoadOptions options) { 063 super(vendor); 064 if (runtime == null) { 065 throw new IllegalArgumentException( 066 "CatalogRuntimeToSQLEnvBridge: runtime must not be null"); 067 } 068 if (vendor == null) { 069 throw new IllegalArgumentException( 070 "CatalogRuntimeToSQLEnvBridge: vendor must not be null"); 071 } 072 if (runtime.vendor() != vendor) { 073 throw new IllegalArgumentException( 074 "CatalogRuntimeToSQLEnvBridge: runtime.vendor=" + runtime.vendor() 075 + " does not match vendor=" + vendor); 076 } 077 this.runtime = runtime; 078 this.options = options; 079 applyDefaults(options); 080 ICatalogProvider delegate = getCatalogProvider(); 081 CatalogBackedCatalogProvider bridged = new CatalogBackedCatalogProvider( 082 runtime, delegate, new CatalogEntryToSQLEnvMapper(), this, 083 options != null ? options.identifierConfig() : null, 084 options != null ? options.diagnosticSink() : null); 085 setCatalogProviderForCompatibility(bridged); 086 } 087 088 /** 089 * The runtime backing this bridge. Useful for tests that want to inspect the 090 * snapshot/overlay directly without reaching through {@link #getCatalogProvider}. 091 */ 092 public CatalogRuntime runtime() { 093 return runtime; 094 } 095 096 /** 097 * Load options the bridge was constructed with, or {@code null} if none. Retained so 098 * downstream callers can introspect the loading mode / fetch caps without re-reading 099 * the runtime. 100 */ 101 public CatalogLoadOptions options() { 102 return options; 103 } 104 105 @Override 106 public void initSQLEnv() { 107 // The runtime IS the metadata source; nothing to materialize lazily here. 108 // Legacy callers that build a TSQLEnv via parser.sqlenv = bridge expect 109 // initSQLEnv() to be a no-op when metadata is supplied externally. 110 } 111 112 private static CatalogRuntime requireRuntime(CatalogRuntime runtime) { 113 if (runtime == null) { 114 throw new IllegalArgumentException( 115 "CatalogRuntimeToSQLEnvBridge: runtime must not be null"); 116 } 117 return runtime; 118 } 119 120 private void applyDefaults(CatalogLoadOptions opts) { 121 if (opts == null) return; 122 if (opts.defaultCatalog() != null && !opts.defaultCatalog().isEmpty()) { 123 setDefaultCatalogName(opts.defaultCatalog()); 124 } 125 if (opts.defaultSchema() != null && !opts.defaultSchema().isEmpty()) { 126 setDefaultSchemaName(opts.defaultSchema()); 127 } 128 if (opts.defaultServer() != null && !opts.defaultServer().isEmpty()) { 129 setDefaultServerName(opts.defaultServer()); 130 } 131 // Pre-register an empty default catalog/schema in the legacy catalogList so the 132 // legacy {@code TSQLEnv.doSearchSchemaObject} '..table' fallback can find them 133 // and route through {@code catalogProvider.findObject} (= the bridge's lookup 134 // path). Without this, unqualified names like {@code SELECT * FROM employees} 135 // never reach the bridge: the legacy path iterates {@code catalogList} which is 136 // empty until something is materialized through the bridge, and the iteration 137 // body — which is what calls findObject — never runs. Registering empties is 138 // safe: the catalog/schema are real {@code TSQLCatalog}/{@code TSQLSchema} 139 // instances that hold zero materialized objects until the bridge populates them 140 // on demand. 141 if (opts.defaultCatalog() != null && !opts.defaultCatalog().isEmpty()) { 142 TSQLCatalog c = getSQLCatalog(opts.defaultCatalog(), true); 143 if (c != null && opts.defaultSchema() != null && !opts.defaultSchema().isEmpty()) { 144 c.getSchema(opts.defaultSchema(), true); 145 } 146 } 147 } 148}