Skip to content

Commit 2507a92

Browse files
authored
Merge pull request #36 from PostHog/fix/information-schema-varchar-text
Return 'text' for VARCHAR types in information_schema.columns
2 parents afed3e5 + 74b26ed commit 2507a92

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

server/catalog.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,176 @@ func initPgCatalog(db *sql.DB) error {
282282

283283
return nil
284284
}
285+
286+
// initInformationSchema creates the column metadata table and information_schema wrapper views.
287+
// This enables accurate type information (VARCHAR lengths, NUMERIC precision) in information_schema.
288+
func initInformationSchema(db *sql.DB) error {
289+
// Create metadata table to store column type information that DuckDB doesn't preserve
290+
metadataTableSQL := `
291+
CREATE TABLE IF NOT EXISTS __duckgres_column_metadata (
292+
table_schema VARCHAR NOT NULL,
293+
table_name VARCHAR NOT NULL,
294+
column_name VARCHAR NOT NULL,
295+
character_maximum_length INTEGER,
296+
numeric_precision INTEGER,
297+
numeric_scale INTEGER,
298+
PRIMARY KEY (table_schema, table_name, column_name)
299+
)
300+
`
301+
if _, err := db.Exec(metadataTableSQL); err != nil {
302+
// Table might already exist, that's OK
303+
// Ignore errors since PRIMARY KEY might not work in all contexts
304+
}
305+
306+
// Create information_schema.columns wrapper view
307+
// Transforms DuckDB type names to PostgreSQL-compatible names (e.g., VARCHAR -> text)
308+
// First try with metadata table join, fall back to simple view if table doesn't exist
309+
columnsViewWithMetaSQL := `
310+
CREATE OR REPLACE VIEW information_schema_columns_compat AS
311+
SELECT
312+
c.table_catalog,
313+
c.table_schema,
314+
c.table_name,
315+
c.column_name,
316+
c.ordinal_position,
317+
c.column_default,
318+
c.is_nullable,
319+
CASE
320+
WHEN UPPER(c.data_type) = 'VARCHAR' OR UPPER(c.data_type) LIKE 'VARCHAR(%' THEN 'text'
321+
ELSE c.data_type
322+
END AS data_type,
323+
COALESCE(m.character_maximum_length, c.character_maximum_length) AS character_maximum_length,
324+
c.character_octet_length,
325+
COALESCE(m.numeric_precision, c.numeric_precision) AS numeric_precision,
326+
COALESCE(m.numeric_scale, c.numeric_scale) AS numeric_scale,
327+
c.datetime_precision,
328+
NULL AS interval_type,
329+
NULL AS interval_precision,
330+
NULL AS character_set_catalog,
331+
NULL AS character_set_schema,
332+
NULL AS character_set_name,
333+
NULL AS collation_catalog,
334+
NULL AS collation_schema,
335+
NULL AS collation_name,
336+
NULL AS domain_catalog,
337+
NULL AS domain_schema,
338+
NULL AS domain_name,
339+
NULL AS udt_catalog,
340+
NULL AS udt_schema,
341+
NULL AS udt_name,
342+
NULL AS scope_catalog,
343+
NULL AS scope_schema,
344+
NULL AS scope_name,
345+
NULL AS maximum_cardinality,
346+
NULL AS dtd_identifier,
347+
'NO' AS is_self_referencing,
348+
'NO' AS is_identity,
349+
NULL AS identity_generation,
350+
NULL AS identity_start,
351+
NULL AS identity_increment,
352+
NULL AS identity_maximum,
353+
NULL AS identity_minimum,
354+
NULL AS identity_cycle,
355+
'NEVER' AS is_generated,
356+
NULL AS generation_expression,
357+
'YES' AS is_updatable
358+
FROM information_schema.columns c
359+
LEFT JOIN __duckgres_column_metadata m
360+
ON c.table_schema = m.table_schema
361+
AND c.table_name = m.table_name
362+
AND c.column_name = m.column_name
363+
`
364+
// Try with metadata table first
365+
if _, err := db.Exec(columnsViewWithMetaSQL); err != nil {
366+
// Metadata table doesn't exist, create simpler view without it
367+
columnsViewSimpleSQL := `
368+
CREATE OR REPLACE VIEW information_schema_columns_compat AS
369+
SELECT
370+
table_catalog,
371+
table_schema,
372+
table_name,
373+
column_name,
374+
ordinal_position,
375+
column_default,
376+
is_nullable,
377+
CASE
378+
WHEN UPPER(data_type) = 'VARCHAR' OR UPPER(data_type) LIKE 'VARCHAR(%' THEN 'text'
379+
ELSE data_type
380+
END AS data_type,
381+
character_maximum_length,
382+
character_octet_length,
383+
numeric_precision,
384+
numeric_scale,
385+
datetime_precision,
386+
NULL AS interval_type,
387+
NULL AS interval_precision,
388+
NULL AS character_set_catalog,
389+
NULL AS character_set_schema,
390+
NULL AS character_set_name,
391+
NULL AS collation_catalog,
392+
NULL AS collation_schema,
393+
NULL AS collation_name,
394+
NULL AS domain_catalog,
395+
NULL AS domain_schema,
396+
NULL AS domain_name,
397+
NULL AS udt_catalog,
398+
NULL AS udt_schema,
399+
NULL AS udt_name,
400+
NULL AS scope_catalog,
401+
NULL AS scope_schema,
402+
NULL AS scope_name,
403+
NULL AS maximum_cardinality,
404+
NULL AS dtd_identifier,
405+
'NO' AS is_self_referencing,
406+
'NO' AS is_identity,
407+
NULL AS identity_generation,
408+
NULL AS identity_start,
409+
NULL AS identity_increment,
410+
NULL AS identity_maximum,
411+
NULL AS identity_minimum,
412+
NULL AS identity_cycle,
413+
'NEVER' AS is_generated,
414+
NULL AS generation_expression,
415+
'YES' AS is_updatable
416+
FROM information_schema.columns
417+
`
418+
db.Exec(columnsViewSimpleSQL)
419+
}
420+
421+
// Create information_schema.tables wrapper view with additional PostgreSQL columns
422+
tablesViewSQL := `
423+
CREATE OR REPLACE VIEW information_schema_tables_compat AS
424+
SELECT
425+
t.table_catalog,
426+
t.table_schema,
427+
t.table_name,
428+
t.table_type,
429+
NULL AS self_referencing_column_name,
430+
NULL AS reference_generation,
431+
NULL AS user_defined_type_catalog,
432+
NULL AS user_defined_type_schema,
433+
NULL AS user_defined_type_name,
434+
'YES' AS is_insertable_into,
435+
'NO' AS is_typed,
436+
NULL AS commit_action
437+
FROM information_schema.tables t
438+
`
439+
db.Exec(tablesViewSQL)
440+
441+
// Create information_schema.schemata wrapper view
442+
schemataViewSQL := `
443+
CREATE OR REPLACE VIEW information_schema_schemata_compat AS
444+
SELECT
445+
s.catalog_name,
446+
s.schema_name,
447+
'duckdb' AS schema_owner,
448+
NULL AS default_character_set_catalog,
449+
NULL AS default_character_set_schema,
450+
NULL AS default_character_set_name,
451+
NULL AS sql_path
452+
FROM information_schema.schemata s
453+
`
454+
db.Exec(schemataViewSQL)
455+
456+
return nil
457+
}

0 commit comments

Comments
 (0)