diff --git a/ir/normalize.go b/ir/normalize.go index a6473a7f..b8aeeea9 100644 --- a/ir/normalize.go +++ b/ir/normalize.go @@ -706,7 +706,7 @@ func normalizePostgreSQLType(input string) string { "pg_catalog.timetz": "timetz", "pg_catalog.interval": "interval", - // Array types (internal PostgreSQL array notation) + // Array types (internal PostgreSQL array notation with underscore prefix) "_text": "text[]", "_int2": "smallint[]", "_int4": "integer[]", @@ -733,6 +733,31 @@ func normalizePostgreSQLType(input string) string { "_timestamptz": "timestamptz[]", "_interval": "interval[]", + // Array types (basetype[] format from SQL query) + "int2[]": "smallint[]", + "int4[]": "integer[]", + "int8[]": "bigint[]", + "float4[]": "real[]", + "float8[]": "double precision[]", + "bool[]": "boolean[]", + "varchar[]": "varchar[]", + "bpchar[]": "character[]", + "numeric[]": "numeric[]", + "uuid[]": "uuid[]", + "json[]": "json[]", + "jsonb[]": "jsonb[]", + "bytea[]": "bytea[]", + "inet[]": "inet[]", + "cidr[]": "cidr[]", + "macaddr[]": "macaddr[]", + "macaddr8[]": "macaddr8[]", + "date[]": "date[]", + "time[]": "time[]", + "timetz[]": "timetz[]", + "timestamp[]": "timestamp[]", + "timestamptz[]": "timestamptz[]", + "interval[]": "interval[]", + // Other common types "pg_catalog.uuid": "uuid", "pg_catalog.json": "json", @@ -780,12 +805,6 @@ func normalizePostgreSQLType(input string) string { return after } - // Handle custom array types (internal PostgreSQL array notation) - // e.g., _my_enum_type -> my_enum_type[] - if strings.HasPrefix(typeName, "_") { - return typeName[1:] + "[]" - } - // Return as-is if no mapping found return typeName } diff --git a/ir/queries/queries.sql b/ir/queries/queries.sql index aec891ff..370e57a7 100644 --- a/ir/queries/queries.sql +++ b/ir/queries/queries.sql @@ -81,8 +81,15 @@ SELECT CASE WHEN dn.nspname = c.table_schema THEN dt.typname ELSE dn.nspname || '.' || dt.typname END + WHEN dt.typtype = 'b' AND dt.typelem <> 0 THEN + -- Array types: apply same schema qualification logic to element type + CASE + WHEN en.nspname = 'pg_catalog' THEN et.typname || '[]' + WHEN en.nspname = c.table_schema THEN et.typname || '[]' + ELSE en.nspname || '.' || et.typname || '[]' + END WHEN dt.typtype = 'b' THEN - -- Base types: qualify if not in pg_catalog or table's schema + -- Non-array base types: qualify if not in pg_catalog or table's schema CASE WHEN dn.nspname = 'pg_catalog' THEN c.udt_name WHEN dn.nspname = c.table_schema THEN dt.typname @@ -110,6 +117,8 @@ LEFT JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attname = c.column_name LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum LEFT JOIN pg_type dt ON dt.oid = a.atttypid LEFT JOIN pg_namespace dn ON dt.typnamespace = dn.oid +LEFT JOIN pg_type et ON dt.typelem = et.oid +LEFT JOIN pg_namespace en ON et.typnamespace = en.oid WHERE c.table_schema NOT IN ('information_schema', 'pg_catalog', 'pg_toast') AND c.table_schema NOT LIKE 'pg_temp_%' @@ -143,8 +152,15 @@ SELECT CASE WHEN dn.nspname = c.table_schema THEN dt.typname ELSE dn.nspname || '.' || dt.typname END + WHEN dt.typtype = 'b' AND dt.typelem <> 0 THEN + -- Array types: apply same schema qualification logic to element type + CASE + WHEN en.nspname = 'pg_catalog' THEN et.typname || '[]' + WHEN en.nspname = c.table_schema THEN et.typname || '[]' + ELSE en.nspname || '.' || et.typname || '[]' + END WHEN dt.typtype = 'b' THEN - -- Base types: qualify if not in pg_catalog or table's schema + -- Non-array base types: qualify if not in pg_catalog or table's schema CASE WHEN dn.nspname = 'pg_catalog' THEN c.udt_name WHEN dn.nspname = c.table_schema THEN dt.typname @@ -172,6 +188,8 @@ LEFT JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attname = c.column_name LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum LEFT JOIN pg_type dt ON dt.oid = a.atttypid LEFT JOIN pg_namespace dn ON dt.typnamespace = dn.oid +LEFT JOIN pg_type et ON dt.typelem = et.oid +LEFT JOIN pg_namespace en ON et.typnamespace = en.oid WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position; diff --git a/ir/queries/queries.sql.go b/ir/queries/queries.sql.go index 0641fc5e..60c7a373 100644 --- a/ir/queries/queries.sql.go +++ b/ir/queries/queries.sql.go @@ -216,8 +216,15 @@ SELECT CASE WHEN dn.nspname = c.table_schema THEN dt.typname ELSE dn.nspname || '.' || dt.typname END + WHEN dt.typtype = 'b' AND dt.typelem <> 0 THEN + -- Array types: apply same schema qualification logic to element type + CASE + WHEN en.nspname = 'pg_catalog' THEN et.typname || '[]' + WHEN en.nspname = c.table_schema THEN et.typname || '[]' + ELSE en.nspname || '.' || et.typname || '[]' + END WHEN dt.typtype = 'b' THEN - -- Base types: qualify if not in pg_catalog or table's schema + -- Non-array base types: qualify if not in pg_catalog or table's schema CASE WHEN dn.nspname = 'pg_catalog' THEN c.udt_name WHEN dn.nspname = c.table_schema THEN dt.typname @@ -245,6 +252,8 @@ LEFT JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attname = c.column_name LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum LEFT JOIN pg_type dt ON dt.oid = a.atttypid LEFT JOIN pg_namespace dn ON dt.typnamespace = dn.oid +LEFT JOIN pg_type et ON dt.typelem = et.oid +LEFT JOIN pg_namespace en ON et.typnamespace = en.oid WHERE c.table_schema NOT IN ('information_schema', 'pg_catalog', 'pg_toast') AND c.table_schema NOT LIKE 'pg_temp_%' @@ -350,8 +359,15 @@ SELECT CASE WHEN dn.nspname = c.table_schema THEN dt.typname ELSE dn.nspname || '.' || dt.typname END + WHEN dt.typtype = 'b' AND dt.typelem <> 0 THEN + -- Array types: apply same schema qualification logic to element type + CASE + WHEN en.nspname = 'pg_catalog' THEN et.typname || '[]' + WHEN en.nspname = c.table_schema THEN et.typname || '[]' + ELSE en.nspname || '.' || et.typname || '[]' + END WHEN dt.typtype = 'b' THEN - -- Base types: qualify if not in pg_catalog or table's schema + -- Non-array base types: qualify if not in pg_catalog or table's schema CASE WHEN dn.nspname = 'pg_catalog' THEN c.udt_name WHEN dn.nspname = c.table_schema THEN dt.typname @@ -379,6 +395,8 @@ LEFT JOIN pg_attribute a ON a.attrelid = cl.oid AND a.attname = c.column_name LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum LEFT JOIN pg_type dt ON dt.oid = a.atttypid LEFT JOIN pg_namespace dn ON dt.typnamespace = dn.oid +LEFT JOIN pg_type et ON dt.typelem = et.oid +LEFT JOIN pg_namespace en ON et.typnamespace = en.oid WHERE c.table_schema = $1 ORDER BY c.table_name, c.ordinal_position diff --git a/testdata/diff/create_table/add_column_array/diff.sql b/testdata/diff/create_table/add_column_array/diff.sql index 2ec1f2b4..71ab5ad8 100644 --- a/testdata/diff/create_table/add_column_array/diff.sql +++ b/testdata/diff/create_table/add_column_array/diff.sql @@ -1 +1,3 @@ ALTER TABLE articles ADD COLUMN tags text[]; + +ALTER TABLE articles ADD COLUMN statuses status[]; diff --git a/testdata/diff/create_table/add_column_array/new.sql b/testdata/diff/create_table/add_column_array/new.sql index 4d25fc14..bead3fc1 100644 --- a/testdata/diff/create_table/add_column_array/new.sql +++ b/testdata/diff/create_table/add_column_array/new.sql @@ -1,5 +1,13 @@ +CREATE TYPE status AS ENUM ( + 'pending', + 'active', + 'inactive', + 'archived' +); + CREATE TABLE public.articles ( id integer NOT NULL, title text, - tags text[] + tags text[], + statuses status[] ); \ No newline at end of file diff --git a/testdata/diff/create_table/add_column_array/old.sql b/testdata/diff/create_table/add_column_array/old.sql index c60430de..49cb2f01 100644 --- a/testdata/diff/create_table/add_column_array/old.sql +++ b/testdata/diff/create_table/add_column_array/old.sql @@ -1,3 +1,10 @@ +CREATE TYPE status AS ENUM ( + 'pending', + 'active', + 'inactive', + 'archived' +); + CREATE TABLE public.articles ( id integer NOT NULL, title text diff --git a/testdata/diff/create_table/add_column_array/plan.json b/testdata/diff/create_table/add_column_array/plan.json index 8d1ffef8..64a63667 100644 --- a/testdata/diff/create_table/add_column_array/plan.json +++ b/testdata/diff/create_table/add_column_array/plan.json @@ -1,9 +1,9 @@ { "version": "1.0.0", - "pgschema_version": "1.4.0", + "pgschema_version": "1.4.1", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "e7d583c1ac9a0f1695b40f23fa0dd1366500b02eaf9c96a5ff5cd65813f48130" + "hash": "35e3acc4a8a360a2e7264d0a04f59eba5538bdda085a20fab68e0586f07ae199" }, "groups": [ { @@ -13,6 +13,12 @@ "type": "table.column", "operation": "create", "path": "public.articles.tags" + }, + { + "sql": "ALTER TABLE articles ADD COLUMN statuses status[];", + "type": "table.column", + "operation": "create", + "path": "public.articles.statuses" } ] } diff --git a/testdata/diff/create_table/add_column_array/plan.sql b/testdata/diff/create_table/add_column_array/plan.sql index 2ec1f2b4..71ab5ad8 100644 --- a/testdata/diff/create_table/add_column_array/plan.sql +++ b/testdata/diff/create_table/add_column_array/plan.sql @@ -1 +1,3 @@ ALTER TABLE articles ADD COLUMN tags text[]; + +ALTER TABLE articles ADD COLUMN statuses status[]; diff --git a/testdata/diff/create_table/add_column_array/plan.txt b/testdata/diff/create_table/add_column_array/plan.txt index 075bed7f..3bd1a517 100644 --- a/testdata/diff/create_table/add_column_array/plan.txt +++ b/testdata/diff/create_table/add_column_array/plan.txt @@ -5,9 +5,12 @@ Summary by type: Tables: ~ articles + + statuses (column) + tags (column) DDL to be executed: -------------------------------------------------- ALTER TABLE articles ADD COLUMN tags text[]; + +ALTER TABLE articles ADD COLUMN statuses status[]; diff --git a/testdata/diff/create_table/enum_array_column/diff.sql b/testdata/diff/create_table/enum_array_column/diff.sql deleted file mode 100644 index f088d604..00000000 --- a/testdata/diff/create_table/enum_array_column/diff.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE items ADD COLUMN statuses status[]; diff --git a/testdata/diff/create_table/enum_array_column/new.sql b/testdata/diff/create_table/enum_array_column/new.sql deleted file mode 100644 index 3a9d488f..00000000 --- a/testdata/diff/create_table/enum_array_column/new.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TYPE status AS ENUM ( - 'pending', - 'active', - 'inactive', - 'archived' -); - -CREATE TABLE items ( - id uuid DEFAULT gen_random_uuid() NOT NULL, - name varchar(255) NOT NULL, - statuses status[] -); diff --git a/testdata/diff/create_table/enum_array_column/old.sql b/testdata/diff/create_table/enum_array_column/old.sql deleted file mode 100644 index f787bd60..00000000 --- a/testdata/diff/create_table/enum_array_column/old.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TYPE status AS ENUM ( - 'pending', - 'active', - 'inactive', - 'archived' -); - -CREATE TABLE items ( - id uuid DEFAULT gen_random_uuid() NOT NULL, - name varchar(255) NOT NULL -); diff --git a/testdata/diff/create_table/enum_array_column/plan.json b/testdata/diff/create_table/enum_array_column/plan.json deleted file mode 100644 index 6e2cee3c..00000000 --- a/testdata/diff/create_table/enum_array_column/plan.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "1.0.0", - "pgschema_version": "1.4.0", - "created_at": "1970-01-01T00:00:00Z", - "source_fingerprint": { - "hash": "68e81c1d995e4acafbcc1d7cb0f4d8e866e177daf6d08e6f00c69e1338c6a14c" - }, - "groups": [ - { - "steps": [ - { - "sql": "ALTER TABLE items ADD COLUMN statuses status[];", - "type": "table.column", - "operation": "create", - "path": "public.items.statuses" - } - ] - } - ] -} diff --git a/testdata/diff/create_table/enum_array_column/plan.sql b/testdata/diff/create_table/enum_array_column/plan.sql deleted file mode 100644 index f088d604..00000000 --- a/testdata/diff/create_table/enum_array_column/plan.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE items ADD COLUMN statuses status[]; diff --git a/testdata/diff/create_table/enum_array_column/plan.txt b/testdata/diff/create_table/enum_array_column/plan.txt deleted file mode 100644 index ab655a9f..00000000 --- a/testdata/diff/create_table/enum_array_column/plan.txt +++ /dev/null @@ -1,13 +0,0 @@ -Plan: 1 to modify. - -Summary by type: - tables: 1 to modify - -Tables: - ~ items - + statuses (column) - -DDL to be executed: --------------------------------------------------- - -ALTER TABLE items ADD COLUMN statuses status[];