Skip to content

Commit 2604ad5

Browse files
EDsCODEclaude
andcommitted
Fix information_schema views for DuckLake mode
The previous implementation tried to create views in ducklake.main after USE ducklake was set, but this caused the views to persist in the shared DuckLake catalog. When the transpiler referenced these views, they couldn't be found because: 1. Views in ducklake.main were stale from previous runs 2. Per-connection views in memory.main weren't accessible via memory.main. prefix after USE ducklake This fix: 1. Splits attachDuckLake into two phases: - attachDuckLake: just attaches the catalog (no USE) - setDuckLakeDefault: runs USE ducklake 2. Creates views BEFORE USE ducklake so they go into memory.main 3. Views query from unqualified "information_schema" which resolves to the default catalog's information_schema at query time 4. Transpiler rewrites to memory.main.information_schema_*_compat which finds the per-connection views created during connection setup The views now work as temporary per-connection views that query from the DuckLake information_schema after USE ducklake is set. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 8de3845 commit 2604ad5

File tree

4 files changed

+43
-35
lines changed

4 files changed

+43
-35
lines changed

server/catalog.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -318,19 +318,16 @@ func initPgCatalog(db *sql.DB) error {
318318

319319
// initInformationSchema creates the column metadata table and information_schema wrapper views.
320320
// This enables accurate type information (VARCHAR lengths, NUMERIC precision) in information_schema.
321-
// When duckLakeMode is true, the views query from ducklake.information_schema instead of the
322-
// local information_schema, since DuckLake is set as the default catalog.
321+
// Views are created in memory.main (before USE ducklake) and query from unqualified information_schema,
322+
// which resolves to the default catalog's information_schema at query time.
323323
func initInformationSchema(db *sql.DB, duckLakeMode bool) error {
324-
// Determine the source information_schema based on mode
325-
// In DuckLake mode, we need to query ducklake.information_schema to see DuckLake tables
326-
// In non-DuckLake mode, we query the local information_schema
324+
// Use just "information_schema" without catalog prefix
325+
// Views are created in memory.main (before USE ducklake) and query from information_schema
326+
// which resolves to the current default catalog's information_schema at query time
327327
infoSchemaPrefix := "information_schema"
328-
if duckLakeMode {
329-
infoSchemaPrefix = "ducklake.information_schema"
330-
}
331328

332329
// Create metadata table to store column type information that DuckDB doesn't preserve
333-
// Table is created in main schema of current database
330+
// Table is created in main schema (which is memory.main before USE ducklake)
334331
metadataTableSQL := `
335332
CREATE TABLE IF NOT EXISTS main.__duckgres_column_metadata (
336333
table_schema VARCHAR NOT NULL,
@@ -351,7 +348,7 @@ func initInformationSchema(db *sql.DB, duckLakeMode bool) error {
351348
// Transforms DuckDB type names to PostgreSQL-compatible names
352349
// Maps: VARCHAR->text, BOOLEAN->boolean, INTEGER->integer, BIGINT->bigint,
353350
// TIMESTAMP->timestamp without time zone, DECIMAL->numeric, etc.
354-
// Views are created in main schema of current database
351+
// Views are created in main schema (which is memory.main before USE ducklake)
355352
columnsViewSQL := `
356353
CREATE OR REPLACE VIEW main.information_schema_columns_compat AS
357354
SELECT

server/server.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func (s *Server) createDBConnection(username string) (*sql.DB, error) {
280280
// Continue anyway - basic queries will still work
281281
}
282282

283-
// Attach DuckLake catalog if configured
283+
// Attach DuckLake catalog if configured (but don't set as default yet)
284284
duckLakeMode := false
285285
if err := s.attachDuckLake(db); err != nil {
286286
// If DuckLake was explicitly configured, fail the connection.
@@ -295,13 +295,22 @@ func (s *Server) createDBConnection(username string) (*sql.DB, error) {
295295
duckLakeMode = true
296296
}
297297

298-
// Initialize information_schema compatibility views
299-
// Must be done AFTER attaching DuckLake so views can reference ducklake.information_schema
298+
// Initialize information_schema compatibility views in memory.main
299+
// Must be done AFTER attaching DuckLake (so views can reference ducklake.information_schema)
300+
// but BEFORE setting DuckLake as default (so views are created in memory.main, not ducklake.main)
300301
if err := initInformationSchema(db, duckLakeMode); err != nil {
301302
log.Printf("Warning: failed to initialize information_schema for user %q: %v", username, err)
302303
// Continue anyway - basic queries will still work
303304
}
304305

306+
// Now set DuckLake as the default catalog so all user queries use it
307+
if duckLakeMode {
308+
if err := setDuckLakeDefault(db); err != nil {
309+
db.Close()
310+
return nil, fmt.Errorf("failed to set DuckLake as default: %w", err)
311+
}
312+
}
313+
305314
return db, nil
306315
}
307316

@@ -333,7 +342,8 @@ func (s *Server) loadExtensions(db *sql.DB) error {
333342
return lastErr
334343
}
335344

336-
// attachDuckLake attaches a DuckLake catalog if configured
345+
// attachDuckLake attaches a DuckLake catalog if configured (but does NOT set it as default).
346+
// Call setDuckLakeDefault after creating per-connection views in memory.main.
337347
func (s *Server) attachDuckLake(db *sql.DB) error {
338348
if s.cfg.DuckLake.MetadataStore == "" {
339349
return nil // DuckLake not configured
@@ -355,10 +365,7 @@ func (s *Server) attachDuckLake(db *sql.DB) error {
355365
var count int
356366
err := db.QueryRow("SELECT COUNT(*) FROM duckdb_databases() WHERE database_name = 'ducklake'").Scan(&count)
357367
if err == nil && count > 0 {
358-
// Already attached, just set as default
359-
if _, err := db.Exec("USE ducklake"); err != nil {
360-
return fmt.Errorf("failed to set DuckLake as default catalog: %w", err)
361-
}
368+
// Already attached
362369
return nil
363370
}
364371

@@ -398,13 +405,17 @@ func (s *Server) attachDuckLake(db *sql.DB) error {
398405
return fmt.Errorf("failed to attach DuckLake: %w", err)
399406
}
400407

401-
// Set DuckLake as the default catalog so all queries use it
402-
// See: https://duckdb.org/docs/stable/core_extensions/ducklake#usage
408+
log.Printf("Attached DuckLake catalog successfully")
409+
return nil
410+
}
411+
412+
// setDuckLakeDefault sets the DuckLake catalog as the default so all queries use it.
413+
// This should be called AFTER creating per-connection views in memory.main.
414+
func setDuckLakeDefault(db *sql.DB) error {
403415
if _, err := db.Exec("USE ducklake"); err != nil {
404416
return fmt.Errorf("failed to set DuckLake as default catalog: %w", err)
405417
}
406-
407-
log.Printf("Attached DuckLake catalog successfully and set as default")
418+
log.Printf("Set DuckLake as default catalog")
408419
return nil
409420
}
410421

transpiler/transform/information_schema.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,17 @@ func (t *InformationSchemaTransform) walkAndTransform(node *pg_query.Node, chang
6060

6161
switch n := node.Node.(type) {
6262
case *pg_query.Node_RangeVar:
63-
// Table references: information_schema.columns -> information_schema_columns_compat
64-
// Views are created in the main schema of the current database
63+
// Table references: information_schema.columns -> memory.main.information_schema_columns_compat
64+
// Views are created in the memory.main schema so they're always accessible regardless of default catalog
6565
// Skip transformation if catalog is explicitly "ducklake" - those are queries from
6666
// within the compat views that need to access the actual DuckLake information_schema
6767
if n.RangeVar != nil && strings.EqualFold(n.RangeVar.Schemaname, "information_schema") &&
6868
!strings.EqualFold(n.RangeVar.Catalogname, "ducklake") {
6969
relname := strings.ToLower(n.RangeVar.Relname)
7070
if newName, ok := t.ViewMappings[relname]; ok {
7171
n.RangeVar.Relname = newName
72-
// Views are in main schema (no catalog prefix needed)
73-
n.RangeVar.Catalogname = ""
72+
// Views are in memory.main schema - always use explicit catalog for DuckLake compatibility
73+
n.RangeVar.Catalogname = "memory"
7474
n.RangeVar.Schemaname = "main"
7575
*changed = true
7676
}

transpiler/transpiler_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,31 +105,31 @@ func TestTranspile_InformationSchema(t *testing.T) {
105105
{
106106
name: "information_schema.columns -> compat view",
107107
input: "SELECT * FROM information_schema.columns",
108-
contains: "information_schema_columns_compat",
108+
contains: "memory.main.information_schema_columns_compat",
109109
excludes: "information_schema.columns",
110110
},
111111
{
112112
name: "information_schema.tables -> compat view",
113113
input: "SELECT * FROM information_schema.tables",
114-
contains: "information_schema_tables_compat",
114+
contains: "memory.main.information_schema_tables_compat",
115115
excludes: "information_schema.tables",
116116
},
117117
{
118118
name: "information_schema.schemata -> compat view",
119119
input: "SELECT * FROM information_schema.schemata",
120-
contains: "information_schema_schemata_compat",
120+
contains: "memory.main.information_schema_schemata_compat",
121121
excludes: "information_schema.schemata",
122122
},
123123
{
124124
name: "INFORMATION_SCHEMA.COLUMNS uppercase -> compat view",
125125
input: "SELECT * FROM INFORMATION_SCHEMA.COLUMNS",
126-
contains: "information_schema_columns_compat",
126+
contains: "memory.main.information_schema_columns_compat",
127127
excludes: "information_schema.columns",
128128
},
129129
{
130130
name: "aliased information_schema query",
131131
input: "SELECT c.column_name FROM information_schema.columns c WHERE c.table_name = 'test'",
132-
contains: "information_schema_columns_compat",
132+
contains: "memory.main.information_schema_columns_compat",
133133
excludes: "information_schema.columns",
134134
},
135135
{
@@ -169,14 +169,14 @@ func TestTranspile_InformationSchema_DuckLakeMode(t *testing.T) {
169169
contains string
170170
}{
171171
{
172-
name: "information_schema.columns with DuckLake -> main qualified",
172+
name: "information_schema.columns with DuckLake -> memory.main qualified",
173173
input: "SELECT * FROM information_schema.columns",
174-
contains: "main.information_schema_columns_compat",
174+
contains: "memory.main.information_schema_columns_compat",
175175
},
176176
{
177-
name: "information_schema.tables with DuckLake -> main qualified",
177+
name: "information_schema.tables with DuckLake -> memory.main qualified",
178178
input: "SELECT * FROM information_schema.tables",
179-
contains: "main.information_schema_tables_compat",
179+
contains: "memory.main.information_schema_tables_compat",
180180
},
181181
}
182182

0 commit comments

Comments
 (0)