Record range constructor functions in pg_range
authorPeter Eisentraut <peter@eisentraut.org>
Thu, 22 Jan 2026 14:17:12 +0000 (15:17 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Thu, 22 Jan 2026 14:56:29 +0000 (15:56 +0100)
When a range type is created, several construction functions are also
created, two for the range type and three for the multirange type.
These have an internal dependency, so they "belong" to the range type.
But there was no way to identify those functions when given a range
type.  An upcoming patch needs access to the two- or possibly the
three-argument range constructor function for a given range type.  The
only way to do that would be with fragile workarounds like matching
names and argument types.  The correct way to do that kind of thing is
to record to the links in the system catalogs.  This is what this
patch does, it records the OIDs of these five constructor functions in
the pg_range catalog.  (Currently, there is no code that makes use of
this.)

Reviewed-by: Paul A Jungwirth <pj@illuminatedcomputing.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Discussion: https://www.postgresql.org/message-id/7d63ddfa-c735-4dfe-8c7a-4f1e2a621058%40eisentraut.org

doc/src/sgml/catalogs.sgml
src/backend/catalog/pg_range.c
src/backend/commands/typecmds.c
src/include/catalog/catversion.h
src/include/catalog/pg_range.dat
src/include/catalog/pg_range.h
src/test/regress/expected/oidjoins.out
src/test/regress/expected/type_sanity.out
src/test/regress/sql/type_sanity.sql

index 2fc634429802657520b507a7f75a4e65c256a366..332193565e2f5943e050b35c22e97e83a02188e2 100644 (file)
@@ -6676,6 +6676,60 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngconstruct2</structfield> <type>regproc</type>
+       (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the 2-argument range constructor function (lower and upper)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngconstruct3</structfield> <type>regproc</type>
+       (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the 3-argument range constructor function (lower, upper, and
+       flags)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmltconstruct0</structfield> <type>regproc</type>
+       (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the 0-argument multirange constructor function (constructs empty
+       range)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmltconstruct1</structfield> <type>regproc</type>
+       (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the 1-argument multirange constructor function (constructs
+       multirange from single range, also used as cast function)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmltconstruct2</structfield> <type>regproc</type>
+       (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the 2-argument multirange constructor function (constructs
+       multirange from array of ranges)
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rngcanonical</structfield> <type>regproc</type>
index cd21c84c8fd62c935146d91a981773289af5d060..cb8c79d0e83f3e59a26b85888ea97e9d9239d051 100644 (file)
@@ -35,7 +35,9 @@
 void
 RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
            Oid rangeSubOpclass, RegProcedure rangeCanonical,
-           RegProcedure rangeSubDiff, Oid multirangeTypeOid)
+           RegProcedure rangeSubDiff, Oid multirangeTypeOid,
+           RegProcedure rangeConstruct2, RegProcedure rangeConstruct3,
+           RegProcedure mltrngConstruct0, RegProcedure mltrngConstruct1, RegProcedure mltrngConstruct2)
 {
    Relation    pg_range;
    Datum       values[Natts_pg_range];
@@ -57,6 +59,11 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
    values[Anum_pg_range_rngcanonical - 1] = ObjectIdGetDatum(rangeCanonical);
    values[Anum_pg_range_rngsubdiff - 1] = ObjectIdGetDatum(rangeSubDiff);
    values[Anum_pg_range_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
+   values[Anum_pg_range_rngconstruct2 - 1] = ObjectIdGetDatum(rangeConstruct2);
+   values[Anum_pg_range_rngconstruct3 - 1] = ObjectIdGetDatum(rangeConstruct3);
+   values[Anum_pg_range_rngmltconstruct0 - 1] = ObjectIdGetDatum(mltrngConstruct0);
+   values[Anum_pg_range_rngmltconstruct1 - 1] = ObjectIdGetDatum(mltrngConstruct1);
+   values[Anum_pg_range_rngmltconstruct2 - 1] = ObjectIdGetDatum(mltrngConstruct2);
 
    tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
index e5fa0578889d58f10da732d2083bc0e516c5ecd0..288edb25f2f4166b97d10d921f5e35bb4b374a9d 100644 (file)
@@ -111,10 +111,12 @@ Oid           binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
 Oid            binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
 
 static void makeRangeConstructors(const char *name, Oid namespace,
-                                 Oid rangeOid, Oid subtype);
+                                 Oid rangeOid, Oid subtype,
+                                 Oid *rangeConstruct2_p, Oid *rangeConstruct3_p);
 static void makeMultirangeConstructors(const char *name, Oid namespace,
                                       Oid multirangeOid, Oid rangeOid,
-                                      Oid rangeArrayOid, Oid *castFuncOid);
+                                      Oid rangeArrayOid,
+                                      Oid *mltrngConstruct0_p, Oid *mltrngConstruct1_p, Oid *mltrngConstruct2_p);
 static Oid findTypeInputFunction(List *procname, Oid typeOid);
 static Oid findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -1406,6 +1408,11 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
    ListCell   *lc;
    ObjectAddress address;
    ObjectAddress mltrngaddress PG_USED_FOR_ASSERTS_ONLY;
+   Oid         rangeConstruct2Oid = InvalidOid;
+   Oid         rangeConstruct3Oid = InvalidOid;
+   Oid         mltrngConstruct0Oid = InvalidOid;
+   Oid         mltrngConstruct1Oid = InvalidOid;
+   Oid         mltrngConstruct2Oid = InvalidOid;
    Oid         castFuncOid;
 
    /* Convert list of names to a name and namespace */
@@ -1661,10 +1668,6 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
                   InvalidOid); /* type's collation (ranges never have one) */
    Assert(multirangeOid == mltrngaddress.objectId);
 
-   /* Create the entry in pg_range */
-   RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-               rangeCanonical, rangeSubtypeDiff, multirangeOid);
-
    /*
     * Create the array type that goes with it.
     */
@@ -1746,10 +1749,18 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
    CommandCounterIncrement();
 
    /* And create the constructor functions for this range type */
-   makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype);
+   makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype,
+                         &rangeConstruct2Oid, &rangeConstruct3Oid);
    makeMultirangeConstructors(multirangeTypeName, typeNamespace,
                               multirangeOid, typoid, rangeArrayOid,
-                              &castFuncOid);
+                              &mltrngConstruct0Oid, &mltrngConstruct1Oid, &mltrngConstruct2Oid);
+   castFuncOid = mltrngConstruct1Oid;
+
+   /* Create the entry in pg_range */
+   RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
+               rangeCanonical, rangeSubtypeDiff, multirangeOid,
+               rangeConstruct2Oid, rangeConstruct3Oid,
+               mltrngConstruct0Oid, mltrngConstruct1Oid, mltrngConstruct2Oid);
 
    /* Create cast from the range type to its multirange type */
    CastCreate(typoid, multirangeOid, castFuncOid, InvalidOid, InvalidOid,
@@ -1769,10 +1780,14 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
  *
  * We actually define 2 functions, with 2 through 3 arguments.  This is just
  * to offer more convenience for the user.
+ *
+ * The OIDs of the created functions are returned through the pointer
+ * arguments.
  */
 static void
 makeRangeConstructors(const char *name, Oid namespace,
-                     Oid rangeOid, Oid subtype)
+                     Oid rangeOid, Oid subtype,
+                     Oid *rangeConstruct2_p, Oid *rangeConstruct3_p)
 {
    static const char *const prosrc[2] = {"range_constructor2",
    "range_constructor3"};
@@ -1833,6 +1848,11 @@ makeRangeConstructors(const char *name, Oid namespace,
         * pg_dump depends on this choice to avoid dumping the constructors.
         */
        recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+
+       if (pronargs[i] == 2)
+           *rangeConstruct2_p = myself.objectId;
+       else if (pronargs[i] == 3)
+           *rangeConstruct3_p = myself.objectId;
    }
 }
 
@@ -1842,13 +1862,13 @@ makeRangeConstructors(const char *name, Oid namespace,
  * If we had an anyrangearray polymorphic type we could use it here,
  * but since each type has its own constructor name there's no need.
  *
- * Sets castFuncOid to the oid of the new constructor that can be used
- * to cast from a range to a multirange.
+ * The OIDs of the created functions are returned through the pointer
+ * arguments.
  */
 static void
 makeMultirangeConstructors(const char *name, Oid namespace,
                           Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
-                          Oid *castFuncOid)
+                          Oid *mltrngConstruct0_p, Oid *mltrngConstruct1_p, Oid *mltrngConstruct2_p)
 {
    ObjectAddress myself,
                referenced;
@@ -1899,6 +1919,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
     * depends on this choice to avoid dumping the constructors.
     */
    recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+   *mltrngConstruct0_p = myself.objectId;
    pfree(argtypes);
 
    /*
@@ -1939,8 +1960,8 @@ makeMultirangeConstructors(const char *name, Oid namespace,
                             0.0);  /* prorows */
    /* ditto */
    recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+   *mltrngConstruct1_p = myself.objectId;
    pfree(argtypes);
-   *castFuncOid = myself.objectId;
 
    /* n-arg constructor - vararg */
    argtypes = buildoidvector(&rangeArrayOid, 1);
@@ -1978,6 +1999,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
                             0.0);  /* prorows */
    /* ditto */
    recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+   *mltrngConstruct2_p = myself.objectId;
    pfree(argtypes);
    pfree(allParameterTypes);
    pfree(parameterModes);
index 13ac6be613e1ad5c22e8511f1dca73bb4c03071b..79db87316211f08e3d05435ec5b2e71cae793f1d 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202601161
+#define CATALOG_VERSION_NO 202601221
 
 #endif
index 830971c494477f29f90786f75ec57b9fd56f5244..fa5e6ff0c3e9062608a13131f6fcbbc14912298a 100644 (file)
 
 { rngtypid => 'int4range', rngsubtype => 'int4',
   rngmultitypid => 'int4multirange', rngsubopc => 'btree/int4_ops',
+  rngconstruct2 => 'int4range(int4,int4)', rngconstruct3 => 'int4range(int4,int4,text)',
+  rngmltconstruct0 => 'int4multirange()', rngmltconstruct1 => 'int4multirange(int4range)', rngmltconstruct2 => 'int4multirange(_int4range)',
   rngcanonical => 'int4range_canonical', rngsubdiff => 'int4range_subdiff' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngmultitypid => 'nummultirange', rngsubopc => 'btree/numeric_ops',
+  rngconstruct2 => 'numrange(numeric,numeric)', rngconstruct3 => 'numrange(numeric,numeric,text)',
+  rngmltconstruct0 => 'nummultirange()', rngmltconstruct1 => 'nummultirange(numrange)', rngmltconstruct2 => 'nummultirange(_numrange)',
   rngcanonical => '-', rngsubdiff => 'numrange_subdiff' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngmultitypid => 'tsmultirange', rngsubopc => 'btree/timestamp_ops',
+  rngconstruct2 => 'tsrange(timestamp,timestamp)', rngconstruct3 => 'tsrange(timestamp,timestamp,text)',
+  rngmltconstruct0 => 'tsmultirange()', rngmltconstruct1 => 'tsmultirange(tsrange)', rngmltconstruct2 => 'tsmultirange(_tsrange)',
   rngcanonical => '-', rngsubdiff => 'tsrange_subdiff' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngmultitypid => 'tstzmultirange', rngsubopc => 'btree/timestamptz_ops',
+  rngconstruct2 => 'tstzrange(timestamptz,timestamptz)', rngconstruct3 => 'tstzrange(timestamptz,timestamptz,text)',
+  rngmltconstruct0 => 'tstzmultirange()', rngmltconstruct1 => 'tstzmultirange(tstzrange)', rngmltconstruct2 => 'tstzmultirange(_tstzrange)',
   rngcanonical => '-', rngsubdiff => 'tstzrange_subdiff' },
 { rngtypid => 'daterange', rngsubtype => 'date',
   rngmultitypid => 'datemultirange', rngsubopc => 'btree/date_ops',
+  rngconstruct2 => 'daterange(date,date)', rngconstruct3 => 'daterange(date,date,text)',
+  rngmltconstruct0 => 'datemultirange()', rngmltconstruct1 => 'datemultirange(daterange)', rngmltconstruct2 => 'datemultirange(_daterange)',
   rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
 { rngtypid => 'int8range', rngsubtype => 'int8',
   rngmultitypid => 'int8multirange', rngsubopc => 'btree/int8_ops',
+  rngconstruct2 => 'int8range(int8,int8)', rngconstruct3 => 'int8range(int8,int8,text)',
+  rngmltconstruct0 => 'int8multirange()', rngmltconstruct1 => 'int8multirange(int8range)', rngmltconstruct2 => 'int8multirange(_int8range)',
   rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
 
 ]
index 5b4f46159056a1a2c72f1afb1fe805aaf62a6468..32ee8cf43a03a0d5e6b1a82e0294040419c2e4ab 100644 (file)
@@ -43,6 +43,15 @@ CATALOG(pg_range,3541,RangeRelationId)
    /* subtype's btree opclass */
    Oid         rngsubopc BKI_LOOKUP(pg_opclass);
 
+   /* range constructor functions */
+   regproc     rngconstruct2 BKI_LOOKUP(pg_proc);
+   regproc     rngconstruct3 BKI_LOOKUP(pg_proc);
+
+   /* multirange constructor functions */
+   regproc     rngmltconstruct0 BKI_LOOKUP(pg_proc);
+   regproc     rngmltconstruct1 BKI_LOOKUP(pg_proc);
+   regproc     rngmltconstruct2 BKI_LOOKUP(pg_proc);
+
    /* canonicalize range, or 0 */
    regproc     rngcanonical BKI_LOOKUP_OPT(pg_proc);
 
@@ -69,7 +78,9 @@ MAKE_SYSCACHE(RANGEMULTIRANGE, pg_range_rngmultitypid_index, 4);
 
 extern void RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
                        Oid rangeSubOpclass, RegProcedure rangeCanonical,
-                       RegProcedure rangeSubDiff, Oid multirangeTypeOid);
+                       RegProcedure rangeSubDiff, Oid multirangeTypeOid,
+                       RegProcedure rangeConstruct2, RegProcedure rangeConstruct3,
+                       RegProcedure mltrngConstruct0, RegProcedure mltrngConstruct1, RegProcedure mltrngConstruct2);
 extern void RangeDelete(Oid rangeTypeOid);
 
 #endif                         /* PG_RANGE_H */
index 215eb899be3e95a74d2a3bdbd67db90437813702..25aaae8d05a341223bd42cad8abce5788b4052bb 100644 (file)
@@ -249,6 +249,11 @@ NOTICE:  checking pg_range {rngsubtype} => pg_type {oid}
 NOTICE:  checking pg_range {rngmultitypid} => pg_type {oid}
 NOTICE:  checking pg_range {rngcollation} => pg_collation {oid}
 NOTICE:  checking pg_range {rngsubopc} => pg_opclass {oid}
+NOTICE:  checking pg_range {rngconstruct2} => pg_proc {oid}
+NOTICE:  checking pg_range {rngconstruct3} => pg_proc {oid}
+NOTICE:  checking pg_range {rngmltconstruct0} => pg_proc {oid}
+NOTICE:  checking pg_range {rngmltconstruct1} => pg_proc {oid}
+NOTICE:  checking pg_range {rngmltconstruct2} => pg_proc {oid}
 NOTICE:  checking pg_range {rngcanonical} => pg_proc {oid}
 NOTICE:  checking pg_range {rngsubdiff} => pg_proc {oid}
 NOTICE:  checking pg_transform {trftype} => pg_type {oid}
index 9ddcacec6bf46bd23d0dd27a3563f3e7f48f1c02..1d21d3eb446782dabef88bba8d9ef10ee3b4b9b9 100644 (file)
@@ -610,7 +610,9 @@ WHERE (is_catalog_text_unique_index_oid(indexrelid) <>
 -- Look for illegal values in pg_range fields.
 SELECT r.rngtypid, r.rngsubtype
 FROM pg_range as r
-WHERE r.rngtypid = 0 OR r.rngsubtype = 0 OR r.rngsubopc = 0;
+WHERE r.rngtypid = 0 OR r.rngsubtype = 0 OR r.rngsubopc = 0
+    OR r.rngconstruct2 = 0 OR r.rngconstruct3 = 0
+    OR r.rngmltconstruct0 = 0 OR r.rngmltconstruct1 = 0 OR r.rngmltconstruct2 = 0;
  rngtypid | rngsubtype 
 ----------+------------
 (0 rows)
@@ -663,6 +665,61 @@ WHERE r.rngmultitypid IS NULL OR r.rngmultitypid = 0;
 ----------+------------+---------------
 (0 rows)
 
+-- check constructor function arguments and return types
+--
+-- proname and prosrc are not required to have these particular
+-- values, but this matches what DefineRange() produces and serves to
+-- sanity-check the catalog entries for built-in types.
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngconstruct2 JOIN pg_type t ON r.rngtypid = t.oid
+WHERE p.pronargs != 2
+    OR p.proargtypes[0] != r.rngsubtype OR p.proargtypes[1] != r.rngsubtype
+    OR p.prorettype != r.rngtypid
+    OR p.proname != t.typname OR p.prosrc != 'range_constructor2';
+ rngtypid | rngsubtype | proname 
+----------+------------+---------
+(0 rows)
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngconstruct3 JOIN pg_type t ON r.rngtypid = t.oid
+WHERE p.pronargs != 3
+    OR p.proargtypes[0] != r.rngsubtype OR p.proargtypes[1] != r.rngsubtype OR p.proargtypes[2] != 'pg_catalog.text'::regtype
+    OR p.prorettype != r.rngtypid
+    OR p.proname != t.typname OR p.prosrc != 'range_constructor3';
+ rngtypid | rngsubtype | proname 
+----------+------------+---------
+(0 rows)
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngmltconstruct0 JOIN pg_type t ON r.rngmultitypid = t.oid
+WHERE p.pronargs != 0
+    OR p.prorettype != r.rngmultitypid
+    OR p.proname != t.typname OR p.prosrc != 'multirange_constructor0';
+ rngtypid | rngsubtype | proname 
+----------+------------+---------
+(0 rows)
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngmltconstruct1 JOIN pg_type t ON r.rngmultitypid = t.oid
+WHERE p.pronargs != 1
+    OR p.proargtypes[0] != r.rngtypid
+    OR p.prorettype != r.rngmultitypid
+    OR p.proname != t.typname OR p.prosrc != 'multirange_constructor1';
+ rngtypid | rngsubtype | proname 
+----------+------------+---------
+(0 rows)
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngmltconstruct2 JOIN pg_type t ON r.rngmultitypid = t.oid JOIN pg_type t2 ON r.rngtypid = t2.oid
+WHERE p.pronargs != 1
+    OR p.proargtypes[0] != t2.typarray
+    OR p.prorettype != r.rngmultitypid
+    OR p.proname != t.typname OR p.prosrc != 'multirange_constructor2';
+ rngtypid | rngsubtype | proname 
+----------+------------+---------
+(0 rows)
+
+-- ******************************************
 -- Create a table that holds all the known in-core data types and leave it
 -- around so as pg_upgrade is able to test their binary compatibility.
 CREATE TABLE tab_core_types AS SELECT
index c2496823d90eb6262d10b1792d4027b56c89f76c..95d5b6e09151acbb0e80db49d2fc9648e89b5c5f 100644 (file)
@@ -451,7 +451,9 @@ WHERE (is_catalog_text_unique_index_oid(indexrelid) <>
 
 SELECT r.rngtypid, r.rngsubtype
 FROM pg_range as r
-WHERE r.rngtypid = 0 OR r.rngsubtype = 0 OR r.rngsubopc = 0;
+WHERE r.rngtypid = 0 OR r.rngsubtype = 0 OR r.rngsubopc = 0
+    OR r.rngconstruct2 = 0 OR r.rngconstruct3 = 0
+    OR r.rngmltconstruct0 = 0 OR r.rngmltconstruct1 = 0 OR r.rngmltconstruct2 = 0;
 
 -- rngcollation should be specified iff subtype is collatable
 
@@ -491,6 +493,49 @@ SELECT r.rngtypid, r.rngsubtype, r.rngmultitypid
 FROM pg_range r
 WHERE r.rngmultitypid IS NULL OR r.rngmultitypid = 0;
 
+-- check constructor function arguments and return types
+--
+-- proname and prosrc are not required to have these particular
+-- values, but this matches what DefineRange() produces and serves to
+-- sanity-check the catalog entries for built-in types.
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngconstruct2 JOIN pg_type t ON r.rngtypid = t.oid
+WHERE p.pronargs != 2
+    OR p.proargtypes[0] != r.rngsubtype OR p.proargtypes[1] != r.rngsubtype
+    OR p.prorettype != r.rngtypid
+    OR p.proname != t.typname OR p.prosrc != 'range_constructor2';
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngconstruct3 JOIN pg_type t ON r.rngtypid = t.oid
+WHERE p.pronargs != 3
+    OR p.proargtypes[0] != r.rngsubtype OR p.proargtypes[1] != r.rngsubtype OR p.proargtypes[2] != 'pg_catalog.text'::regtype
+    OR p.prorettype != r.rngtypid
+    OR p.proname != t.typname OR p.prosrc != 'range_constructor3';
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngmltconstruct0 JOIN pg_type t ON r.rngmultitypid = t.oid
+WHERE p.pronargs != 0
+    OR p.prorettype != r.rngmultitypid
+    OR p.proname != t.typname OR p.prosrc != 'multirange_constructor0';
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngmltconstruct1 JOIN pg_type t ON r.rngmultitypid = t.oid
+WHERE p.pronargs != 1
+    OR p.proargtypes[0] != r.rngtypid
+    OR p.prorettype != r.rngmultitypid
+    OR p.proname != t.typname OR p.prosrc != 'multirange_constructor1';
+
+SELECT r.rngtypid, r.rngsubtype, p.proname
+FROM pg_range r JOIN pg_proc p ON p.oid = r.rngmltconstruct2 JOIN pg_type t ON r.rngmultitypid = t.oid JOIN pg_type t2 ON r.rngtypid = t2.oid
+WHERE p.pronargs != 1
+    OR p.proargtypes[0] != t2.typarray
+    OR p.prorettype != r.rngmultitypid
+    OR p.proname != t.typname OR p.prosrc != 'multirange_constructor2';
+
+
+-- ******************************************
+
 -- Create a table that holds all the known in-core data types and leave it
 -- around so as pg_upgrade is able to test their binary compatibility.
 CREATE TABLE tab_core_types AS SELECT