mirror of
https://github.com/postgres/postgres.git
synced 2025-07-26 01:22:12 +03:00
Multirange datatypes
Multiranges are basically sorted arrays of non-overlapping ranges with set-theoretic operations defined over them. Since v14, each range type automatically gets a corresponding multirange datatype. There are both manual and automatic mechanisms for naming multirange types. Once can specify multirange type name using multirange_type_name attribute in CREATE TYPE. Otherwise, a multirange type name is generated automatically. If the range type name contains "range" then we change that to "multirange". Otherwise, we add "_multirange" to the end. Implementation of multiranges comes with a space-efficient internal representation format, which evades extra paddings and duplicated storage of oids. Altogether this format allows fetching a particular range by its index in O(n). Statistic gathering and selectivity estimation are implemented for multiranges. For this purpose, stored multirange is approximated as union range without gaps. This field will likely need improvements in the future. Catversion is bumped. Discussion: https://postgr.es/m/CALNJ-vSUpQ_Y%3DjXvTxt1VYFztaBSsWVXeF1y6gTYQ4bOiWDLgQ%40mail.gmail.com Discussion: https://postgr.es/m/a0b8026459d1e6167933be2104a6174e7d40d0ab.camel%40j-davis.com#fe7218c83b08068bfffb0c5293eceda0 Author: Paul Jungwirth, revised by me Reviewed-by: David Fetter, Corey Huinker, Jeff Davis, Pavel Stehule Reviewed-by: Alvaro Herrera, Tom Lane, Isaac Morland, David G. Johnston Reviewed-by: Zhihong Yu, Alexander Korotkov
This commit is contained in:
@ -107,9 +107,14 @@ typedef struct
|
||||
|
||||
/* Potentially set by pg_upgrade_support functions */
|
||||
Oid binary_upgrade_next_array_pg_type_oid = InvalidOid;
|
||||
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);
|
||||
static void makeMultirangeConstructors(const char *name, Oid namespace,
|
||||
Oid multirangeOid, Oid rangeOid,
|
||||
Oid rangeArrayOid, Oid *castFuncOid);
|
||||
static Oid findTypeInputFunction(List *procname, Oid typeOid);
|
||||
static Oid findTypeOutputFunction(List *procname, Oid typeOid);
|
||||
static Oid findTypeReceiveFunction(List *procname, Oid typeOid);
|
||||
@ -772,7 +777,8 @@ DefineDomain(CreateDomainStmt *stmt)
|
||||
typtype != TYPTYPE_COMPOSITE &&
|
||||
typtype != TYPTYPE_DOMAIN &&
|
||||
typtype != TYPTYPE_ENUM &&
|
||||
typtype != TYPTYPE_RANGE)
|
||||
typtype != TYPTYPE_RANGE &&
|
||||
typtype != TYPTYPE_MULTIRANGE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("\"%s\" is not a valid base type for a domain",
|
||||
@ -1323,6 +1329,11 @@ checkEnumOwner(HeapTuple tup)
|
||||
/*
|
||||
* DefineRange
|
||||
* Registers a new range type.
|
||||
*
|
||||
* Perhaps it might be worthwhile to set pg_type.typelem to the base type,
|
||||
* and likewise on multiranges to set it to the range type. But having a
|
||||
* non-zero typelem is treated elsewhere as a synonym for being an array,
|
||||
* and users might have queries with that same assumption.
|
||||
*/
|
||||
ObjectAddress
|
||||
DefineRange(CreateRangeStmt *stmt)
|
||||
@ -1331,7 +1342,12 @@ DefineRange(CreateRangeStmt *stmt)
|
||||
Oid typeNamespace;
|
||||
Oid typoid;
|
||||
char *rangeArrayName;
|
||||
char *multirangeTypeName = NULL;
|
||||
char *multirangeArrayName;
|
||||
Oid multirangeNamespace = InvalidOid;
|
||||
Oid rangeArrayOid;
|
||||
Oid multirangeOid;
|
||||
Oid multirangeArrayOid;
|
||||
Oid rangeSubtype = InvalidOid;
|
||||
List *rangeSubOpclassName = NIL;
|
||||
List *rangeCollationName = NIL;
|
||||
@ -1348,6 +1364,8 @@ DefineRange(CreateRangeStmt *stmt)
|
||||
AclResult aclresult;
|
||||
ListCell *lc;
|
||||
ObjectAddress address;
|
||||
ObjectAddress mltrngaddress;
|
||||
Oid castFuncOid;
|
||||
|
||||
/* Convert list of names to a name and namespace */
|
||||
typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
|
||||
@ -1431,6 +1449,16 @@ DefineRange(CreateRangeStmt *stmt)
|
||||
errmsg("conflicting or redundant options")));
|
||||
rangeSubtypeDiffName = defGetQualifiedName(defel);
|
||||
}
|
||||
else if (strcmp(defel->defname, "multirange_type_name") == 0)
|
||||
{
|
||||
if (multirangeTypeName != NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("conflicting or redundant options")));
|
||||
/* we can look up the subtype name immediately */
|
||||
multirangeNamespace = QualifiedNameGetCreationNamespace(defGetQualifiedName(defel),
|
||||
&multirangeTypeName);
|
||||
}
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
@ -1496,8 +1524,10 @@ DefineRange(CreateRangeStmt *stmt)
|
||||
/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
|
||||
alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
|
||||
|
||||
/* Allocate OID for array type */
|
||||
/* Allocate OID for array type, its multirange, and its multirange array */
|
||||
rangeArrayOid = AssignTypeArrayOid();
|
||||
multirangeOid = AssignTypeMultirangeOid();
|
||||
multirangeArrayOid = AssignTypeMultirangeArrayOid();
|
||||
|
||||
/* Create the pg_type entry */
|
||||
address =
|
||||
@ -1536,9 +1566,75 @@ DefineRange(CreateRangeStmt *stmt)
|
||||
Assert(typoid == InvalidOid || typoid == address.objectId);
|
||||
typoid = address.objectId;
|
||||
|
||||
/* Create the multirange that goes with it */
|
||||
if (multirangeTypeName)
|
||||
{
|
||||
Oid old_typoid;
|
||||
|
||||
/*
|
||||
* Look to see if multirange type already exists.
|
||||
*/
|
||||
old_typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
|
||||
CStringGetDatum(multirangeTypeName),
|
||||
ObjectIdGetDatum(multirangeNamespace));
|
||||
|
||||
/*
|
||||
* If it's not a shell, see if it's an autogenerated array type, and if so
|
||||
* rename it out of the way.
|
||||
*/
|
||||
if (OidIsValid(old_typoid) && get_typisdefined(old_typoid))
|
||||
{
|
||||
if (!moveArrayTypeName(old_typoid, multirangeTypeName, multirangeNamespace))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
||||
errmsg("type \"%s\" already exists", multirangeTypeName)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Generate multirange name automatically */
|
||||
multirangeNamespace = typeNamespace;
|
||||
multirangeTypeName = makeMultirangeTypeName(typeName, multirangeNamespace);
|
||||
}
|
||||
|
||||
mltrngaddress =
|
||||
TypeCreate(multirangeOid, /* force assignment of this type OID */
|
||||
multirangeTypeName, /* type name */
|
||||
multirangeNamespace, /* namespace */
|
||||
InvalidOid, /* relation oid (n/a here) */
|
||||
0, /* relation kind (ditto) */
|
||||
GetUserId(), /* owner's ID */
|
||||
-1, /* internal size (always varlena) */
|
||||
TYPTYPE_MULTIRANGE, /* type-type (multirange type) */
|
||||
TYPCATEGORY_RANGE, /* type-category (range type) */
|
||||
false, /* multirange types are never preferred */
|
||||
DEFAULT_TYPDELIM, /* array element delimiter */
|
||||
F_MULTIRANGE_IN, /* input procedure */
|
||||
F_MULTIRANGE_OUT, /* output procedure */
|
||||
F_MULTIRANGE_RECV, /* receive procedure */
|
||||
F_MULTIRANGE_SEND, /* send procedure */
|
||||
InvalidOid, /* typmodin procedure - none */
|
||||
InvalidOid, /* typmodout procedure - none */
|
||||
F_MULTIRANGE_TYPANALYZE, /* analyze procedure */
|
||||
InvalidOid, /* subscript procedure - none */
|
||||
InvalidOid, /* element type ID - none */
|
||||
false, /* this is not an array type */
|
||||
multirangeArrayOid, /* array type we are about to create */
|
||||
InvalidOid, /* base type ID (only for domains) */
|
||||
NULL, /* never a default type value */
|
||||
NULL, /* no binary form available either */
|
||||
false, /* never passed by value */
|
||||
alignment, /* alignment */
|
||||
'x', /* TOAST strategy (always extended) */
|
||||
-1, /* typMod (Domains only) */
|
||||
0, /* Array dimensions of typbasetype */
|
||||
false, /* Type NOT NULL */
|
||||
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);
|
||||
rangeCanonical, rangeSubtypeDiff, multirangeOid);
|
||||
|
||||
/*
|
||||
* Create the array type that goes with it.
|
||||
@ -1580,8 +1676,54 @@ DefineRange(CreateRangeStmt *stmt)
|
||||
|
||||
pfree(rangeArrayName);
|
||||
|
||||
/* Create the multirange's array type */
|
||||
|
||||
multirangeArrayName = makeArrayTypeName(multirangeTypeName, typeNamespace);
|
||||
|
||||
TypeCreate(multirangeArrayOid, /* force assignment of this type OID */
|
||||
multirangeArrayName, /* type name */
|
||||
multirangeNamespace, /* namespace */
|
||||
InvalidOid, /* relation oid (n/a here) */
|
||||
0, /* relation kind (ditto) */
|
||||
GetUserId(), /* owner's ID */
|
||||
-1, /* internal size (always varlena) */
|
||||
TYPTYPE_BASE, /* type-type (base type) */
|
||||
TYPCATEGORY_ARRAY, /* type-category (array) */
|
||||
false, /* array types are never preferred */
|
||||
DEFAULT_TYPDELIM, /* array element delimiter */
|
||||
F_ARRAY_IN, /* input procedure */
|
||||
F_ARRAY_OUT, /* output procedure */
|
||||
F_ARRAY_RECV, /* receive procedure */
|
||||
F_ARRAY_SEND, /* send procedure */
|
||||
InvalidOid, /* typmodin procedure - none */
|
||||
InvalidOid, /* typmodout procedure - none */
|
||||
F_ARRAY_TYPANALYZE, /* analyze procedure */
|
||||
F_ARRAY_SUBSCRIPT_HANDLER, /* array subscript procedure */
|
||||
multirangeOid, /* element type ID */
|
||||
true, /* yes this is an array type */
|
||||
InvalidOid, /* no further array type */
|
||||
InvalidOid, /* base type ID */
|
||||
NULL, /* never a default type value */
|
||||
NULL, /* binary default isn't sent either */
|
||||
false, /* never passed by value */
|
||||
alignment, /* alignment - same as range's */
|
||||
'x', /* ARRAY is always toastable */
|
||||
-1, /* typMod (Domains only) */
|
||||
0, /* Array dimensions of typbasetype */
|
||||
false, /* Type NOT NULL */
|
||||
InvalidOid); /* typcollation */
|
||||
|
||||
/* And create the constructor functions for this range type */
|
||||
makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype);
|
||||
makeMultirangeConstructors(multirangeTypeName, typeNamespace,
|
||||
multirangeOid, typoid, rangeArrayOid,
|
||||
&castFuncOid);
|
||||
|
||||
/* Create cast from the range type to its multirange type */
|
||||
CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
|
||||
|
||||
pfree(multirangeTypeName);
|
||||
pfree(multirangeArrayName);
|
||||
|
||||
return address;
|
||||
}
|
||||
@ -1659,6 +1801,149 @@ makeRangeConstructors(const char *name, Oid namespace,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We make a separate multirange constructor for each range type
|
||||
* so its name can include the base type, like range constructors do.
|
||||
* 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.
|
||||
*/
|
||||
static void
|
||||
makeMultirangeConstructors(const char *name, Oid namespace,
|
||||
Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
|
||||
Oid *castFuncOid)
|
||||
{
|
||||
ObjectAddress myself,
|
||||
referenced;
|
||||
oidvector *argtypes;
|
||||
Datum allParamTypes;
|
||||
ArrayType *allParameterTypes;
|
||||
Datum paramModes;
|
||||
ArrayType *parameterModes;
|
||||
|
||||
referenced.classId = TypeRelationId;
|
||||
referenced.objectId = multirangeOid;
|
||||
referenced.objectSubId = 0;
|
||||
|
||||
/* 0-arg constructor - for empty multiranges */
|
||||
argtypes = buildoidvector(NULL, 0);
|
||||
myself = ProcedureCreate(name, /* name: same as multirange type */
|
||||
namespace,
|
||||
false, /* replace */
|
||||
false, /* returns set */
|
||||
multirangeOid, /* return type */
|
||||
BOOTSTRAP_SUPERUSERID, /* proowner */
|
||||
INTERNALlanguageId, /* language */
|
||||
F_FMGR_INTERNAL_VALIDATOR,
|
||||
"multirange_constructor0", /* prosrc */
|
||||
NULL, /* probin */
|
||||
PROKIND_FUNCTION,
|
||||
false, /* security_definer */
|
||||
false, /* leakproof */
|
||||
false, /* isStrict */
|
||||
PROVOLATILE_IMMUTABLE, /* volatility */
|
||||
PROPARALLEL_SAFE, /* parallel safety */
|
||||
argtypes, /* parameterTypes */
|
||||
PointerGetDatum(NULL), /* allParameterTypes */
|
||||
PointerGetDatum(NULL), /* parameterModes */
|
||||
PointerGetDatum(NULL), /* parameterNames */
|
||||
NIL, /* parameterDefaults */
|
||||
PointerGetDatum(NULL), /* trftypes */
|
||||
PointerGetDatum(NULL), /* proconfig */
|
||||
InvalidOid, /* prosupport */
|
||||
1.0, /* procost */
|
||||
0.0); /* prorows */
|
||||
|
||||
/*
|
||||
* Make the constructor internally-dependent on the multirange type so
|
||||
* that they go away silently when the type is dropped. Note that pg_dump
|
||||
* depends on this choice to avoid dumping the constructors.
|
||||
*/
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
|
||||
pfree(argtypes);
|
||||
|
||||
/*
|
||||
* 1-arg constructor - for casts
|
||||
*
|
||||
* In theory we shouldn't need both this and the vararg (n-arg)
|
||||
* constructor, but having a separate 1-arg function lets us define casts
|
||||
* against it.
|
||||
*/
|
||||
argtypes = buildoidvector(&rangeOid, 1);
|
||||
myself = ProcedureCreate(name, /* name: same as multirange type */
|
||||
namespace,
|
||||
false, /* replace */
|
||||
false, /* returns set */
|
||||
multirangeOid, /* return type */
|
||||
BOOTSTRAP_SUPERUSERID, /* proowner */
|
||||
INTERNALlanguageId, /* language */
|
||||
F_FMGR_INTERNAL_VALIDATOR,
|
||||
"multirange_constructor1", /* prosrc */
|
||||
NULL, /* probin */
|
||||
PROKIND_FUNCTION,
|
||||
false, /* security_definer */
|
||||
false, /* leakproof */
|
||||
true, /* isStrict */
|
||||
PROVOLATILE_IMMUTABLE, /* volatility */
|
||||
PROPARALLEL_SAFE, /* parallel safety */
|
||||
argtypes, /* parameterTypes */
|
||||
PointerGetDatum(NULL), /* allParameterTypes */
|
||||
PointerGetDatum(NULL), /* parameterModes */
|
||||
PointerGetDatum(NULL), /* parameterNames */
|
||||
NIL, /* parameterDefaults */
|
||||
PointerGetDatum(NULL), /* trftypes */
|
||||
PointerGetDatum(NULL), /* proconfig */
|
||||
InvalidOid, /* prosupport */
|
||||
1.0, /* procost */
|
||||
0.0); /* prorows */
|
||||
/* ditto */
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
|
||||
pfree(argtypes);
|
||||
*castFuncOid = myself.objectId;
|
||||
|
||||
/* n-arg constructor - vararg */
|
||||
argtypes = buildoidvector(&rangeArrayOid, 1);
|
||||
allParamTypes = ObjectIdGetDatum(rangeArrayOid);
|
||||
allParameterTypes = construct_array(&allParamTypes,
|
||||
1, OIDOID,
|
||||
sizeof(Oid), true, 'i');
|
||||
paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
|
||||
parameterModes = construct_array(¶mModes, 1, CHAROID,
|
||||
1, true, 'c');
|
||||
myself = ProcedureCreate(name, /* name: same as multirange type */
|
||||
namespace,
|
||||
false, /* replace */
|
||||
false, /* returns set */
|
||||
multirangeOid, /* return type */
|
||||
BOOTSTRAP_SUPERUSERID, /* proowner */
|
||||
INTERNALlanguageId, /* language */
|
||||
F_FMGR_INTERNAL_VALIDATOR,
|
||||
"multirange_constructor2", /* prosrc */
|
||||
NULL, /* probin */
|
||||
PROKIND_FUNCTION,
|
||||
false, /* security_definer */
|
||||
false, /* leakproof */
|
||||
false, /* isStrict */
|
||||
PROVOLATILE_IMMUTABLE, /* volatility */
|
||||
PROPARALLEL_SAFE, /* parallel safety */
|
||||
argtypes, /* parameterTypes */
|
||||
PointerGetDatum(allParameterTypes), /* allParameterTypes */
|
||||
PointerGetDatum(parameterModes), /* parameterModes */
|
||||
PointerGetDatum(NULL), /* parameterNames */
|
||||
NIL, /* parameterDefaults */
|
||||
PointerGetDatum(NULL), /* trftypes */
|
||||
PointerGetDatum(NULL), /* proconfig */
|
||||
InvalidOid, /* prosupport */
|
||||
1.0, /* procost */
|
||||
0.0); /* prorows */
|
||||
/* ditto */
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
|
||||
pfree(argtypes);
|
||||
pfree(allParameterTypes);
|
||||
pfree(parameterModes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find suitable I/O and other support functions for a type.
|
||||
@ -2152,6 +2437,72 @@ AssignTypeArrayOid(void)
|
||||
return type_array_oid;
|
||||
}
|
||||
|
||||
/*
|
||||
* AssignTypeMultirangeOid
|
||||
*
|
||||
* Pre-assign the range type's multirange OID for use in pg_type.oid
|
||||
*/
|
||||
Oid
|
||||
AssignTypeMultirangeOid(void)
|
||||
{
|
||||
Oid type_multirange_oid;
|
||||
|
||||
/* Use binary-upgrade override for pg_type.oid? */
|
||||
if (IsBinaryUpgrade)
|
||||
{
|
||||
if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
|
||||
|
||||
type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
|
||||
binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
|
||||
}
|
||||
else
|
||||
{
|
||||
Relation pg_type = table_open(TypeRelationId, AccessShareLock);
|
||||
|
||||
type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
|
||||
Anum_pg_type_oid);
|
||||
table_close(pg_type, AccessShareLock);
|
||||
}
|
||||
|
||||
return type_multirange_oid;
|
||||
}
|
||||
|
||||
/*
|
||||
* AssignTypeMultirangeArrayOid
|
||||
*
|
||||
* Pre-assign the range type's multirange array OID for use in pg_type.typarray
|
||||
*/
|
||||
Oid
|
||||
AssignTypeMultirangeArrayOid(void)
|
||||
{
|
||||
Oid type_multirange_array_oid;
|
||||
|
||||
/* Use binary-upgrade override for pg_type.oid? */
|
||||
if (IsBinaryUpgrade)
|
||||
{
|
||||
if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
|
||||
|
||||
type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
|
||||
binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
|
||||
}
|
||||
else
|
||||
{
|
||||
Relation pg_type = table_open(TypeRelationId, AccessShareLock);
|
||||
|
||||
type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
|
||||
Anum_pg_type_oid);
|
||||
table_close(pg_type, AccessShareLock);
|
||||
}
|
||||
|
||||
return type_multirange_array_oid;
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------
|
||||
* DefineCompositeType
|
||||
|
Reference in New Issue
Block a user