Skip to content

Commit 191abb4

Browse files
tianzhouclaude
andauthored
fix: skip range type constructor functions and qualify range columns (#683)
* fix: skip range type constructor functions and qualify range columns A user-defined range type makes PostgreSQL create five LANGUAGE internal constructor functions (range_constructor2/3, multirange_constructor0/1/2). The function queries listed them as user functions, so a dump of any database with a range type was not loadable. Filter out every pg_proc row with an internal dependency, as pg_dump's getFuncs does, in the function, procedure, aggregate and privilege queries. Columns of a range or multirange type fell through the resolved_type CASE to udt_name and lost their schema, so other.textrange rendered as textrange. Qualify 'r' and 'm' types like enums; built-in int4range and int4multirange stay bare. Adds the testdata/dump/range suite as the regression and the design doc for full range type support (this is its Phase 1). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: canonical range functions can only be C, drop the shell-type bootstrap PL/pgSQL rejects a shell type (a pseudo-type) as argument or return type even with check_function_bodies off, and SQL rejects it outright, so a canonical function is always a C function. Capture and render the option but do not try to create the function or a shell type. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 4f6d50a commit 191abb4

8 files changed

Lines changed: 768 additions & 0 deletions

File tree

‎cmd/dump/dump_integration_test.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@ func TestDumpCommand_Inherits(t *testing.T) {
187187
runExactMatchTest(t, "inherits")
188188
}
189189

190+
func TestDumpCommand_Range(t *testing.T) {
191+
if testing.Short() {
192+
t.Skip("Skipping integration test in short mode")
193+
}
194+
runExactMatchTest(t, "range")
195+
}
196+
190197
func TestDumpCommand_Issue421QuotedFKColumns(t *testing.T) {
191198
if testing.Short() {
192199
t.Skip("Skipping integration test in short mode")

‎docs/plans/2026-11-05-range-types-design.md‎

Lines changed: 375 additions & 0 deletions
Large diffs are not rendered by default.

‎ir/queries/queries.sql‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ WITH column_base AS (
137137
ELSE
138138
dn.nspname::text || '.' || dt.typname::text || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '')
139139
END
140+
WHEN dt.typtype IN ('r', 'm') THEN
141+
-- Range and multirange types: built-in ones (int4range, int4multirange)
142+
-- stay bare, user-defined ones are schema-qualified like enums
143+
CASE
144+
WHEN dn.nspname = 'pg_catalog' THEN dt.typname::text
145+
ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname)
146+
END
140147
ELSE c.udt_name::text
141148
END AS resolved_type,
142149
c.is_identity,
@@ -282,6 +289,13 @@ WITH column_base AS (
282289
ELSE
283290
dn.nspname::text || '.' || dt.typname::text || COALESCE(substring(format_type(a.atttypid, a.atttypmod) FROM '\([^)]*\)'), '')
284291
END
292+
WHEN dt.typtype IN ('r', 'm') THEN
293+
-- Range and multirange types: built-in ones (int4range, int4multirange)
294+
-- stay bare, user-defined ones are schema-qualified like enums
295+
CASE
296+
WHEN dn.nspname = 'pg_catalog' THEN dt.typname::text
297+
ELSE quote_ident(dn.nspname) || '.' || quote_ident(dt.typname)
298+
END
285299
ELSE c.udt_name::text
286300
END AS resolved_type,
287301
c.is_identity,
@@ -784,6 +798,14 @@ WHERE
784798
AND dep.refobjsubid = 0
785799
AND dep.deptype = 'e'
786800
)
801+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
802+
-- a range type's constructors, for example, depend internally on the type
803+
AND NOT EXISTS (
804+
SELECT 1 FROM pg_catalog.pg_depend dep
805+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
806+
AND dep.objid = p.oid
807+
AND dep.deptype = 'i'
808+
)
787809
ORDER BY r.routine_schema, r.routine_name;
788810

789811
-- GetProcedures retrieves all user-defined procedures (excluding extension members)
@@ -816,6 +838,14 @@ WHERE
816838
AND dep.refobjsubid = 0
817839
AND dep.deptype = 'e'
818840
)
841+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
842+
-- a range type's constructors, for example, depend internally on the type
843+
AND NOT EXISTS (
844+
SELECT 1 FROM pg_catalog.pg_depend dep
845+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
846+
AND dep.objid = p.oid
847+
AND dep.deptype = 'i'
848+
)
819849
ORDER BY r.routine_schema, r.routine_name;
820850

821851
-- GetAggregates retrieves all user-defined aggregates
@@ -872,6 +902,14 @@ WHERE p.prokind = 'a' -- Only aggregates
872902
AND dep.refobjsubid = 0
873903
AND dep.deptype = 'e'
874904
)
905+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
906+
-- a range type's constructors, for example, depend internally on the type
907+
AND NOT EXISTS (
908+
SELECT 1 FROM pg_catalog.pg_depend dep
909+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
910+
AND dep.objid = p.oid
911+
AND dep.deptype = 'i'
912+
)
875913
ORDER BY n.nspname, p.proname;
876914

877915
-- GetViews retrieves all views and materialized views
@@ -1711,6 +1749,14 @@ WHERE r.routine_schema = $1
17111749
AND dep.refobjsubid = 0
17121750
AND dep.deptype = 'e'
17131751
)
1752+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
1753+
-- a range type's constructors, for example, depend internally on the type
1754+
AND NOT EXISTS (
1755+
SELECT 1 FROM pg_catalog.pg_depend dep
1756+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
1757+
AND dep.objid = p.oid
1758+
AND dep.deptype = 'i'
1759+
)
17141760
ORDER BY r.routine_schema, r.routine_name;
17151761

17161762
-- GetProceduresForSchema retrieves all user-defined procedures for a specific schema
@@ -1745,6 +1791,14 @@ WHERE r.routine_schema = $1
17451791
AND dep.refobjsubid = 0
17461792
AND dep.deptype = 'e'
17471793
)
1794+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
1795+
-- a range type's constructors, for example, depend internally on the type
1796+
AND NOT EXISTS (
1797+
SELECT 1 FROM pg_catalog.pg_depend dep
1798+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
1799+
AND dep.objid = p.oid
1800+
AND dep.deptype = 'i'
1801+
)
17481802
ORDER BY r.routine_schema, r.routine_name;
17491803

17501804
-- GetAggregatesForSchema retrieves all user-defined aggregates for a specific schema.
@@ -1861,6 +1915,14 @@ WHERE p.prokind = 'a' -- Only aggregates
18611915
AND dep.refobjsubid = 0
18621916
AND dep.deptype = 'e'
18631917
)
1918+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
1919+
-- a range type's constructors, for example, depend internally on the type
1920+
AND NOT EXISTS (
1921+
SELECT 1 FROM pg_catalog.pg_depend dep
1922+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
1923+
AND dep.objid = p.oid
1924+
AND dep.deptype = 'i'
1925+
)
18641926
ORDER BY n.nspname, p.proname;
18651927

18661928
-- GetViewsForSchema retrieves all views and materialized views for a specific schema
@@ -2252,6 +2314,14 @@ WITH acl_data AS (
22522314
AND dep.refobjsubid = 0
22532315
AND dep.deptype = 'e'
22542316
)
2317+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
2318+
-- a range type's constructors, for example, depend internally on the type
2319+
AND NOT EXISTS (
2320+
SELECT 1 FROM pg_catalog.pg_depend dep
2321+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
2322+
AND dep.objid = p.oid
2323+
AND dep.deptype = 'i'
2324+
)
22552325
AND p.prokind = 'f'
22562326
AND p.proacl IS NOT NULL
22572327

@@ -2276,6 +2346,14 @@ WITH acl_data AS (
22762346
AND dep.refobjsubid = 0
22772347
AND dep.deptype = 'e'
22782348
)
2349+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
2350+
-- a range type's constructors, for example, depend internally on the type
2351+
AND NOT EXISTS (
2352+
SELECT 1 FROM pg_catalog.pg_depend dep
2353+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
2354+
AND dep.objid = p.oid
2355+
AND dep.deptype = 'i'
2356+
)
22792357
AND p.prokind = 'p'
22802358
AND p.proacl IS NOT NULL
22812359

@@ -2357,6 +2435,14 @@ WITH objects_with_acl AS (
23572435
AND dep.refobjsubid = 0
23582436
AND dep.deptype = 'e'
23592437
)
2438+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
2439+
-- a range type's constructors, for example, depend internally on the type
2440+
AND NOT EXISTS (
2441+
SELECT 1 FROM pg_catalog.pg_depend dep
2442+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
2443+
AND dep.objid = p.oid
2444+
AND dep.deptype = 'i'
2445+
)
23602446
AND p.prokind = 'f'
23612447

23622448
UNION ALL
@@ -2378,6 +2464,14 @@ WITH objects_with_acl AS (
23782464
AND dep.refobjsubid = 0
23792465
AND dep.deptype = 'e'
23802466
)
2467+
-- Skip functions PostgreSQL created for another object (pg_dump does the same):
2468+
-- a range type's constructors, for example, depend internally on the type
2469+
AND NOT EXISTS (
2470+
SELECT 1 FROM pg_catalog.pg_depend dep
2471+
WHERE dep.classid = 'pg_catalog.pg_proc'::regclass
2472+
AND dep.objid = p.oid
2473+
AND dep.deptype = 'i'
2474+
)
23812475
AND p.prokind = 'p'
23822476

23832477
UNION ALL

‎ir/queries/queries.sql.go‎

Lines changed: 94 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎testdata/dump/range/manifest.json‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "range",
3+
"description": "User-defined range types: subtype_diff, an explicit multirange name, a cross-schema range with collation and operator class, and a table using range, multirange and built-in range columns",
4+
"notes": [
5+
"PostgreSQL creates five constructor functions (LANGUAGE internal) for every range type; they depend internally on the type and are not dumped, as in pg_dump.",
6+
"A column of a range or multirange type from another schema keeps its schema prefix; built-in int4range and int4multirange stay bare.",
7+
"The CREATE TYPE ... AS RANGE statements themselves are not yet dumped (Phase 2 of docs/plans/2026-11-05-range-types-design.md)."
8+
]
9+
}

0 commit comments

Comments
 (0)