diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/pg_range.c | 10 | ||||
-rw-r--r-- | src/backend/catalog/pg_type.c | 118 | ||||
-rw-r--r-- | src/backend/commands/typecmds.c | 357 | ||||
-rw-r--r-- | src/backend/executor/functions.c | 3 | ||||
-rw-r--r-- | src/backend/parser/parse_coerce.c | 350 | ||||
-rw-r--r-- | src/backend/utils/adt/Makefile | 2 | ||||
-rw-r--r-- | src/backend/utils/adt/multirangetypes.c | 2679 | ||||
-rw-r--r-- | src/backend/utils/adt/multirangetypes_selfuncs.c | 1320 | ||||
-rw-r--r-- | src/backend/utils/adt/pg_upgrade_support.c | 22 | ||||
-rw-r--r-- | src/backend/utils/adt/pseudotypes.c | 38 | ||||
-rw-r--r-- | src/backend/utils/adt/rangetypes.c | 177 | ||||
-rw-r--r-- | src/backend/utils/adt/rangetypes_typanalyze.c | 78 | ||||
-rw-r--r-- | src/backend/utils/cache/lsyscache.c | 62 | ||||
-rw-r--r-- | src/backend/utils/cache/syscache.c | 12 | ||||
-rw-r--r-- | src/backend/utils/cache/typcache.c | 98 | ||||
-rw-r--r-- | src/backend/utils/fmgr/funcapi.c | 180 |
16 files changed, 5404 insertions, 102 deletions
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c index a606d8c3adb..91b0fb0611a 100644 --- a/src/backend/catalog/pg_range.c +++ b/src/backend/catalog/pg_range.c @@ -35,7 +35,7 @@ void RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, Oid rangeSubOpclass, RegProcedure rangeCanonical, - RegProcedure rangeSubDiff) + RegProcedure rangeSubDiff, Oid multirangeTypeOid) { Relation pg_range; Datum values[Natts_pg_range]; @@ -43,6 +43,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, HeapTuple tup; ObjectAddress myself; ObjectAddress referenced; + ObjectAddress referencing; ObjectAddresses *addrs; pg_range = table_open(RangeRelationId, RowExclusiveLock); @@ -55,6 +56,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, values[Anum_pg_range_rngsubopc - 1] = ObjectIdGetDatum(rangeSubOpclass); values[Anum_pg_range_rngcanonical - 1] = ObjectIdGetDatum(rangeCanonical); values[Anum_pg_range_rngsubdiff - 1] = ObjectIdGetDatum(rangeSubDiff); + values[Anum_pg_range_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid); tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls); @@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); free_object_addresses(addrs); + /* record multirange type's dependency on the range type */ + referencing.classId = TypeRelationId; + referencing.objectId = multirangeTypeOid; + referencing.objectSubId = 0; + recordDependencyOn(&referencing, &myself, DEPENDENCY_INTERNAL); + table_close(pg_range, RowExclusiveLock); } diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 4252875ef50..2aa686a640f 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -28,6 +28,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/typecmds.h" +#include "mb/pg_wchar.h" #include "miscadmin.h" #include "parser/scansup.h" #include "utils/acl.h" @@ -37,6 +38,9 @@ #include "utils/rel.h" #include "utils/syscache.h" +static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace, + bool tryOriginal); + /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_pg_type_oid = InvalidOid; @@ -863,31 +867,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace) char * makeArrayTypeName(const char *typeName, Oid typeNamespace) { - char *arr = (char *) palloc(NAMEDATALEN); - int namelen = strlen(typeName); - int i; - - /* - * The idea is to prepend underscores as needed until we make a name that - * doesn't collide with anything... - */ - for (i = 1; i < NAMEDATALEN - 1; i++) - { - arr[i - 1] = '_'; - if (i + namelen < NAMEDATALEN) - strcpy(arr + i, typeName); - else - { - memcpy(arr + i, typeName, NAMEDATALEN - i); - truncate_identifier(arr, NAMEDATALEN, false); - } - if (!SearchSysCacheExists2(TYPENAMENSP, - CStringGetDatum(arr), - ObjectIdGetDatum(typeNamespace))) - break; - } + char *arr; - if (i >= NAMEDATALEN - 1) + arr = makeUniqueTypeName(typeName, typeNamespace, false); + if (arr == NULL) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("could not form array type name for type \"%s\"", @@ -958,3 +941,90 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace) return true; } + + +/* + * makeMultirangeTypeName + * - given a range type name, make a multirange type name for it + * + * caller is responsible for pfreeing the result + */ +char * +makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace) +{ + char *buf; + char *rangestr; + + /* + * If the range type name contains "range" then change that to + * "multirange". Otherwise add "_multirange" to the end. + */ + rangestr = strstr(rangeTypeName, "range"); + if (rangestr) + { + char *prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName); + + buf = psprintf("%s%s%s", prefix, "multi", rangestr); + } + else + buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12)); + + /* clip it at NAMEDATALEN-1 bytes */ + buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0'; + + if (SearchSysCacheExists2(TYPENAMENSP, + CStringGetDatum(buf), + ObjectIdGetDatum(typeNamespace))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("type \"%s\" already exists", buf), + errdetail("Failed while creating a multirange type for type \"%s\".", rangeTypeName), + errhint("You can manually specify a multirange type name using the \"multirange_type_name\" attribute"))); + + return pstrdup(buf); +} + +/* + * makeUniqueTypeName + * Generate a unique name for a prospective new type + * + * Given a typeName, return a new palloc'ed name by preprending underscores + * until a non-conflicting name results. + * + * If tryOriginal, first try with zero underscores. + */ +static char * +makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal) +{ + int i; + int namelen; + char dest[NAMEDATALEN]; + + Assert(strlen(typeName) <= NAMEDATALEN - 1); + + if (tryOriginal && + !SearchSysCacheExists2(TYPENAMENSP, + CStringGetDatum(typeName), + ObjectIdGetDatum(typeNamespace))) + return pstrdup(typeName); + + /* + * The idea is to prepend underscores as needed until we make a name that + * doesn't collide with anything ... + */ + namelen = strlen(typeName); + for (i = 1; i < NAMEDATALEN - 1; i++) + { + dest[i - 1] = '_'; + strlcpy(dest + i, typeName, NAMEDATALEN - i); + if (namelen + i >= NAMEDATALEN) + truncate_identifier(dest, NAMEDATALEN, false); + + if (!SearchSysCacheExists2(TYPENAMENSP, + CStringGetDatum(dest), + ObjectIdGetDatum(typeNamespace))) + return pstrdup(dest); + } + + return NULL; +} diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 7c0b2c3bf02..0fcd8c8f16e 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -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 diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 459a33375b1..ca8d637e73a 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -1711,7 +1711,8 @@ check_sql_fn_retval(List *queryTreeLists, if (fn_typtype == TYPTYPE_BASE || fn_typtype == TYPTYPE_DOMAIN || fn_typtype == TYPTYPE_ENUM || - fn_typtype == TYPTYPE_RANGE) + fn_typtype == TYPTYPE_RANGE || + fn_typtype == TYPTYPE_MULTIRANGE) { /* * For scalar-type returns, the target list must have exactly one diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index da6c3ae4b5f..e33618f9744 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -190,19 +190,21 @@ coerce_type(ParseState *pstate, Node *node, if (targetTypeId == ANYARRAYOID || targetTypeId == ANYENUMOID || targetTypeId == ANYRANGEOID || + targetTypeId == ANYMULTIRANGEOID || targetTypeId == ANYCOMPATIBLEARRAYOID || - targetTypeId == ANYCOMPATIBLERANGEOID) + targetTypeId == ANYCOMPATIBLERANGEOID || + targetTypeId == ANYCOMPATIBLEMULTIRANGEOID) { /* * Assume can_coerce_type verified that implicit coercion is okay. * * These cases are unlike the ones above because the exposed type of - * the argument must be an actual array, enum, or range type. In - * particular the argument must *not* be an UNKNOWN constant. If it - * is, we just fall through; below, we'll call the pseudotype's input - * function, which will produce an error. Also, if what we have is a - * domain over array, enum, or range, we have to relabel it to its - * base type. + * the argument must be an actual array, enum, range, or multirange + * type. In particular the argument must *not* be an UNKNOWN + * constant. If it is, we just fall through; below, we'll call the + * pseudotype's input function, which will produce an error. Also, if + * what we have is a domain over array, enum, range, or multirange, we + * have to relabel it to its base type. * * Note: currently, we can't actually see a domain-over-enum here, * since the other functions in this file will not match such a @@ -1570,8 +1572,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type) * 1) All arguments declared ANYELEMENT must have the same datatype. * 2) All arguments declared ANYARRAY must have the same datatype, * which must be a varlena array type. - * 3) All arguments declared ANYRANGE must have the same datatype, - * which must be a range type. + * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or + * multirange type, all derived from the same base datatype. * 4) If there are arguments of more than one of these polymorphic types, * the array element type and/or range subtype must be the same as each * other and the same as the ANYELEMENT type. @@ -1586,8 +1588,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type) * to a common supertype (chosen as per select_common_type's rules). * ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the * common supertype to not be an array. If there are ANYCOMPATIBLEARRAY - * or ANYCOMPATIBLERANGE arguments, their element types or subtypes are - * included while making the choice of common supertype. + * or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element + * types or subtypes are included while making the choice of common supertype. * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array * type over the common supertype (which might not be the same array type * as any of the original arrays). @@ -1595,6 +1597,10 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type) * (after domain flattening), since we have no preference rule that would * let us choose one over another. Furthermore, that range's subtype * must exactly match the common supertype chosen by rule 7. + * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange + * type (after domain flattening), since we have no preference rule that would + * let us choose one over another. Furthermore, that multirange's range's + * subtype must exactly match the common supertype chosen by rule 7. * * Domains over arrays match ANYARRAY, and are immediately flattened to their * base type. (Thus, for example, we will consider it a match if one ANYARRAY @@ -1603,7 +1609,9 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type) * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY. * * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE, - * and are immediately flattened to their base type. + * and are immediately flattened to their base type, and domains over + * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately + * flattened to their base type. * * Note that domains aren't currently considered to match ANYENUM, * even if their base type would match. @@ -1621,8 +1629,12 @@ check_generic_type_consistency(const Oid *actual_arg_types, Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; Oid range_typeid = InvalidOid; + Oid multirange_typeid = InvalidOid; Oid anycompatible_range_typeid = InvalidOid; Oid anycompatible_range_typelem = InvalidOid; + Oid anycompatible_multirange_typeid = InvalidOid; + Oid anycompatible_multirange_typelem = InvalidOid; + Oid range_typelem = InvalidOid; bool have_anynonarray = false; bool have_anyenum = false; bool have_anycompatible_nonarray = false; @@ -1671,6 +1683,15 @@ check_generic_type_consistency(const Oid *actual_arg_types, return false; range_typeid = actual_type; } + else if (decl_type == ANYMULTIRANGEOID) + { + if (actual_type == UNKNOWNOID) + continue; + actual_type = getBaseType(actual_type); /* flatten domains */ + if (OidIsValid(multirange_typeid) && actual_type != multirange_typeid) + return false; + multirange_typeid = actual_type; + } else if (decl_type == ANYCOMPATIBLEOID || decl_type == ANYCOMPATIBLENONARRAYOID) { @@ -1715,6 +1736,45 @@ check_generic_type_consistency(const Oid *actual_arg_types, anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem; } } + else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID) + { + if (actual_type == UNKNOWNOID) + continue; + actual_type = getBaseType(actual_type); /* flatten domains */ + if (OidIsValid(anycompatible_multirange_typeid)) + { + /* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */ + if (anycompatible_multirange_typeid != actual_type) + return false; + } + else + { + anycompatible_multirange_typeid = actual_type; + anycompatible_multirange_typelem = get_multirange_range(actual_type); + if (!OidIsValid(anycompatible_multirange_typelem)) + return false; /* not a multirange type */ + + if (OidIsValid(anycompatible_range_typeid)) + { + /* + * ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE + * arguments must match + */ + if (anycompatible_range_typeid != anycompatible_multirange_typelem) + return false; + } + else + { + anycompatible_range_typeid = anycompatible_multirange_typelem; + anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid); + if (!OidIsValid(anycompatible_range_typelem)) + return false; /* not a range type */ + } + /* collect the subtype for common-supertype choice */ + anycompatible_actual_types[n_anycompatible_args++] = + anycompatible_range_typelem; + } + } } /* Get the element type based on the array type, if we have one */ @@ -1761,8 +1821,6 @@ check_generic_type_consistency(const Oid *actual_arg_types, /* Get the element type based on the range type, if we have one */ if (OidIsValid(range_typeid)) { - Oid range_typelem; - range_typelem = get_range_subtype(range_typeid); if (!OidIsValid(range_typelem)) return false; /* should be a range, but isn't */ @@ -1781,6 +1839,45 @@ check_generic_type_consistency(const Oid *actual_arg_types, } } + /* Get the element type based on the multirange type, if we have one */ + if (OidIsValid(multirange_typeid)) + { + Oid multirange_typelem; + + multirange_typelem = get_multirange_range(multirange_typeid); + if (!OidIsValid(multirange_typelem)) + return false; /* should be a multirange, but isn't */ + + if (!OidIsValid(range_typeid)) + { + /* + * If we don't have a range type yet, use the one we just got + */ + range_typeid = multirange_typelem; + range_typelem = get_range_subtype(multirange_typelem); + if (!OidIsValid(range_typelem)) + return false; /* should be a range, but isn't */ + } + else if (multirange_typelem != range_typeid) + { + /* otherwise, they better match */ + return false; + } + + if (!OidIsValid(elem_typeid)) + { + /* + * If we don't have an element type yet, use the one we just got + */ + elem_typeid = range_typelem; + } + else if (range_typelem != elem_typeid) + { + /* otherwise, they better match */ + return false; + } + } + if (have_anynonarray) { /* require the element type to not be an array or domain over array */ @@ -1819,8 +1916,10 @@ check_generic_type_consistency(const Oid *actual_arg_types, } /* - * the anycompatible type must exactly match the range element type, - * if we were able to identify one + * The anycompatible type must exactly match the range element type, + * if we were able to identify one. This checks compatibility for + * anycompatiblemultirange too since that also sets + * anycompatible_range_typelem above. */ if (OidIsValid(anycompatible_range_typelem) && anycompatible_range_typelem != anycompatible_typeid) @@ -1859,21 +1958,27 @@ check_generic_type_consistency(const Oid *actual_arg_types, * argument's actual type as the function's return type. * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the * argument's actual type as the function's return type. - * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE, - * use the argument's actual type as the function's return type. - * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is + * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any + * argument is ANYRANGE or ANYMULTIRANGE, use that argument's + * actual type, range type or multirange type as the function's return + * type. + * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is + * ANYMULTIRANGE, use the argument's actual type as the function's return + * type. Or if any argument is ANYRANGE, use its multirange type as the + * function's return type. + * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is * at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the * return type from those inputs, or throw error if we can't. - * 5) Otherwise, if return type is ANYRANGE, throw error. (We have no way to - * select a specific range type if the arguments don't include ANYRANGE.) - * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used + * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error. + * (We have no way to select a specific range type if the arguments don't + * include ANYRANGE.) * (alone or in combination with plain ANYELEMENT), we add the extra * condition that the ANYELEMENT type must be an enum. - * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used, + * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used, * we add the extra condition that the ANYELEMENT type must not be an array. * (This is a no-op if used in combination with ANYARRAY or ANYENUM, but * is an extra restriction if not.) - * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and + * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and * ANYCOMPATIBLERANGE are handled by resolving the common supertype * of those arguments (or their element types/subtypes, for array and range * inputs), and then coercing all those arguments to the common supertype, @@ -1927,10 +2032,15 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; Oid range_typeid = InvalidOid; + Oid multirange_typeid = InvalidOid; Oid anycompatible_typeid = InvalidOid; Oid anycompatible_array_typeid = InvalidOid; Oid anycompatible_range_typeid = InvalidOid; Oid anycompatible_range_typelem = InvalidOid; + Oid anycompatible_multirange_typeid = InvalidOid; + Oid anycompatible_multirange_typelem = InvalidOid; + Oid range_typelem; + Oid multirange_typelem; bool have_anynonarray = (rettype == ANYNONARRAYOID); bool have_anyenum = (rettype == ANYENUMOID); bool have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID); @@ -2015,6 +2125,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, format_type_be(actual_type)))); range_typeid = actual_type; } + else if (decl_type == ANYMULTIRANGEOID) + { + n_poly_args++; + if (actual_type == UNKNOWNOID) + { + have_poly_unknowns = true; + continue; + } + if (allow_poly && decl_type == actual_type) + continue; /* no new information here */ + actual_type = getBaseType(actual_type); /* flatten domains */ + if (OidIsValid(multirange_typeid) && actual_type != multirange_typeid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("arguments declared \"anymultirange\" are not all alike"), + errdetail("%s versus %s", + format_type_be(multirange_typeid), + format_type_be(actual_type)))); + multirange_typeid = actual_type; + } else if (decl_type == ANYCOMPATIBLEOID || decl_type == ANYCOMPATIBLENONARRAYOID) { @@ -2083,6 +2213,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem; } } + else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID) + { + have_poly_anycompatible = true; + if (actual_type == UNKNOWNOID) + continue; + if (allow_poly && decl_type == actual_type) + continue; /* no new information here */ + actual_type = getBaseType(actual_type); /* flatten domains */ + if (OidIsValid(anycompatible_multirange_typeid)) + { + /* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */ + if (anycompatible_multirange_typeid != actual_type) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"), + errdetail("%s versus %s", + format_type_be(anycompatible_multirange_typeid), + format_type_be(actual_type)))); + } + else + { + anycompatible_multirange_typeid = actual_type; + anycompatible_multirange_typelem = get_multirange_range(actual_type); + anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem); + if (!OidIsValid(anycompatible_multirange_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a multirange type but type %s", + "anycompatiblemultirange", + format_type_be(actual_type)))); + /* collect the subtype for common-supertype choice */ + anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem; + } + } } /* @@ -2151,8 +2315,6 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, /* Get the element type based on the range type, if we have one */ if (OidIsValid(range_typeid)) { - Oid range_typelem; - range_typelem = get_range_subtype(range_typeid); if (!OidIsValid(range_typelem)) ereport(ERROR, @@ -2181,6 +2343,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, format_type_be(elem_typeid)))); } } + else + range_typelem = InvalidOid; + + /* Get the element type based on the multirange type, if we have one */ + if (OidIsValid(multirange_typeid)) + { + multirange_typelem = get_multirange_range(multirange_typeid); + if (!OidIsValid(multirange_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a multirange type but type %s", + "anymultirange", + format_type_be(multirange_typeid)))); + + if (!OidIsValid(range_typeid)) + { + /* + * If we don't have a range type yet, use the one we just got + */ + range_typeid = multirange_typelem; + range_typelem = get_range_subtype(range_typeid); + } + else if (multirange_typelem != range_typeid) + { + /* otherwise, they better match */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not consistent with argument declared %s", + "anymultirange", "anyrange"), + errdetail("%s versus %s", + format_type_be(multirange_typeid), + format_type_be(range_typeid)))); + } + + if (!OidIsValid(elem_typeid)) + { + /* + * if we don't have an element type yet, use the one we just got + */ + elem_typeid = range_typelem; + } + else if (range_typelem != elem_typeid) + { + /* otherwise, they better match */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not consistent with argument declared %s", + "anymultirange", "anyelement"), + errdetail("%s versus %s", + format_type_be(multirange_typeid), + format_type_be(elem_typeid)))); + } + } + else + multirange_typelem = InvalidOid; if (!OidIsValid(elem_typeid)) { @@ -2189,6 +2406,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, elem_typeid = ANYELEMENTOID; array_typeid = ANYARRAYOID; range_typeid = ANYRANGEOID; + multirange_typeid = ANYMULTIRANGEOID; } else { @@ -2288,6 +2506,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, anycompatible_typeid = ANYCOMPATIBLEOID; anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID; anycompatible_range_typeid = ANYCOMPATIBLERANGEOID; + anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID; } else { @@ -2319,6 +2538,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, declared_arg_types[j] = anycompatible_array_typeid; else if (decl_type == ANYCOMPATIBLERANGEOID) declared_arg_types[j] = anycompatible_range_typeid; + else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID) + declared_arg_types[j] = anycompatible_multirange_typeid; } } @@ -2369,6 +2590,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, } declared_arg_types[j] = range_typeid; } + else if (decl_type == ANYMULTIRANGEOID) + { + if (!OidIsValid(multirange_typeid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find multirange type for data type %s", + format_type_be(elem_typeid)))); + } + declared_arg_types[j] = multirange_typeid; + } } } @@ -2405,6 +2637,22 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, return range_typeid; } + /* if we return ANYMULTIRANGE use the appropriate argument type */ + if (rettype == ANYMULTIRANGEOID) + { + if (!OidIsValid(multirange_typeid)) + { + if (OidIsValid(range_typeid)) + multirange_typeid = get_range_multirange(range_typeid); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find multirange type for data type %s", + format_type_be(elem_typeid)))); + } + return multirange_typeid; + } + /* if we return ANYCOMPATIBLE use the appropriate type */ if (rettype == ANYCOMPATIBLEOID || rettype == ANYCOMPATIBLENONARRAYOID) @@ -2439,6 +2687,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types, return anycompatible_range_typeid; } + /* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */ + if (rettype == ANYCOMPATIBLEMULTIRANGEOID) + { + /* this error is unreachable if the function signature is valid: */ + if (!OidIsValid(anycompatible_multirange_typeid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg_internal("could not identify anycompatiblemultirange type"))); + return anycompatible_multirange_typeid; + } + /* we don't return a generic type; send back the original return type */ return rettype; } @@ -2456,20 +2715,38 @@ check_valid_polymorphic_signature(Oid ret_type, const Oid *declared_arg_types, int nargs) { - if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID) + if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID) { /* - * ANYRANGE requires an ANYRANGE input, else we can't tell which of - * several range types with the same element type to use. Likewise - * for ANYCOMPATIBLERANGE. + * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE + * input, else we can't tell which of several range types with the + * same element type to use. */ for (int i = 0; i < nargs; i++) { - if (declared_arg_types[i] == ret_type) + if (declared_arg_types[i] == ANYRANGEOID || + declared_arg_types[i] == ANYMULTIRANGEOID) return NULL; /* OK */ } - return psprintf(_("A result of type %s requires at least one input of type %s."), - format_type_be(ret_type), format_type_be(ret_type)); + return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."), + format_type_be(ret_type)); + } + else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID) + { + /* + * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an + * ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE input, else we can't + * tell which of several range types with the same element type to + * use. + */ + for (int i = 0; i < nargs; i++) + { + if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID || + declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID) + return NULL; /* OK */ + } + return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."), + format_type_be(ret_type)); } else if (IsPolymorphicTypeFamily1(ret_type)) { @@ -2480,7 +2757,7 @@ check_valid_polymorphic_signature(Oid ret_type, return NULL; /* OK */ } /* Keep this list in sync with IsPolymorphicTypeFamily1! */ - return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."), + return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."), format_type_be(ret_type)); } else if (IsPolymorphicTypeFamily2(ret_type)) @@ -2632,6 +2909,11 @@ IsBinaryCoercible(Oid srctype, Oid targettype) if (type_is_range(srctype)) return true; + /* Also accept any multirange type as coercible to ANMULTIYRANGE */ + if (targettype == ANYMULTIRANGEOID || targettype == ANYCOMPATIBLEMULTIRANGEOID) + if (type_is_multirange(srctype)) + return true; + /* Also accept any composite type as coercible to RECORD */ if (targettype == RECORDOID) if (ISCOMPLEX(srctype)) diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index ce09ad73754..82732146d3d 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -60,6 +60,8 @@ OBJS = \ mac8.o \ mcxtfuncs.o \ misc.o \ + multirangetypes.o \ + multirangetypes_selfuncs.o \ name.o \ network.o \ network_gist.o \ diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c new file mode 100644 index 00000000000..a4dc439a218 --- /dev/null +++ b/src/backend/utils/adt/multirangetypes.c @@ -0,0 +1,2679 @@ +/*------------------------------------------------------------------------- + * + * multirangetypes.c + * I/O functions, operators, and support functions for multirange types. + * + * The stored (serialized) format of a multirange value is: + * + * 12 bytes: MultirangeType struct including varlena header, multirange + * type's OID and the number of ranges in the multirange. + * 4 * (rangesCount - 1) bytes: 32-bit items pointing to the each range + * in the multirange starting from + * the second one. + * 1 * rangesCount bytes : 8-bit flags for each range in the multirange + * The rest of the multirange are range bound values pointed by multirange + * items. + * + * Majority of items contain lengths of corresponding range bound values. + * Thanks to that items are typically low numbers. This makes multiranges + * compression-friendly. Every MULTIRANGE_ITEM_OFFSET_STRIDE item contains + * an offset of the corresponding range bound values. That allows fast lookups + * for a particular range index. Offsets are counted starting from the end of + * flags aligned to the bound type. + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/multirangetypes.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/tupmacs.h" +#include "common/hashfn.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/multirangetypes.h" +#include "utils/array.h" +#include "utils/memutils.h" + +/* fn_extra cache entry for one of the range I/O functions */ +typedef struct MultirangeIOData +{ + TypeCacheEntry *typcache; /* multirange type's typcache entry */ + FmgrInfo typioproc; /* range type's I/O proc */ + Oid typioparam; /* range type's I/O parameter */ +} MultirangeIOData; + +typedef enum +{ + MULTIRANGE_BEFORE_RANGE, + MULTIRANGE_IN_RANGE, + MULTIRANGE_IN_RANGE_ESCAPED, + MULTIRANGE_IN_RANGE_QUOTED, + MULTIRANGE_IN_RANGE_QUOTED_ESCAPED, + MULTIRANGE_AFTER_RANGE, + MULTIRANGE_FINISHED, +} MultirangeParseState; + +/* + * Macros for accessing past MultirangeType parts of multirange: items, flags + * and boundaries. + */ +#define MultirangeGetItemsPtr(mr) ((uint32 *) ((Pointer) (mr) + \ + sizeof(MultirangeType))) +#define MultirangeGetFlagsPtr(mr) ((uint8 *) ((Pointer) (mr) + \ + sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32))) +#define MultirangeGetBoundariesPtr(mr, align) ((Pointer) (mr) + \ + att_align_nominal(sizeof(MultirangeType) + \ + ((mr)->rangeCount - 1) * sizeof(uint32) + \ + (mr)->rangeCount * sizeof(uint8), (align))) + +#define MULTIRANGE_ITEM_OFF_BIT 0x80000000 +#define MULTIRANGE_ITEM_GET_OFFLEN(item) ((item) & 0x7FFFFFFF) +#define MULTIRANGE_ITEM_HAS_OFF(item) ((item) & MULTIRANGE_ITEM_OFF_BIT) +#define MULTIRANGE_ITEM_OFFSET_STRIDE 4 + +typedef int (*multirange_bsearch_comparison) (TypeCacheEntry *typcache, + RangeBound *lower, + RangeBound *upper, + void *key, + bool *match); + +static MultirangeIOData *get_multirange_io_data(FunctionCallInfo fcinfo, + Oid mltrngtypid, + IOFuncSelector func); +static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, + int32 input_range_count, + RangeType **ranges); + +/* + *---------------------------------------------------------- + * I/O FUNCTIONS + *---------------------------------------------------------- + */ + +/* + * Converts string to multirange. + * + * We expect curly brackets to bound the list, with zero or more ranges + * separated by commas. We accept whitespace anywhere: before/after our + * brackets and around the commas. Ranges can be the empty literal or some + * stuff inside parens/brackets. Mostly we delegate parsing the individual + * range contents to range_in, but we have to detect quoting and + * backslash-escaping which can happen for range bounds. Backslashes can + * escape something inside or outside a quoted string, and a quoted string + * can escape quote marks with either backslashes or double double-quotes. + */ +Datum +multirange_in(PG_FUNCTION_ARGS) +{ + char *input_str = PG_GETARG_CSTRING(0); + Oid mltrngtypoid = PG_GETARG_OID(1); + Oid typmod = PG_GETARG_INT32(2); + TypeCacheEntry *rangetyp; + int32 ranges_seen = 0; + int32 range_count = 0; + int32 range_capacity = 8; + RangeType *range; + RangeType **ranges = palloc(range_capacity * sizeof(RangeType *)); + MultirangeIOData *cache; + MultirangeType *ret; + MultirangeParseState parse_state; + const char *ptr = input_str; + const char *range_str = NULL; + int32 range_str_len; + char *range_str_copy; + + cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input); + rangetyp = cache->typcache->rngtype; + + /* consume whitespace */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + if (*ptr == '{') + ptr++; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Missing left bracket."))); + + /* consume ranges */ + parse_state = MULTIRANGE_BEFORE_RANGE; + for (; parse_state != MULTIRANGE_FINISHED; ptr++) + { + char ch = *ptr; + + if (ch == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Unexpected end of input."))); + + /* skip whitespace */ + if (isspace((unsigned char) ch)) + continue; + + switch (parse_state) + { + case MULTIRANGE_BEFORE_RANGE: + if (ch == '[' || ch == '(') + { + range_str = ptr; + parse_state = MULTIRANGE_IN_RANGE; + } + else if (ch == '}' && ranges_seen == 0) + parse_state = MULTIRANGE_FINISHED; + else if (pg_strncasecmp(ptr, RANGE_EMPTY_LITERAL, + strlen(RANGE_EMPTY_LITERAL)) == 0) + { + ranges_seen++; + /* nothing to do with an empty range */ + ptr += strlen(RANGE_EMPTY_LITERAL) - 1; + parse_state = MULTIRANGE_AFTER_RANGE; + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Expected range start."))); + break; + case MULTIRANGE_IN_RANGE: + if (ch == '"') + parse_state = MULTIRANGE_IN_RANGE_QUOTED; + else if (ch == '\\') + parse_state = MULTIRANGE_IN_RANGE_ESCAPED; + else if (ch == ']' || ch == ')') + { + range_str_len = ptr - range_str + 1; + range_str_copy = pnstrdup(range_str, range_str_len); + if (range_capacity == range_count) + { + range_capacity *= 2; + ranges = (RangeType **) + repalloc(ranges, range_capacity * sizeof(RangeType *)); + } + ranges_seen++; + range = DatumGetRangeTypeP(InputFunctionCall(&cache->typioproc, + range_str_copy, + cache->typioparam, + typmod)); + if (!RangeIsEmpty(range)) + ranges[range_count++] = range; + parse_state = MULTIRANGE_AFTER_RANGE; + } + else + /* include it in range_str */ ; + break; + case MULTIRANGE_IN_RANGE_ESCAPED: + /* include it in range_str */ + parse_state = MULTIRANGE_IN_RANGE; + break; + case MULTIRANGE_IN_RANGE_QUOTED: + if (ch == '"') + if (*(ptr + 1) == '"') + { + /* two quote marks means an escaped quote mark */ + ptr++; + } + else + parse_state = MULTIRANGE_IN_RANGE; + else if (ch == '\\') + parse_state = MULTIRANGE_IN_RANGE_QUOTED_ESCAPED; + else + /* include it in range_str */ ; + break; + case MULTIRANGE_AFTER_RANGE: + if (ch == ',') + parse_state = MULTIRANGE_BEFORE_RANGE; + else if (ch == '}') + parse_state = MULTIRANGE_FINISHED; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Expected comma or end of multirange."))); + break; + case MULTIRANGE_IN_RANGE_QUOTED_ESCAPED: + /* include it in range_str */ + parse_state = MULTIRANGE_IN_RANGE_QUOTED; + break; + default: + elog(ERROR, "unknown parse state: %d", parse_state); + } + } + + /* consume whitespace */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + if (*ptr != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed multirange literal: \"%s\"", + input_str), + errdetail("Junk after right bracket."))); + + ret = make_multirange(mltrngtypoid, rangetyp, range_count, ranges); + PG_RETURN_MULTIRANGE_P(ret); +} + +Datum +multirange_out(PG_FUNCTION_ARGS) +{ + MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0); + Oid mltrngtypoid = MultirangeTypeGetOid(multirange); + MultirangeIOData *cache; + StringInfoData buf; + RangeType *range; + char *rangeStr; + int32 range_count; + int32 i; + RangeType **ranges; + + cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output); + + initStringInfo(&buf); + + appendStringInfoChar(&buf, '{'); + + multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges); + for (i = 0; i < range_count; i++) + { + if (i > 0) + appendStringInfoChar(&buf, ','); + range = ranges[i]; + rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range)); + appendStringInfoString(&buf, rangeStr); + } + + appendStringInfoChar(&buf, '}'); + + PG_RETURN_CSTRING(buf.data); +} + +/* + * Binary representation: First a int32-sized count of ranges, followed by + * ranges in their native binary representation. + */ +Datum +multirange_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid mltrngtypoid = PG_GETARG_OID(1); + int32 typmod = PG_GETARG_INT32(2); + MultirangeIOData *cache; + uint32 range_count; + RangeType **ranges; + MultirangeType *ret; + StringInfoData tmpbuf; + + cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive); + + range_count = pq_getmsgint(buf, 4); + ranges = palloc(range_count * sizeof(RangeType *)); + + initStringInfo(&tmpbuf); + for (int i = 0; i < range_count; i++) + { + uint32 range_len = pq_getmsgint(buf, 4); + const char *range_data = pq_getmsgbytes(buf, range_len); + + resetStringInfo(&tmpbuf); + appendBinaryStringInfo(&tmpbuf, range_data, range_len); + + ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc, + &tmpbuf, + cache->typioparam, + typmod)); + } + pfree(tmpbuf.data); + + pq_getmsgend(buf); + + ret = make_multirange(mltrngtypoid, cache->typcache->rngtype, + range_count, ranges); + PG_RETURN_MULTIRANGE_P(ret); +} + +Datum +multirange_send(PG_FUNCTION_ARGS) +{ + MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0); + Oid mltrngtypoid = MultirangeTypeGetOid(multirange); + StringInfo buf = makeStringInfo(); + RangeType **ranges; + int32 range_count; + MultirangeIOData *cache; + + cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send); + + /* construct output */ + pq_begintypsend(buf); + + pq_sendint32(buf, multirange->rangeCount); + + multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges); + for (int i = 0; i < range_count; i++) + { + Datum range; + + range = RangeTypePGetDatum(ranges[i]); + range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range)); + + pq_sendint32(buf, VARSIZE(range) - VARHDRSZ); + pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ); + } + + PG_RETURN_BYTEA_P(pq_endtypsend(buf)); +} + +/* + * get_multirange_io_data: get cached information needed for multirange type I/O + * + * The multirange I/O functions need a bit more cached info than other multirange + * functions, so they store a MultirangeIOData struct in fn_extra, not just a + * pointer to a type cache entry. + */ +static MultirangeIOData * +get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid, IOFuncSelector func) +{ + MultirangeIOData *cache = (MultirangeIOData *) fcinfo->flinfo->fn_extra; + + if (cache == NULL || cache->typcache->type_id != mltrngtypid) + { + Oid typiofunc; + int16 typlen; + bool typbyval; + char typalign; + char typdelim; + + cache = (MultirangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(MultirangeIOData)); + cache->typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO); + if (cache->typcache->rngtype == NULL) + elog(ERROR, "type %u is not a multirange type", mltrngtypid); + + /* get_type_io_data does more than we need, but is convenient */ + get_type_io_data(cache->typcache->rngtype->type_id, + func, + &typlen, + &typbyval, + &typalign, + &typdelim, + &cache->typioparam, + &typiofunc); + + if (!OidIsValid(typiofunc)) + { + /* this could only happen for receive or send */ + if (func == IOFunc_receive) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary input function available for type %s", + format_type_be(cache->typcache->rngtype->type_id)))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("no binary output function available for type %s", + format_type_be(cache->typcache->rngtype->type_id)))); + } + fmgr_info_cxt(typiofunc, &cache->typioproc, + fcinfo->flinfo->fn_mcxt); + + fcinfo->flinfo->fn_extra = (void *) cache; + } + + return cache; +} + +/* + * Converts a list of arbitrary ranges into a list that is sorted and merged. + * Changes the contents of `ranges`. + * + * Returns the number of slots actually used, which may be less than + * input_range_count but never more. + * + * We assume that no input ranges are null, but empties are okay. + */ +static int32 +multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count, + RangeType **ranges) +{ + RangeType *lastRange = NULL; + RangeType *currentRange; + int32 i; + int32 output_range_count = 0; + + /* Sort the ranges so we can find the ones that overlap/meet. */ + qsort_arg(ranges, input_range_count, sizeof(RangeType *), range_compare, + rangetyp); + + /* Now merge where possible: */ + for (i = 0; i < input_range_count; i++) + { + currentRange = ranges[i]; + if (RangeIsEmpty(currentRange)) + continue; + + if (lastRange == NULL) + { + ranges[output_range_count++] = lastRange = currentRange; + continue; + } + + /* + * range_adjacent_internal gives true if *either* A meets B or B meets + * A, which is not quite want we want, but we rely on the sorting + * above to rule out B meets A ever happening. + */ + if (range_adjacent_internal(rangetyp, lastRange, currentRange)) + { + /* The two ranges touch (without overlap), so merge them: */ + ranges[output_range_count - 1] = lastRange = + range_union_internal(rangetyp, lastRange, currentRange, false); + } + else if (range_before_internal(rangetyp, lastRange, currentRange)) + { + /* There's a gap, so make a new entry: */ + lastRange = ranges[output_range_count] = currentRange; + output_range_count++; + } + else + { + /* They must overlap, so merge them: */ + ranges[output_range_count - 1] = lastRange = + range_union_internal(rangetyp, lastRange, currentRange, true); + } + } + + return output_range_count; +} + +/* + *---------------------------------------------------------- + * SUPPORT FUNCTIONS + * + * These functions aren't in pg_proc, but are useful for + * defining new generic multirange functions in C. + *---------------------------------------------------------- + */ + +/* + * multirange_get_typcache: get cached information about a multirange type + * + * This is for use by multirange-related functions that follow the convention + * of using the fn_extra field as a pointer to the type cache entry for + * the multirange type. Functions that need to cache more information than + * that must fend for themselves. + */ +TypeCacheEntry * +multirange_get_typcache(FunctionCallInfo fcinfo, Oid mltrngtypid) +{ + TypeCacheEntry *typcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + + if (typcache == NULL || + typcache->type_id != mltrngtypid) + { + typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO); + if (typcache->rngtype == NULL) + elog(ERROR, "type %u is not a multirange type", mltrngtypid); + fcinfo->flinfo->fn_extra = (void *) typcache; + } + + return typcache; +} + + +/* + * Estimate size occupied by serialized multirage. + */ +static Size +multirange_size_estimate(TypeCacheEntry *rangetyp, int32 range_count, + RangeType **ranges) +{ + char elemalign = rangetyp->rngelemtype->typalign; + Size size; + int32 i; + + /* + * Count space for MultirangeType struct, items and flags. + */ + size = att_align_nominal(sizeof(MultirangeType) + + Max(range_count - 1, 0) * sizeof(uint32) + + range_count * sizeof(uint8), elemalign); + + /* Count space for range bounds */ + for (i = 0; i < range_count; i++) + size += att_align_nominal(VARSIZE(ranges[i]) - + sizeof(RangeType) - + sizeof(char), elemalign); + + return size; +} + +/* + * Write multirange data into pre-allocated space. + */ +static void +write_multirange_data(MultirangeType *multirange, TypeCacheEntry *rangetyp, + int32 range_count, RangeType **ranges) +{ + uint32 *items; + uint32 prev_offset = 0; + uint8 *flags; + int32 i; + Pointer begin, + ptr; + char elemalign = rangetyp->rngelemtype->typalign; + + items = MultirangeGetItemsPtr(multirange); + flags = MultirangeGetFlagsPtr(multirange); + ptr = begin = MultirangeGetBoundariesPtr(multirange, elemalign); + for (i = 0; i < range_count; i++) + { + uint32 len; + + if (i > 0) + { + /* + * Every range, except the first one, has an item. Every + * MULTIRANGE_ITEM_OFFSET_STRIDE item contains an offset, others + * contain lengths. + */ + items[i - 1] = ptr - begin; + if ((i % MULTIRANGE_ITEM_OFFSET_STRIDE) != 0) + items[i - 1] -= prev_offset; + else + items[i - 1] |= MULTIRANGE_ITEM_OFF_BIT; + prev_offset = ptr - begin; + } + flags[i] = *((Pointer) ranges[i] + VARSIZE(ranges[i]) - sizeof(char)); + len = VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char); + memcpy(ptr, (Pointer) (ranges[i] + 1), len); + ptr += att_align_nominal(len, elemalign); + } +} + + +/* + * This serializes the multirange from a list of non-null ranges. It also + * sorts the ranges and merges any that touch. The ranges should already be + * detoasted, and there should be no NULLs. This should be used by most + * callers. + * + * Note that we may change the `ranges` parameter (the pointers, but not + * any already-existing RangeType contents). + */ +MultirangeType * +make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count, + RangeType **ranges) +{ + MultirangeType *multirange; + Size size; + + /* Sort and merge input ranges. */ + range_count = multirange_canonicalize(rangetyp, range_count, ranges); + + /* Note: zero-fill is required here, just as in heap tuples */ + size = multirange_size_estimate(rangetyp, range_count, ranges); + multirange = palloc0(size); + SET_VARSIZE(multirange, size); + + /* Now fill in the datum */ + multirange->multirangetypid = mltrngtypoid; + multirange->rangeCount = range_count; + + write_multirange_data(multirange, rangetyp, range_count, ranges); + + return multirange; +} + +/* + * Get offset of bounds values of the i'th range in the multirange. + */ +static uint32 +multirange_get_bounds_offset(const MultirangeType *multirange, int32 i) +{ + uint32 *items = MultirangeGetItemsPtr(multirange); + uint32 offset = 0; + + /* + * Summarize lengths till we meet an offset. + */ + while (i > 0) + { + offset += MULTIRANGE_ITEM_GET_OFFLEN(items[i - 1]); + if (MULTIRANGE_ITEM_HAS_OFF(items[i - 1])) + break; + i--; + } + return offset; +} + +/* + * Fetch the i'th range from the multirange. + */ +RangeType * +multirange_get_range(TypeCacheEntry *rangetyp, + const MultirangeType *multirange, int i) +{ + uint32 offset; + uint8 flags; + Pointer begin, + ptr; + int16 typlen = rangetyp->rngelemtype->typlen; + char typalign = rangetyp->rngelemtype->typalign; + uint32 len; + RangeType *range; + + Assert(i < multirange->rangeCount); + + offset = multirange_get_bounds_offset(multirange, i); + flags = MultirangeGetFlagsPtr(multirange)[i]; + ptr = begin = MultirangeGetBoundariesPtr(multirange, typalign) + offset; + + /* + * Calculate the size of bound values. In principle, we could get offset + * of the next range bound values and calculate accordingly. But range + * bound values are aligned, so we have to walk the values to get the + * exact size. + */ + if (RANGE_HAS_LBOUND(flags)) + ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr); + if (RANGE_HAS_UBOUND(flags)) + ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr); + len = (ptr - begin) + sizeof(RangeType) + sizeof(uint8); + + range = palloc0(len); + SET_VARSIZE(range, len); + range->rangetypid = rangetyp->type_id; + + memcpy(range + 1, begin, ptr - begin); + *((uint8 *) (range + 1) + (ptr - begin)) = flags; + + return range; +} + +/* + * Fetch bounds from the i'th range of the multirange. This is the shortcut for + * doing the same thing as multirange_get_range() + range_deserialize(), but + * performing fewer operations. + */ +void +multirange_get_bounds(TypeCacheEntry *rangetyp, + const MultirangeType *multirange, + uint32 i, RangeBound *lower, RangeBound *upper) +{ + uint32 offset; + uint8 flags; + Pointer ptr; + int16 typlen = rangetyp->rngelemtype->typlen; + char typalign = rangetyp->rngelemtype->typalign; + bool typbyval = rangetyp->rngelemtype->typbyval; + Datum lbound; + Datum ubound; + + Assert(i < multirange->rangeCount); + + offset = multirange_get_bounds_offset(multirange, i); + flags = MultirangeGetFlagsPtr(multirange)[i]; + ptr = MultirangeGetBoundariesPtr(multirange, typalign) + offset; + + /* multirange can't contain empty ranges */ + Assert((flags & RANGE_EMPTY) == 0); + + /* fetch lower bound, if any */ + if (RANGE_HAS_LBOUND(flags)) + { + /* att_align_pointer cannot be necessary here */ + lbound = fetch_att(ptr, typbyval, typlen); + ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr); + } + else + lbound = (Datum) 0; + + /* fetch upper bound, if any */ + if (RANGE_HAS_UBOUND(flags)) + { + ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + ubound = fetch_att(ptr, typbyval, typlen); + /* no need for att_addlength_pointer */ + } + else + ubound = (Datum) 0; + + /* emit results */ + lower->val = lbound; + lower->infinite = (flags & RANGE_LB_INF) != 0; + lower->inclusive = (flags & RANGE_LB_INC) != 0; + lower->lower = true; + + upper->val = ubound; + upper->infinite = (flags & RANGE_UB_INF) != 0; + upper->inclusive = (flags & RANGE_UB_INC) != 0; + upper->lower = false; +} + +/* + * multirange_deserialize: deconstruct a multirange value + * + * NB: the given multirange object must be fully detoasted; it cannot have a + * short varlena header. + */ +void +multirange_deserialize(TypeCacheEntry *rangetyp, + const MultirangeType *multirange, int32 *range_count, + RangeType ***ranges) +{ + *range_count = multirange->rangeCount; + + /* Convert each ShortRangeType into a RangeType */ + if (*range_count > 0) + { + int i; + + *ranges = palloc(*range_count * sizeof(RangeType *)); + for (i = 0; i < *range_count; i++) + (*ranges)[i] = multirange_get_range(rangetyp, multirange, i); + } + else + { + *ranges = NULL; + } +} + +MultirangeType * +make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp) +{ + return make_multirange(mltrngtypoid, rangetyp, 0, NULL); +} + +/* + * Similar to range_overlaps_internal(), but takes range bounds instead of + * ranges as arguments. + */ +static bool +range_bounds_overlaps(TypeCacheEntry *typcache, + RangeBound *lower1, RangeBound *upper1, + RangeBound *lower2, RangeBound *upper2) +{ + if (range_cmp_bounds(typcache, lower1, lower2) >= 0 && + range_cmp_bounds(typcache, lower1, upper2) <= 0) + return true; + + if (range_cmp_bounds(typcache, lower2, lower1) >= 0 && + range_cmp_bounds(typcache, lower2, upper1) <= 0) + return true; + + return false; +} + +/* + * Similar to range_contains_internal(), but takes range bounds instead of + * ranges as arguments. + */ +static bool +range_bounds_contains(TypeCacheEntry *typcache, + RangeBound *lower1, RangeBound *upper1, + RangeBound *lower2, RangeBound *upper2) +{ + if (range_cmp_bounds(typcache, lower1, lower2) <= 0 && + range_cmp_bounds(typcache, upper1, upper2) >= 0) + return true; + + return false; +} + +/* + * Check if the given key matches any range in multirange using binary search. + * If the required range isn't found, that counts as a mismatch. When the + * required range is found, the comparison function can still report this as + * either match or mismatch. For instance, if we search for containment, we can + * found a range, which is overlapping but not containing the key range, and + * that would count as a mismatch. + */ +static bool +multirange_bsearch_match(TypeCacheEntry *typcache, MultirangeType *mr, + void *key, multirange_bsearch_comparison cmp_func) +{ + uint32 l, + u, + idx; + int comparison; + bool match = false; + + l = 0; + u = mr->rangeCount; + while (l < u) + { + RangeBound lower, + upper; + + idx = (l + u) / 2; + multirange_get_bounds(typcache, mr, idx, &lower, &upper); + comparison = (*cmp_func) (typcache, &lower, &upper, key, &match); + + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + return match; + } + + return false; +} + +/* + *---------------------------------------------------------- + * GENERIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* + * Construct multirange value from zero or more ranges. Since this is a + * variadic function we get passed an array. The array must contain ranges + * that match our return value, and there must be no NULLs. + */ +Datum +multirange_constructor2(PG_FUNCTION_ARGS) +{ + Oid mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo); + Oid rngtypid; + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + ArrayType *rangeArray; + int range_count; + Datum *elements; + bool *nulls; + RangeType **ranges; + int dims; + int i; + + typcache = multirange_get_typcache(fcinfo, mltrngtypid); + rangetyp = typcache->rngtype; + + /* + * A no-arg invocation should call multirange_constructor0 instead, but + * returning an empty range is what that does. + */ + + if (PG_NARGS() == 0) + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL)); + + /* + * These checks should be guaranteed by our signature, but let's do them + * just in case. + */ + + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("multirange values cannot contain NULL members"))); + + rangeArray = PG_GETARG_ARRAYTYPE_P(0); + + dims = ARR_NDIM(rangeArray); + if (dims > 1) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("multiranges cannot be constructed from multi-dimensional arrays"))); + + rngtypid = ARR_ELEMTYPE(rangeArray); + if (rngtypid != rangetyp->type_id) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type %u does not match constructor type", rngtypid))); + + /* + * Be careful: we can still be called with zero ranges, like this: + * `int4multirange(variadic '{}'::int4range[]) + */ + if (dims == 0) + { + range_count = 0; + ranges = NULL; + } + else + { + deconstruct_array(rangeArray, rngtypid, rangetyp->typlen, rangetyp->typbyval, + rangetyp->typalign, &elements, &nulls, &range_count); + + ranges = palloc0(range_count * sizeof(RangeType *)); + for (i = 0; i < range_count; i++) + { + if (nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("multirange values cannot contain NULL members"))); + + /* make_multirange will do its own copy */ + ranges[i] = DatumGetRangeTypeP(elements[i]); + } + } + + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges)); +} + +/* + * Construct multirange value from a single range. It'd be nice if we could + * just use multirange_constructor2 for this case, but we need a non-variadic + * single-arg function to let us define a CAST from a range to its multirange. + */ +Datum +multirange_constructor1(PG_FUNCTION_ARGS) +{ + Oid mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo); + Oid rngtypid; + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + RangeType *range; + + typcache = multirange_get_typcache(fcinfo, mltrngtypid); + rangetyp = typcache->rngtype; + + /* + * These checks should be guaranteed by our signature, but let's do them + * just in case. + */ + + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("multirange values cannot contain NULL members"))); + + range = PG_GETARG_RANGE_P(0); + + /* Make sure the range type matches. */ + rngtypid = RangeTypeGetOid(range); + if (rngtypid != rangetyp->type_id) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type %u does not match constructor type", rngtypid))); + + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range)); +} + +/* + * Constructor just like multirange_constructor1, but opr_sanity gets angry + * if the same internal function handles multiple functions with different arg + * counts. + */ +Datum +multirange_constructor0(PG_FUNCTION_ARGS) +{ + Oid mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo); + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + + typcache = multirange_get_typcache(fcinfo, mltrngtypid); + rangetyp = typcache->rngtype; + + /* We should always be called with no arguments */ + + if (PG_NARGS() == 0) + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL)); + else + elog(ERROR, /* can't happen */ + "niladic multirange constructor must not receive arguments"); +} + + +/* multirange, multirange -> multirange type functions */ + +/* multirange union */ +Datum +multirange_union(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + int32 range_count1; + int32 range_count2; + int32 range_count3; + RangeType **ranges1; + RangeType **ranges2; + RangeType **ranges3; + + if (MultirangeIsEmpty(mr1)) + PG_RETURN_MULTIRANGE_P(mr2); + if (MultirangeIsEmpty(mr2)) + PG_RETURN_MULTIRANGE_P(mr1); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1); + multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2); + + range_count3 = range_count1 + range_count2; + ranges3 = palloc0(range_count3 * sizeof(RangeType *)); + memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *)); + memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *)); + PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype, + range_count3, ranges3)); +} + +/* multirange minus */ +Datum +multirange_minus(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + Oid mltrngtypoid = MultirangeTypeGetOid(mr1); + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + int32 range_count1; + int32 range_count2; + RangeType **ranges1; + RangeType **ranges2; + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + rangetyp = typcache->rngtype; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + PG_RETURN_MULTIRANGE_P(mr1); + + multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1); + multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2); + + PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid, + rangetyp, + range_count1, + ranges1, + range_count2, + ranges2)); +} + +MultirangeType * +multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp, + int32 range_count1, RangeType **ranges1, + int32 range_count2, RangeType **ranges2) +{ + RangeType *r1; + RangeType *r2; + RangeType **ranges3; + int32 range_count3; + int32 i1; + int32 i2; + + /* + * Worst case: every range in ranges1 makes a different cut to some range + * in ranges2. + */ + ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *)); + range_count3 = 0; + + /* + * For each range in mr1, keep subtracting until it's gone or the ranges + * in mr2 have passed it. After a subtraction we assign what's left back + * to r1. The parallel progress through mr1 and mr2 is similar to + * multirange_overlaps_multirange_internal. + */ + r2 = ranges2[0]; + for (i1 = 0, i2 = 0; i1 < range_count1; i1++) + { + r1 = ranges1[i1]; + + /* Discard r2s while r2 << r1 */ + while (r2 != NULL && range_before_internal(rangetyp, r2, r1)) + { + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + } + + while (r2 != NULL) + { + if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1)) + { + /* + * If r2 takes a bite out of the middle of r1, we need two + * outputs + */ + range_count3++; + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + + } + else if (range_overlaps_internal(rangetyp, r1, r2)) + { + /* + * If r2 overlaps r1, replace r1 with r1 - r2. + */ + r1 = range_minus_internal(rangetyp, r1, r2); + + /* + * If r2 goes past r1, then we need to stay with it, in case + * it hits future r1s. Otherwise we need to keep r1, in case + * future r2s hit it. Since we already subtracted, there's no + * point in using the overright/overleft calls. + */ + if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2)) + break; + else + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + + } + else + { + /* + * This and all future r2s are past r1, so keep them. Also + * assign whatever is left of r1 to the result. + */ + break; + } + } + + /* + * Nothing else can remove anything from r1, so keep it. Even if r1 is + * empty here, make_multirange will remove it. + */ + ranges3[range_count3++] = r1; + } + + return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3); +} + +/* multirange intersection */ +Datum +multirange_intersect(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + Oid mltrngtypoid = MultirangeTypeGetOid(mr1); + TypeCacheEntry *typcache; + TypeCacheEntry *rangetyp; + int32 range_count1; + int32 range_count2; + RangeType **ranges1; + RangeType **ranges2; + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + rangetyp = typcache->rngtype; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp)); + + multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1); + multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2); + + PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid, + rangetyp, + range_count1, + ranges1, + range_count2, + ranges2)); +} + +MultirangeType * +multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp, + int32 range_count1, RangeType **ranges1, + int32 range_count2, RangeType **ranges2) +{ + RangeType *r1; + RangeType *r2; + RangeType **ranges3; + int32 range_count3; + int32 i1; + int32 i2; + + if (range_count1 == 0 || range_count2 == 0) + return make_multirange(mltrngtypoid, rangetyp, 0, NULL); + + /*----------------------------------------------- + * Worst case is a stitching pattern like this: + * + * mr1: --- --- --- --- + * mr2: --- --- --- + * mr3: - - - - - - + * + * That seems to be range_count1 + range_count2 - 1, + * but one extra won't hurt. + *----------------------------------------------- + */ + ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *)); + range_count3 = 0; + + /* + * For each range in mr1, keep intersecting until the ranges in mr2 have + * passed it. The parallel progress through mr1 and mr2 is similar to + * multirange_minus_multirange_internal, but we don't have to assign back + * to r1. + */ + r2 = ranges2[0]; + for (i1 = 0, i2 = 0; i1 < range_count1; i1++) + { + r1 = ranges1[i1]; + + /* Discard r2s while r2 << r1 */ + while (r2 != NULL && range_before_internal(rangetyp, r2, r1)) + { + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + } + + while (r2 != NULL) + { + if (range_overlaps_internal(rangetyp, r1, r2)) + { + /* Keep the overlapping part */ + ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2); + + /* If we "used up" all of r2, go to the next one... */ + if (range_overleft_internal(rangetyp, r2, r1)) + r2 = ++i2 >= range_count2 ? NULL : ranges2[i2]; + + /* ...otherwise go to the next r1 */ + else + break; + } + else + /* We're past r1, so move to the next one */ + break; + } + + /* If we're out of r2s, there can be no more intersections */ + if (r2 == NULL) + break; + } + + return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3); +} + +/* + * range_agg_transfn: combine adjacent/overlapping ranges. + * + * All we do here is gather the input ranges into an array + * so that the finalfn can sort and combine them. + */ +Datum +range_agg_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid rngtypoid; + ArrayBuildState *state; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "range_agg_transfn called in non-aggregate context"); + + rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1); + if (!type_is_range(rngtypoid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("range_agg must be called with a range"))); + + if (PG_ARGISNULL(0)) + state = initArrayResult(rngtypoid, aggContext, false); + else + state = (ArrayBuildState *) PG_GETARG_POINTER(0); + + /* skip NULLs */ + if (!PG_ARGISNULL(1)) + accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext); + + PG_RETURN_POINTER(state); +} + +/* + * range_agg_finalfn: use our internal array to merge touching ranges. + */ +Datum +range_agg_finalfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid mltrngtypoid; + TypeCacheEntry *typcache; + ArrayBuildState *state; + int32 range_count; + RangeType **ranges; + int i; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "range_agg_finalfn called in non-aggregate context"); + + state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0); + if (state == NULL) + /* This shouldn't be possible, but just in case.... */ + PG_RETURN_NULL(); + + /* Also return NULL if we had zero inputs, like other aggregates */ + range_count = state->nelems; + if (range_count == 0) + PG_RETURN_NULL(); + + mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo); + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + + ranges = palloc0(range_count * sizeof(RangeType *)); + for (i = 0; i < range_count; i++) + ranges[i] = DatumGetRangeTypeP(state->dvalues[i]); + + PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges)); +} + +Datum +multirange_intersect_agg_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid mltrngtypoid; + TypeCacheEntry *typcache; + MultirangeType *result; + MultirangeType *current; + int32 range_count1; + int32 range_count2; + RangeType **ranges1; + RangeType **ranges2; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context"); + + mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1); + if (!type_is_multirange(mltrngtypoid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("range_intersect_agg must be called with a multirange"))); + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + + /* strictness ensures these are non-null */ + result = PG_GETARG_MULTIRANGE_P(0); + current = PG_GETARG_MULTIRANGE_P(1); + + multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1); + multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2); + + result = multirange_intersect_internal(mltrngtypoid, + typcache->rngtype, + range_count1, + ranges1, + range_count2, + ranges2); + PG_RETURN_RANGE_P(result); +} + + +/* multirange -> element type functions */ + +/* extract lower bound value */ +Datum +multirange_lower(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_NULL(); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + multirange_get_bounds(typcache->rngtype, mr, 0, + &lower, &upper); + + if (!lower.infinite) + PG_RETURN_DATUM(lower.val); + else + PG_RETURN_NULL(); +} + +/* extract upper bound value */ +Datum +multirange_upper(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_NULL(); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower, &upper); + + if (!upper.infinite) + PG_RETURN_DATUM(upper.val); + else + PG_RETURN_NULL(); +} + + +/* multirange -> bool functions */ + +/* is multirange empty? */ +Datum +multirange_empty(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + + PG_RETURN_BOOL(MultirangeIsEmpty(mr)); +} + +/* is lower bound inclusive? */ +Datum +multirange_lower_inc(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + multirange_get_bounds(typcache->rngtype, mr, 0, + &lower, &upper); + + PG_RETURN_BOOL(lower.inclusive); +} + +/* is upper bound inclusive? */ +Datum +multirange_upper_inc(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower, &upper); + + PG_RETURN_BOOL(upper.inclusive); +} + +/* is lower bound infinite? */ +Datum +multirange_lower_inf(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + multirange_get_bounds(typcache->rngtype, mr, 0, + &lower, &upper); + + PG_RETURN_BOOL(lower.infinite); +} + +/* is upper bound infinite? */ +Datum +multirange_upper_inf(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + TypeCacheEntry *typcache; + RangeBound lower; + RangeBound upper; + + if (MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower, &upper); + + PG_RETURN_BOOL(upper.infinite); +} + + + +/* multirange, element -> bool functions */ + +/* contains? */ +Datum +multirange_contains_elem(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + Datum val = PG_GETARG_DATUM(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val)); +} + +/* contained by? */ +Datum +elem_contained_by_multirange(PG_FUNCTION_ARGS) +{ + Datum val = PG_GETARG_DATUM(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val)); +} + +/* + * Comparison function for checking if any range of multirange contains given + * key element using binary search. + */ +static int +multirange_elem_bsearch_comparison(TypeCacheEntry *typcache, + RangeBound *lower, RangeBound *upper, + void *key, bool *match) +{ + Datum val = *((Datum *) key); + int cmp; + + if (!lower->infinite) + { + cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo, + typcache->rng_collation, + lower->val, val)); + if (cmp > 0 || (cmp == 0 && !lower->inclusive)) + return -1; + } + + if (!upper->infinite) + { + cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo, + typcache->rng_collation, + upper->val, val)); + if (cmp < 0 || (cmp == 0 && !upper->inclusive)) + return 1; + } + + *match = true; + return 0; +} + +/* + * Test whether multirange mr contains a specific element value. + */ +bool +multirange_contains_elem_internal(TypeCacheEntry *typcache, + MultirangeType *mr, Datum val) +{ + if (MultirangeIsEmpty(mr)) + return false; + + return multirange_bsearch_match(typcache->rngtype, mr, &val, + multirange_elem_bsearch_comparison); +} + +/* multirange, range -> bool functions */ + +/* contains? */ +Datum +multirange_contains_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r)); +} + +/* contained by? */ +Datum +range_contained_by_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r)); +} + +/* + * Comparison function for checking if any range of multirange contains given + * key range using binary search. + */ +static int +multirange_range_contains_bsearch_comparison(TypeCacheEntry *typcache, + RangeBound *lower, RangeBound *upper, + void *key, bool *match) +{ + RangeBound *keyLower = (RangeBound *) key; + RangeBound *keyUpper = (RangeBound *) key + 1; + + /* Check if key range is strictly in the left or in the right */ + if (range_cmp_bounds(typcache, keyUpper, lower) < 0) + return -1; + if (range_cmp_bounds(typcache, keyLower, upper) > 0) + return -1; + + /* + * At this point we found overlapping range. But we have to check if it + * really contains the key range. Anyway, we have to stop our search + * here, because multirange contains only non-overlapping ranges. + */ + *match = range_bounds_contains(typcache, lower, upper, keyLower, keyUpper); + + return 0; +} + +/* + * Test whether multirange mr contains a specific range r. + */ +bool +multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r) +{ + TypeCacheEntry *rangetyp; + RangeBound bounds[2]; + bool empty; + + rangetyp = typcache->rngtype; + + /* + * Every multirange contains an infinite number of empty ranges, even an + * empty one. + */ + if (RangeIsEmpty(r)) + return true; + + if (MultirangeIsEmpty(mr)) + return false; + + range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty); + Assert(!empty); + + return multirange_bsearch_match(rangetyp, mr, bounds, + multirange_range_contains_bsearch_comparison); +} + + +/* multirange, multirange -> bool functions */ + +/* equality (internal version) */ +bool +multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2) +{ + TypeCacheEntry *rangetyp = typcache->rngtype; + int32 range_count_1; + int32 range_count_2; + int32 i; + RangeBound lower1, + upper1, + lower2, + upper2; + + /* Different types should be prevented by ANYMULTIRANGE matching rules */ + if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2)) + elog(ERROR, "multirange types do not match"); + + range_count_1 = mr1->rangeCount; + range_count_2 = mr2->rangeCount; + + if (range_count_1 != range_count_2) + return false; + + for (i = 0; i < range_count_1; i++) + { + multirange_get_bounds(rangetyp, mr1, i, &lower1, &upper1); + multirange_get_bounds(rangetyp, mr2, i, &lower2, &upper2); + + if (range_cmp_bounds(rangetyp, &lower1, &lower2) != 0 || + range_cmp_bounds(rangetyp, &upper1, &upper2) != 0) + return false; + } + + return true; +} + +/* equality */ +Datum +multirange_eq(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_eq_internal(typcache, mr1, mr2)); +} + +/* inequality (internal version) */ +bool +multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2) +{ + return (!multirange_eq_internal(typcache, mr1, mr2)); +} + +/* inequality */ +Datum +multirange_ne(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2)); +} + +/* overlaps? */ +Datum +range_overlaps_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr)); +} + +Datum +multirange_overlaps_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr)); +} + +Datum +multirange_overlaps_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_overlaps_multirange_internal(typcache, mr1, mr2)); +} + +/* + * Comparison function for checking if any range of multirange overlaps given + * key range using binary search. + */ +static int +multirange_range_overlaps_bsearch_comparison(TypeCacheEntry *typcache, + RangeBound *lower, RangeBound *upper, + void *key, bool *match) +{ + RangeBound *keyLower = (RangeBound *) key; + RangeBound *keyUpper = (RangeBound *) key + 1; + + if (range_cmp_bounds(typcache, keyUpper, lower) < 0) + return -1; + if (range_cmp_bounds(typcache, keyLower, upper) > 0) + return -1; + + *match = true; + return 0; +} + +bool +range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr) +{ + TypeCacheEntry *rangetyp; + RangeBound bounds[2]; + bool empty; + + rangetyp = typcache->rngtype; + + /* + * Empties never overlap, even with empties. (This seems strange since + * they *do* contain each other, but we want to follow how ranges work.) + */ + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty); + Assert(!empty); + + return multirange_bsearch_match(rangetyp, mr, bounds, + multirange_range_overlaps_bsearch_comparison); +} + +bool +multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1, + MultirangeType *mr2) +{ + TypeCacheEntry *rangetyp; + int32 range_count1; + int32 range_count2; + int32 i1; + int32 i2; + RangeBound lower1, + upper1, + lower2, + upper2; + + /* + * Empties never overlap, even with empties. (This seems strange since + * they *do* contain each other, but we want to follow how ranges work.) + */ + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + return false; + + rangetyp = typcache->rngtype; + + range_count1 = mr1->rangeCount; + range_count2 = mr2->rangeCount; + + /* + * Every range in mr1 gets a chance to overlap with the ranges in mr2, but + * we can use their ordering to avoid O(n^2). This is similar to + * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we + * don't find an overlap with r we're done, and here if we don't find an + * overlap with r2 we try the next r2. + */ + i1 = 0; + multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1); + for (i1 = 0, i2 = 0; i2 < range_count2; i2++) + { + multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2); + + /* Discard r1s while r1 << r2 */ + while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0) + { + if (++i1 >= range_count1) + return false; + multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1); + } + + /* + * If r1 && r2, we're done, otherwise we failed to find an overlap for + * r2, so go to the next one. + */ + if (range_bounds_overlaps(rangetyp, &lower1, &upper1, &lower2, &upper2)) + return true; + } + + /* We looked through all of mr2 without finding an overlap */ + return false; +} + +/* does not extend to right of? */ +Datum +range_overleft_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty); + Assert(!empty); + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower2, &upper2); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0); +} + +Datum +multirange_overleft_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (MultirangeIsEmpty(mr) || RangeIsEmpty(r)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lower1, &upper1); + range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty); + Assert(!empty); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0); +} + +Datum +multirange_overleft_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1, + &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, mr2->rangeCount - 1, + &lower2, &upper2); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0); +} + +/* does not extend to left of? */ +Datum +range_overright_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty); + Assert(!empty); + multirange_get_bounds(typcache->rngtype, mr, 0, &lower2, &upper2); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0); +} + +Datum +multirange_overright_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (MultirangeIsEmpty(mr) || RangeIsEmpty(r)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + multirange_get_bounds(typcache->rngtype, mr, 0, &lower1, &upper1); + range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty); + Assert(!empty); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0); +} + +Datum +multirange_overright_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + RangeBound lower1, + upper1, + lower2, + upper2; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + PG_RETURN_BOOL(false); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + multirange_get_bounds(typcache->rngtype, mr1, 0, &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, 0, &lower2, &upper2); + + PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0); +} + +/* contains? */ +Datum +multirange_contains_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache, mr1, mr2)); +} + +/* contained by? */ +Datum +multirange_contained_by_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache, mr2, mr1)); +} + +/* + * Test whether multirange mr1 contains every range from another multirange mr2. + */ +bool +multirange_contains_multirange_internal(TypeCacheEntry *typcache, + MultirangeType *mr1, MultirangeType *mr2) +{ + TypeCacheEntry *rangetyp; + int32 range_count1 = mr1->rangeCount; + int32 range_count2 = mr2->rangeCount; + int i1, + i2; + RangeBound lower1, + upper1, + lower2, + upper2; + + rangetyp = typcache->rngtype; + + /* + * We follow the same logic for empties as ranges: - an empty multirange + * contains an empty range/multirange. - an empty multirange can't contain + * any other range/multirange. - an empty multirange is contained by any + * other range/multirange. + */ + + if (range_count2 == 0) + return true; + if (range_count1 == 0) + return false; + + /* + * Every range in mr2 must be contained by some range in mr1. To avoid + * O(n^2) we walk through both ranges in tandem. + */ + i1 = 0; + multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1); + for (i2 = 0; i2 < range_count2; i2++) + { + multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2); + + /* Discard r1s while r1 << r2 */ + while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0) + { + if (++i1 >= range_count1) + return false; + multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1); + } + + /* + * If r1 @> r2, go to the next r2, otherwise return false (since every + * r1[n] and r1[n+1] must have a gap). Note this will give weird + * answers if you don't canonicalize, e.g. with a custom + * int2multirange {[1,1], [2,2]} there is a "gap". But that is + * consistent with other range operators, e.g. '[1,1]'::int2range -|- + * '[2,2]'::int2range is false. + */ + if (!range_bounds_contains(rangetyp, &lower1, &upper1, + &lower2, &upper2)) + return false; + } + + /* All ranges in mr2 are satisfied */ + return true; +} + +/* strictly left of? */ +Datum +range_before_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr)); +} + +Datum +multirange_before_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr)); +} + +Datum +multirange_before_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_before_multirange_internal(typcache, mr1, mr2)); +} + +/* strictly right of? */ +Datum +range_after_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr)); +} + +Datum +multirange_after_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr)); +} + +Datum +multirange_after_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + PG_RETURN_BOOL(multirange_before_multirange_internal(typcache, mr2, mr1)); +} + +/* strictly left of? (internal version) */ +bool +range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r, + MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty); + Assert(!empty); + + multirange_get_bounds(typcache->rngtype, mr, 0, + &lower2, &upper2); + + return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0); +} + +bool +multirange_before_multirange_internal(TypeCacheEntry *typcache, + MultirangeType *mr1, + MultirangeType *mr2) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + return false; + + multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1, + &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, 0, + &lower2, &upper2); + + return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0); +} + +/* strictly right of? (internal version) */ +bool +range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r, + MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + int32 range_count; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty); + Assert(!empty); + + range_count = mr->rangeCount; + multirange_get_bounds(typcache->rngtype, mr, range_count - 1, + &lower2, &upper2); + + return (range_cmp_bounds(typcache->rngtype, &lower1, &upper2) > 0); +} + +bool +range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r, + MultirangeType *mr) +{ + RangeBound lower1, + upper1, + lower2, + upper2; + bool empty; + int32 range_count; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty); + Assert(!empty); + + range_count = mr->rangeCount; + multirange_get_bounds(typcache->rngtype, mr, 0, + &lower2, &upper2); + + if (bounds_adjacent(typcache->rngtype, upper1, lower2)) + return true; + + if (range_count > 1) + multirange_get_bounds(typcache->rngtype, mr, range_count - 1, + &lower2, &upper2); + + if (bounds_adjacent(typcache->rngtype, upper2, lower1)) + return true; + + return false; +} + +/* adjacent to? */ +Datum +range_adjacent_multirange(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE_P(0); + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr)); +} + +Datum +multirange_adjacent_range(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + RangeType *r = PG_GETARG_RANGE_P(1); + TypeCacheEntry *typcache; + + if (RangeIsEmpty(r) || MultirangeIsEmpty(mr)) + return false; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + + PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr)); +} + +Datum +multirange_adjacent_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + TypeCacheEntry *typcache; + int32 range_count1; + int32 range_count2; + RangeBound lower1, + upper1, + lower2, + upper2; + + if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2)) + return false; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + range_count1 = mr1->rangeCount; + range_count2 = mr2->rangeCount; + multirange_get_bounds(typcache->rngtype, mr1, range_count1 - 1, + &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, 0, + &lower2, &upper2); + if (bounds_adjacent(typcache->rngtype, upper1, lower2)) + PG_RETURN_BOOL(true); + + if (range_count1 > 1) + multirange_get_bounds(typcache->rngtype, mr1, 0, + &lower1, &upper1); + if (range_count2 > 1) + multirange_get_bounds(typcache->rngtype, mr2, range_count2 - 1, + &lower2, &upper2); + if (bounds_adjacent(typcache->rngtype, upper2, lower1)) + PG_RETURN_BOOL(true); + PG_RETURN_BOOL(false); +} + +/* Btree support */ + +/* btree comparator */ +Datum +multirange_cmp(PG_FUNCTION_ARGS) +{ + MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0); + MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1); + int32 range_count_1; + int32 range_count_2; + int32 range_count_max; + int32 i; + TypeCacheEntry *typcache; + int cmp = 0; /* If both are empty we'll use this. */ + + /* Different types should be prevented by ANYMULTIRANGE matching rules */ + if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2)) + elog(ERROR, "multirange types do not match"); + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1)); + + range_count_1 = mr1->rangeCount; + range_count_2 = mr2->rangeCount; + + /* Loop over source data */ + range_count_max = Max(range_count_1, range_count_2); + for (i = 0; i < range_count_max; i++) + { + RangeBound lower1, + upper1, + lower2, + upper2; + + /* + * If one multirange is shorter, it's as if it had empty ranges at the + * end to extend its length. An empty range compares earlier than any + * other range, so the shorter multirange comes before the longer. + * This is the same behavior as in other types, e.g. in strings 'aaa' + * < 'aaaaaa'. + */ + if (i >= range_count_1) + { + cmp = -1; + break; + } + if (i >= range_count_2) + { + cmp = 1; + break; + } + + multirange_get_bounds(typcache->rngtype, mr1, i, &lower1, &upper1); + multirange_get_bounds(typcache->rngtype, mr2, i, &lower2, &upper2); + + cmp = range_cmp_bounds(typcache->rngtype, &lower1, &lower2); + if (cmp == 0) + cmp = range_cmp_bounds(typcache->rngtype, &upper1, &upper2); + if (cmp != 0) + break; + } + + PG_FREE_IF_COPY(mr1, 0); + PG_FREE_IF_COPY(mr2, 1); + + PG_RETURN_INT32(cmp); +} + +/* inequality operators using the multirange_cmp function */ +Datum +multirange_lt(PG_FUNCTION_ARGS) +{ + int cmp = multirange_cmp(fcinfo); + + PG_RETURN_BOOL(cmp < 0); +} + +Datum +multirange_le(PG_FUNCTION_ARGS) +{ + int cmp = multirange_cmp(fcinfo); + + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +multirange_ge(PG_FUNCTION_ARGS) +{ + int cmp = multirange_cmp(fcinfo); + + PG_RETURN_BOOL(cmp >= 0); +} + +Datum +multirange_gt(PG_FUNCTION_ARGS) +{ + int cmp = multirange_cmp(fcinfo); + + PG_RETURN_BOOL(cmp > 0); +} + +/* multirange -> range functions */ + +/* Find the smallest range that includes everything in the multirange */ +Datum +range_merge_from_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + Oid mltrngtypoid = MultirangeTypeGetOid(mr); + TypeCacheEntry *typcache; + RangeType *result; + + typcache = multirange_get_typcache(fcinfo, mltrngtypoid); + + if (MultirangeIsEmpty(mr)) + { + result = make_empty_range(typcache->rngtype); + } + else if (mr->rangeCount == 1) + { + result = multirange_get_range(typcache->rngtype, mr, 0); + } + else + { + RangeBound firstLower, + firstUpper, + lastLower, + lastUpper; + + multirange_get_bounds(typcache->rngtype, mr, 0, + &firstLower, &firstUpper); + multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1, + &lastLower, &lastUpper); + + result = make_range(typcache->rngtype, &firstLower, &lastUpper, false); + } + + PG_RETURN_RANGE_P(result); +} + +/* Hash support */ + +/* hash a multirange value */ +Datum +hash_multirange(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + uint32 result = 1; + TypeCacheEntry *typcache, + *scache; + int32 range_count, + i; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + scache = typcache->rngtype->rngelemtype; + if (!OidIsValid(scache->hash_proc_finfo.fn_oid)) + { + scache = lookup_type_cache(scache->type_id, + TYPECACHE_HASH_PROC_FINFO); + if (!OidIsValid(scache->hash_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(scache->type_id)))); + } + + range_count = mr->rangeCount; + for (i = 0; i < range_count; i++) + { + RangeBound lower, + upper; + uint8 flags = MultirangeGetFlagsPtr(mr)[i]; + uint32 lower_hash; + uint32 upper_hash; + uint32 range_hash; + + multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper); + + if (RANGE_HAS_LBOUND(flags)) + lower_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo, + typcache->rngtype->rng_collation, + lower.val)); + else + lower_hash = 0; + + if (RANGE_HAS_UBOUND(flags)) + upper_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo, + typcache->rngtype->rng_collation, + upper.val)); + else + upper_hash = 0; + + /* Merge hashes of flags and bounds */ + range_hash = hash_uint32((uint32) flags); + range_hash ^= lower_hash; + range_hash = (range_hash << 1) | (range_hash >> 31); + range_hash ^= upper_hash; + + /* + * Use the same approach as hash_array to combine the individual + * elements' hash values: + */ + result = (result << 5) - result + range_hash; + } + + PG_FREE_IF_COPY(mr, 0); + + PG_RETURN_UINT32(result); +} + +/* + * Returns 64-bit value by hashing a value to a 64-bit value, with a seed. + * Otherwise, similar to hash_multirange. + */ +Datum +hash_multirange_extended(PG_FUNCTION_ARGS) +{ + MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0); + Datum seed = PG_GETARG_DATUM(1); + uint64 result = 1; + TypeCacheEntry *typcache, + *scache; + int32 range_count, + i; + + typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr)); + scache = typcache->rngtype->rngelemtype; + if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid)) + { + scache = lookup_type_cache(scache->type_id, + TYPECACHE_HASH_EXTENDED_PROC_FINFO); + if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(scache->type_id)))); + } + + range_count = mr->rangeCount; + for (i = 0; i < range_count; i++) + { + RangeBound lower, + upper; + uint8 flags = MultirangeGetFlagsPtr(mr)[i]; + uint64 lower_hash; + uint64 upper_hash; + uint64 range_hash; + + multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper); + + if (RANGE_HAS_LBOUND(flags)) + lower_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo, + typcache->rngtype->rng_collation, + lower.val, + seed)); + else + lower_hash = 0; + + if (RANGE_HAS_UBOUND(flags)) + upper_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo, + typcache->rngtype->rng_collation, + upper.val, + seed)); + else + upper_hash = 0; + + /* Merge hashes of flags and bounds */ + range_hash = DatumGetUInt64(hash_uint32_extended((uint32) flags, + DatumGetInt64(seed))); + range_hash ^= lower_hash; + range_hash = ROTATE_HIGH_AND_LOW_32BITS(range_hash); + range_hash ^= upper_hash; + + /* + * Use the same approach as hash_array to combine the individual + * elements' hash values: + */ + result = (result << 5) - result + range_hash; + } + + PG_FREE_IF_COPY(mr, 0); + + PG_RETURN_UINT64(result); +} diff --git a/src/backend/utils/adt/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c new file mode 100644 index 00000000000..7259af0b853 --- /dev/null +++ b/src/backend/utils/adt/multirangetypes_selfuncs.c @@ -0,0 +1,1320 @@ +/*------------------------------------------------------------------------- + * + * multirangetypes_selfuncs.c + * Functions for selectivity estimation of multirange operators + * + * Estimates are based on histograms of lower and upper bounds, and the + * fraction of empty multiranges. + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/multirangetypes_selfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <math.h> + +#include "access/htup_details.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_type.h" +#include "utils/float.h" +#include "utils/fmgrprotos.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/multirangetypes.h" +#include "utils/selfuncs.h" +#include "utils/typcache.h" + +static double calc_multirangesel(TypeCacheEntry *typcache, + VariableStatData *vardata, + const MultirangeType *constval, Oid operator); +static double default_multirange_selectivity(Oid operator); +static double default_multirange_selectivity(Oid operator); +static double calc_hist_selectivity(TypeCacheEntry *typcache, + VariableStatData *vardata, + const MultirangeType *constval, + Oid operator); +static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache, + const RangeBound *constbound, + const RangeBound *hist, + int hist_nvalues, bool equal); +static int rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, + const RangeBound *hist, int hist_length, bool equal); +static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value, + const RangeBound *hist1, const RangeBound *hist2); +static float8 get_len_position(double value, double hist1, double hist2); +static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, + const RangeBound *bound2); +static int length_hist_bsearch(Datum *length_hist_values, + int length_hist_nvalues, double value, + bool equal); +static double calc_length_hist_frac(Datum *length_hist_values, + int length_hist_nvalues, double length1, + double length2, bool equal); +static double calc_hist_selectivity_contained(TypeCacheEntry *typcache, + const RangeBound *lower, + RangeBound *upper, + const RangeBound *hist_lower, + int hist_nvalues, + Datum *length_hist_values, + int length_hist_nvalues); +static double calc_hist_selectivity_contains(TypeCacheEntry *typcache, + const RangeBound *lower, + const RangeBound *upper, + const RangeBound *hist_lower, + int hist_nvalues, + Datum *length_hist_values, + int length_hist_nvalues); + +/* + * Returns a default selectivity estimate for given operator, when we don't + * have statistics or cannot use them for some reason. + */ +static double +default_multirange_selectivity(Oid operator) +{ + switch (operator) + { + case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RANGE_OP: + case OID_RANGE_OVERLAPS_MULTIRANGE_OP: + return 0.01; + + case OID_MULTIRANGE_CONTAINS_RANGE_OP: + case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP: + case OID_MULTIRANGE_RANGE_CONTAINED_OP: + case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP: + return 0.005; + + case OID_MULTIRANGE_CONTAINS_ELEM_OP: + case OID_MULTIRANGE_ELEM_CONTAINED_OP: + + /* + * "multirange @> elem" is more or less identical to a scalar + * inequality "A >= b AND A <= c". + */ + return DEFAULT_MULTIRANGE_INEQ_SEL; + + case OID_MULTIRANGE_LESS_OP: + case OID_MULTIRANGE_LESS_EQUAL_OP: + case OID_MULTIRANGE_GREATER_OP: + case OID_MULTIRANGE_GREATER_EQUAL_OP: + case OID_MULTIRANGE_LEFT_RANGE_OP: + case OID_MULTIRANGE_LEFT_MULTIRANGE_OP: + case OID_RANGE_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_RIGHT_RANGE_OP: + case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP: + case OID_RANGE_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP: + case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP: + case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + /* these are similar to regular scalar inequalities */ + return DEFAULT_INEQ_SEL; + + default: + + /* + * all multirange operators should be handled above, but just in + * case + */ + return 0.01; + } +} + +/* + * multirangesel -- restriction selectivity for multirange operators + */ +Datum +multirangesel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + VariableStatData vardata; + Node *other; + bool varonleft; + Selectivity selec; + TypeCacheEntry *typcache = NULL; + MultirangeType *constmultirange = NULL; + RangeType *constrange = NULL; + + /* + * If expression is not (variable op something) or (something op + * variable), then punt and return a default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + PG_RETURN_FLOAT8(default_multirange_selectivity(operator)); + + /* + * Can't do anything useful if the something is not a constant, either. + */ + if (!IsA(other, Const)) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(default_multirange_selectivity(operator)); + } + + /* + * All the multirange operators are strict, so we can cope with a NULL + * constant right away. + */ + if (((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(0.0); + } + + /* + * If var is on the right, commute the operator, so that we can assume the + * var is on the left in what follows. + */ + if (!varonleft) + { + /* we have other Op var, commute to make var Op other */ + operator = get_commutator(operator); + if (!operator) + { + /* Use default selectivity (should we raise an error instead?) */ + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(default_multirange_selectivity(operator)); + } + } + + /* + * OK, there's a Var and a Const we're dealing with here. We need the + * Const to be of same multirange type as the column, else we can't do + * anything useful. (Such cases will likely fail at runtime, but here we'd + * rather just return a default estimate.) + * + * If the operator is "multirange @> element", the constant should be of + * the element type of the multirange column. Convert it to a multirange + * that includes only that single point, so that we don't need special + * handling for that in what follows. + */ + if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP) + { + typcache = multirange_get_typcache(fcinfo, vardata.vartype); + + if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id) + { + RangeBound lower, + upper; + + lower.inclusive = true; + lower.val = ((Const *) other)->constvalue; + lower.infinite = false; + lower.lower = true; + upper.inclusive = true; + upper.val = ((Const *) other)->constvalue; + upper.infinite = false; + upper.lower = false; + constrange = range_serialize(typcache->rngtype, &lower, &upper, false); + constmultirange = make_multirange(typcache->type_id, typcache->rngtype, + 1, &constrange); + } + } + else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP || + operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP || + operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP || + operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP || + operator == OID_MULTIRANGE_LEFT_RANGE_OP || + operator == OID_MULTIRANGE_RIGHT_RANGE_OP) + { + /* + * Promote a range in "multirange OP range" just like we do an element + * in "multirange OP element". + */ + typcache = multirange_get_typcache(fcinfo, vardata.vartype); + if (((Const *) other)->consttype == typcache->rngtype->type_id) + { + constrange = DatumGetRangeTypeP(((Const *) other)->constvalue); + constmultirange = make_multirange(typcache->type_id, typcache->rngtype, + 1, &constrange); + } + } + else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP || + operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP || + operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP || + operator == OID_RANGE_LEFT_MULTIRANGE_OP || + operator == OID_RANGE_RIGHT_MULTIRANGE_OP || + operator == OID_MULTIRANGE_ELEM_CONTAINED_OP || + operator == OID_MULTIRANGE_RANGE_CONTAINED_OP) + { + /* + * Here, the Var is the elem/range, not the multirange. For now we + * just punt and return the default estimate. In future we could + * disassemble the multirange constant to do something more + * intelligent. + */ + } + else if (((Const *) other)->consttype == vardata.vartype) + { + /* Both sides are the same multirange type */ + typcache = multirange_get_typcache(fcinfo, vardata.vartype); + + constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue); + } + + /* + * If we got a valid constant on one side of the operator, proceed to + * estimate using statistics. Otherwise punt and return a default constant + * estimate. Note that calc_multirangesel need not handle + * OID_MULTIRANGE_*_CONTAINED_OP. + */ + if (constmultirange) + selec = calc_multirangesel(typcache, &vardata, constmultirange, operator); + else + selec = default_multirange_selectivity(operator); + + ReleaseVariableStats(vardata); + + CLAMP_PROBABILITY(selec); + + PG_RETURN_FLOAT8((float8) selec); +} + +static double +calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata, + const MultirangeType *constval, Oid operator) +{ + double hist_selec; + double selec; + float4 empty_frac, + null_frac; + + /* + * First look up the fraction of NULLs and empty multiranges from + * pg_statistic. + */ + if (HeapTupleIsValid(vardata->statsTuple)) + { + Form_pg_statistic stats; + AttStatsSlot sslot; + + stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); + null_frac = stats->stanullfrac; + + /* Try to get fraction of empty multiranges */ + if (get_attstatsslot(&sslot, vardata->statsTuple, + STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, + InvalidOid, + ATTSTATSSLOT_NUMBERS)) + { + if (sslot.nnumbers != 1) + elog(ERROR, "invalid empty fraction statistic"); /* shouldn't happen */ + empty_frac = sslot.numbers[0]; + free_attstatsslot(&sslot); + } + else + { + /* No empty fraction statistic. Assume no empty ranges. */ + empty_frac = 0.0; + } + } + else + { + /* + * No stats are available. Follow through the calculations below + * anyway, assuming no NULLs and no empty multiranges. This still + * allows us to give a better-than-nothing estimate based on whether + * the constant is an empty multirange or not. + */ + null_frac = 0.0; + empty_frac = 0.0; + } + + if (MultirangeIsEmpty(constval)) + { + /* + * An empty multirange matches all multiranges, all empty multiranges, + * or nothing, depending on the operator + */ + switch (operator) + { + /* these return false if either argument is empty */ + case OID_RANGE_OVERLAPS_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP: + case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP: + /* nothing is less than an empty multirange */ + case OID_MULTIRANGE_LESS_OP: + selec = 0.0; + break; + + /* + * only empty multiranges can be contained by an empty + * multirange + */ + case OID_MULTIRANGE_RANGE_CONTAINED_OP: + case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP: + /* only empty ranges are <= an empty multirange */ + case OID_MULTIRANGE_LESS_EQUAL_OP: + selec = empty_frac; + break; + + /* everything contains an empty multirange */ + case OID_MULTIRANGE_CONTAINS_RANGE_OP: + case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP: + /* everything is >= an empty multirange */ + case OID_MULTIRANGE_GREATER_EQUAL_OP: + selec = 1.0; + break; + + /* all non-empty multiranges are > an empty multirange */ + case OID_MULTIRANGE_GREATER_OP: + selec = 1.0 - empty_frac; + break; + + /* an element cannot be empty */ + case OID_MULTIRANGE_ELEM_CONTAINED_OP: + case OID_MULTIRANGE_CONTAINS_ELEM_OP: + default: + elog(ERROR, "unexpected operator %u", operator); + selec = 0.0; /* keep compiler quiet */ + break; + } + } + else + { + /* + * Calculate selectivity using bound histograms. If that fails for + * some reason, e.g no histogram in pg_statistic, use the default + * constant estimate for the fraction of non-empty values. This is + * still somewhat better than just returning the default estimate, + * because this still takes into account the fraction of empty and + * NULL tuples, if we had statistics for them. + */ + hist_selec = calc_hist_selectivity(typcache, vardata, constval, + operator); + if (hist_selec < 0.0) + hist_selec = default_multirange_selectivity(operator); + + /* + * Now merge the results for the empty multiranges and histogram + * calculations, realizing that the histogram covers only the + * non-null, non-empty values. + */ + if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP || + operator == OID_MULTIRANGE_RANGE_CONTAINED_OP || + operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP) + { + /* empty is contained by anything non-empty */ + selec = (1.0 - empty_frac) * hist_selec + empty_frac; + } + else + { + /* with any other operator, empty Op non-empty matches nothing */ + selec = (1.0 - empty_frac) * hist_selec; + } + } + + /* all multirange operators are strict */ + selec *= (1.0 - null_frac); + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * Calculate multirange operator selectivity using histograms of multirange bounds. + * + * This estimate is for the portion of values that are not empty and not + * NULL. + */ +static double +calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata, + const MultirangeType *constval, Oid operator) +{ + TypeCacheEntry *rng_typcache = typcache->rngtype; + AttStatsSlot hslot; + AttStatsSlot lslot; + int nhist; + RangeBound *hist_lower; + RangeBound *hist_upper; + int i; + RangeBound const_lower; + RangeBound const_upper; + RangeBound tmp; + double hist_selec; + + /* Can't use the histogram with insecure multirange support functions */ + if (!statistic_proc_security_check(vardata, + rng_typcache->rng_cmp_proc_finfo.fn_oid)) + return -1; + if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) && + !statistic_proc_security_check(vardata, + rng_typcache->rng_subdiff_finfo.fn_oid)) + return -1; + + /* Try to get histogram of ranges */ + if (!(HeapTupleIsValid(vardata->statsTuple) && + get_attstatsslot(&hslot, vardata->statsTuple, + STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid, + ATTSTATSSLOT_VALUES))) + return -1.0; + + /* check that it's a histogram, not just a dummy entry */ + if (hslot.nvalues < 2) + { + free_attstatsslot(&hslot); + return -1.0; + } + + /* + * Convert histogram of ranges into histograms of its lower and upper + * bounds. + */ + nhist = hslot.nvalues; + hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist); + hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist); + for (i = 0; i < nhist; i++) + { + bool empty; + + range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]), + &hist_lower[i], &hist_upper[i], &empty); + /* The histogram should not contain any empty ranges */ + if (empty) + elog(ERROR, "bounds histogram contains an empty range"); + } + + /* @> and @< also need a histogram of range lengths */ + if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP || + operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP || + operator == OID_MULTIRANGE_RANGE_CONTAINED_OP || + operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP) + { + if (!(HeapTupleIsValid(vardata->statsTuple) && + get_attstatsslot(&lslot, vardata->statsTuple, + STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM, + InvalidOid, + ATTSTATSSLOT_VALUES))) + { + free_attstatsslot(&hslot); + return -1.0; + } + + /* check that it's a histogram, not just a dummy entry */ + if (lslot.nvalues < 2) + { + free_attstatsslot(&lslot); + free_attstatsslot(&hslot); + return -1.0; + } + } + else + memset(&lslot, 0, sizeof(lslot)); + + /* Extract the bounds of the constant value. */ + Assert(constval->rangeCount > 0); + multirange_get_bounds(rng_typcache, constval, 0, + &const_lower, &tmp); + multirange_get_bounds(rng_typcache, constval, constval->rangeCount - 1, + &tmp, &const_upper); + + /* + * Calculate selectivity comparing the lower or upper bound of the + * constant with the histogram of lower or upper bounds. + */ + switch (operator) + { + case OID_MULTIRANGE_LESS_OP: + + /* + * The regular b-tree comparison operators (<, <=, >, >=) compare + * the lower bounds first, and the upper bounds for values with + * equal lower bounds. Estimate that by comparing the lower bounds + * only. This gives a fairly accurate estimate assuming there + * aren't many rows with a lower bound equal to the constant's + * lower bound. + */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_MULTIRANGE_LESS_EQUAL_OP: + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, true); + break; + + case OID_MULTIRANGE_GREATER_OP: + hist_selec = + 1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_MULTIRANGE_GREATER_EQUAL_OP: + hist_selec = + 1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, true); + break; + + case OID_RANGE_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_LEFT_RANGE_OP: + case OID_MULTIRANGE_LEFT_MULTIRANGE_OP: + /* var << const when upper(var) < lower(const) */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_upper, nhist, false); + break; + + case OID_RANGE_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_RIGHT_RANGE_OP: + case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP: + /* var >> const when lower(var) > upper(const) */ + hist_selec = + 1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, + hist_lower, nhist, true); + break; + + case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP: + /* compare lower bounds */ + hist_selec = + 1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, false); + break; + + case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP: + /* compare upper bounds */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_upper, + hist_upper, nhist, true); + break; + + case OID_RANGE_OVERLAPS_MULTIRANGE_OP: + case OID_MULTIRANGE_OVERLAPS_RANGE_OP: + case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP: + case OID_MULTIRANGE_CONTAINS_ELEM_OP: + + /* + * A && B <=> NOT (A << B OR A >> B). + * + * Since A << B and A >> B are mutually exclusive events we can + * sum their probabilities to find probability of (A << B OR A >> + * B). + * + * "multirange @> elem" is equivalent to "multirange && + * {[elem,elem]}". The caller already constructed the singular + * range from the element constant, so just treat it the same as + * &&. + */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, + &const_lower, hist_upper, + nhist, false); + hist_selec += + (1.0 - calc_hist_selectivity_scalar(rng_typcache, + &const_upper, hist_lower, + nhist, true)); + hist_selec = 1.0 - hist_selec; + break; + + case OID_MULTIRANGE_CONTAINS_RANGE_OP: + case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP: + hist_selec = + calc_hist_selectivity_contains(rng_typcache, &const_lower, + &const_upper, hist_lower, nhist, + lslot.values, lslot.nvalues); + break; + + case OID_MULTIRANGE_RANGE_CONTAINED_OP: + case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP: + if (const_lower.infinite) + { + /* + * Lower bound no longer matters. Just estimate the fraction + * with an upper bound <= const upper bound + */ + hist_selec = + calc_hist_selectivity_scalar(rng_typcache, &const_upper, + hist_upper, nhist, true); + } + else if (const_upper.infinite) + { + hist_selec = + 1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower, + hist_lower, nhist, false); + } + else + { + hist_selec = + calc_hist_selectivity_contained(rng_typcache, &const_lower, + &const_upper, hist_lower, nhist, + lslot.values, lslot.nvalues); + } + break; + + default: + elog(ERROR, "unknown multirange operator %u", operator); + hist_selec = -1.0; /* keep compiler quiet */ + break; + } + + free_attstatsslot(&lslot); + free_attstatsslot(&hslot); + + return hist_selec; +} + + +/* + * Look up the fraction of values less than (or equal, if 'equal' argument + * is true) a given const in a histogram of range bounds. + */ +static double +calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound, + const RangeBound *hist, int hist_nvalues, bool equal) +{ + Selectivity selec; + int index; + + /* + * Find the histogram bin the given constant falls into. Estimate + * selectivity as the number of preceding whole bins. + */ + index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal); + selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1); + + /* Adjust using linear interpolation within the bin */ + if (index >= 0 && index < hist_nvalues - 1) + selec += get_position(typcache, constbound, &hist[index], + &hist[index + 1]) / (Selectivity) (hist_nvalues - 1); + + return selec; +} + +/* + * Binary search on an array of range bounds. Returns greatest index of range + * bound in array which is less(less or equal) than given range bound. If all + * range bounds in array are greater or equal(greater) than given range bound, + * return -1. When "equal" flag is set conditions in brackets are used. + * + * This function is used in scalar operator selectivity estimation. Another + * goal of this function is to find a histogram bin where to stop + * interpolation of portion of bounds which are less or equal to given bound. + */ +static int +rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist, + int hist_length, bool equal) +{ + int lower = -1, + upper = hist_length - 1, + cmp, + middle; + + while (lower < upper) + { + middle = (lower + upper + 1) / 2; + cmp = range_cmp_bounds(typcache, &hist[middle], value); + + if (cmp < 0 || (equal && cmp == 0)) + lower = middle; + else + upper = middle - 1; + } + return lower; +} + + +/* + * Binary search on length histogram. Returns greatest index of range length in + * histogram which is less than (less than or equal) the given length value. If + * all lengths in the histogram are greater than (greater than or equal) the + * given length, returns -1. + */ +static int +length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues, + double value, bool equal) +{ + int lower = -1, + upper = length_hist_nvalues - 1, + middle; + + while (lower < upper) + { + double middleval; + + middle = (lower + upper + 1) / 2; + + middleval = DatumGetFloat8(length_hist_values[middle]); + if (middleval < value || (equal && middleval <= value)) + lower = middle; + else + upper = middle - 1; + } + return lower; +} + +/* + * Get relative position of value in histogram bin in [0,1] range. + */ +static float8 +get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1, + const RangeBound *hist2) +{ + bool has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + float8 position; + + if (!hist1->infinite && !hist2->infinite) + { + float8 bin_width; + + /* + * Both bounds are finite. Assuming the subtype's comparison function + * works sanely, the value must be finite, too, because it lies + * somewhere between the bounds. If it doesn't, arbitrarily return + * 0.5. + */ + if (value->infinite) + return 0.5; + + /* Can't interpolate without subdiff function */ + if (!has_subdiff) + return 0.5; + + /* Calculate relative position using subdiff function. */ + bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + hist2->val, + hist1->val)); + if (isnan(bin_width) || bin_width <= 0.0) + return 0.5; /* punt for NaN or zero-width bin */ + + position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + value->val, + hist1->val)) + / bin_width; + + if (isnan(position)) + return 0.5; /* punt for NaN from subdiff, Inf/Inf, etc */ + + /* Relative position must be in [0,1] range */ + position = Max(position, 0.0); + position = Min(position, 1.0); + return position; + } + else if (hist1->infinite && !hist2->infinite) + { + /* + * Lower bin boundary is -infinite, upper is finite. If the value is + * -infinite, return 0.0 to indicate it's equal to the lower bound. + * Otherwise return 1.0 to indicate it's infinitely far from the lower + * bound. + */ + return ((value->infinite && value->lower) ? 0.0 : 1.0); + } + else if (!hist1->infinite && hist2->infinite) + { + /* same as above, but in reverse */ + return ((value->infinite && !value->lower) ? 1.0 : 0.0); + } + else + { + /* + * If both bin boundaries are infinite, they should be equal to each + * other, and the value should also be infinite and equal to both + * bounds. (But don't Assert that, to avoid crashing if a user creates + * a datatype with a broken comparison function). + * + * Assume the value to lie in the middle of the infinite bounds. + */ + return 0.5; + } +} + + +/* + * Get relative position of value in a length histogram bin in [0,1] range. + */ +static double +get_len_position(double value, double hist1, double hist2) +{ + if (!isinf(hist1) && !isinf(hist2)) + { + /* + * Both bounds are finite. The value should be finite too, because it + * lies somewhere between the bounds. If it doesn't, just return + * something. + */ + if (isinf(value)) + return 0.5; + + return 1.0 - (hist2 - value) / (hist2 - hist1); + } + else if (isinf(hist1) && !isinf(hist2)) + { + /* + * Lower bin boundary is -infinite, upper is finite. Return 1.0 to + * indicate the value is infinitely far from the lower bound. + */ + return 1.0; + } + else if (isinf(hist1) && isinf(hist2)) + { + /* same as above, but in reverse */ + return 0.0; + } + else + { + /* + * If both bin boundaries are infinite, they should be equal to each + * other, and the value should also be infinite and equal to both + * bounds. (But don't Assert that, to avoid crashing unnecessarily if + * the caller messes up) + * + * Assume the value to lie in the middle of the infinite bounds. + */ + return 0.5; + } +} + +/* + * Measure distance between two range bounds. + */ +static float8 +get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2) +{ + bool has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + + if (!bound1->infinite && !bound2->infinite) + { + /* + * Neither bound is infinite, use subdiff function or return default + * value of 1.0 if no subdiff is available. + */ + if (has_subdiff) + { + float8 res; + + res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo, + typcache->rng_collation, + bound2->val, + bound1->val)); + /* Reject possible NaN result, also negative result */ + if (isnan(res) || res < 0.0) + return 1.0; + else + return res; + } + else + return 1.0; + } + else if (bound1->infinite && bound2->infinite) + { + /* Both bounds are infinite */ + if (bound1->lower == bound2->lower) + return 0.0; + else + return get_float8_infinity(); + } + else + { + /* One bound is infinite, the other is not */ + return get_float8_infinity(); + } +} + +/* + * Calculate the average of function P(x), in the interval [length1, length2], + * where P(x) is the fraction of tuples with length < x (or length <= x if + * 'equal' is true). + */ +static double +calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues, + double length1, double length2, bool equal) +{ + double frac; + double A, + B, + PA, + PB; + double pos; + int i; + double area; + + Assert(length2 >= length1); + + if (length2 < 0.0) + return 0.0; /* shouldn't happen, but doesn't hurt to check */ + + /* All lengths in the table are <= infinite. */ + if (isinf(length2) && equal) + return 1.0; + + /*---------- + * The average of a function between A and B can be calculated by the + * formula: + * + * B + * 1 / + * ------- | P(x)dx + * B - A / + * A + * + * The geometrical interpretation of the integral is the area under the + * graph of P(x). P(x) is defined by the length histogram. We calculate + * the area in a piecewise fashion, iterating through the length histogram + * bins. Each bin is a trapezoid: + * + * P(x2) + * /| + * / | + * P(x1)/ | + * | | + * | | + * ---+---+-- + * x1 x2 + * + * where x1 and x2 are the boundaries of the current histogram, and P(x1) + * and P(x1) are the cumulative fraction of tuples at the boundaries. + * + * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1) + * + * The first bin contains the lower bound passed by the caller, so we + * use linear interpolation between the previous and next histogram bin + * boundary to calculate P(x1). Likewise for the last bin: we use linear + * interpolation to calculate P(x2). For the bins in between, x1 and x2 + * lie on histogram bin boundaries, so P(x1) and P(x2) are simply: + * P(x1) = (bin index) / (number of bins) + * P(x2) = (bin index + 1 / (number of bins) + */ + + /* First bin, the one that contains lower bound */ + i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal); + if (i >= length_hist_nvalues - 1) + return 1.0; + + if (i < 0) + { + i = 0; + pos = 0.0; + } + else + { + /* interpolate length1's position in the bin */ + pos = get_len_position(length1, + DatumGetFloat8(length_hist_values[i]), + DatumGetFloat8(length_hist_values[i + 1])); + } + PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1); + B = length1; + + /* + * In the degenerate case that length1 == length2, simply return + * P(length1). This is not merely an optimization: if length1 == length2, + * we'd divide by zero later on. + */ + if (length2 == length1) + return PB; + + /* + * Loop through all the bins, until we hit the last bin, the one that + * contains the upper bound. (if lower and upper bounds are in the same + * bin, this falls out immediately) + */ + area = 0.0; + for (; i < length_hist_nvalues - 1; i++) + { + double bin_upper = DatumGetFloat8(length_hist_values[i + 1]); + + /* check if we've reached the last bin */ + if (!(bin_upper < length2 || (equal && bin_upper <= length2))) + break; + + /* the upper bound of previous bin is the lower bound of this bin */ + A = B; + PA = PB; + + B = bin_upper; + PB = (double) i / (double) (length_hist_nvalues - 1); + + /* + * Add the area of this trapezoid to the total. The point of the + * if-check is to avoid NaN, in the corner case that PA == PB == 0, + * and B - A == Inf. The area of a zero-height trapezoid (PA == PB == + * 0) is zero, regardless of the width (B - A). + */ + if (PA > 0 || PB > 0) + area += 0.5 * (PB + PA) * (B - A); + } + + /* Last bin */ + A = B; + PA = PB; + + B = length2; /* last bin ends at the query upper bound */ + if (i >= length_hist_nvalues - 1) + pos = 0.0; + else + { + if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1])) + pos = 0.0; + else + pos = get_len_position(length2, + DatumGetFloat8(length_hist_values[i]), + DatumGetFloat8(length_hist_values[i + 1])); + } + PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1); + + if (PA > 0 || PB > 0) + area += 0.5 * (PB + PA) * (B - A); + + /* + * Ok, we have calculated the area, ie. the integral. Divide by width to + * get the requested average. + * + * Avoid NaN arising from infinite / infinite. This happens at least if + * length2 is infinite. It's not clear what the correct value would be in + * that case, so 0.5 seems as good as any value. + */ + if (isinf(area) && isinf(length2)) + frac = 0.5; + else + frac = area / (length2 - length1); + + return frac; +} + +/* + * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction + * of multiranges that fall within the constant lower and upper bounds. This uses + * the histograms of range lower bounds and range lengths, on the assumption + * that the range lengths are independent of the lower bounds. + * + * The caller has already checked that constant lower and upper bounds are + * finite. + */ +static double +calc_hist_selectivity_contained(TypeCacheEntry *typcache, + const RangeBound *lower, RangeBound *upper, + const RangeBound *hist_lower, int hist_nvalues, + Datum *length_hist_values, int length_hist_nvalues) +{ + int i, + upper_index; + float8 prev_dist; + double bin_width; + double upper_bin_width; + double sum_frac; + + /* + * Begin by finding the bin containing the upper bound, in the lower bound + * histogram. Any range with a lower bound > constant upper bound can't + * match, ie. there are no matches in bins greater than upper_index. + */ + upper->inclusive = !upper->inclusive; + upper->lower = true; + upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues, + false); + + /* + * If the upper bound value is below the histogram's lower limit, there + * are no matches. + */ + if (upper_index < 0) + return 0.0; + + /* + * If the upper bound value is at or beyond the histogram's upper limit, + * start our loop at the last actual bin, as though the upper bound were + * within that bin; get_position will clamp its result to 1.0 anyway. + * (This corresponds to assuming that the data population above the + * histogram's upper limit is empty, exactly like what we just assumed for + * the lower limit.) + */ + upper_index = Min(upper_index, hist_nvalues - 2); + + /* + * Calculate upper_bin_width, ie. the fraction of the (upper_index, + * upper_index + 1) bin which is greater than upper bound of query range + * using linear interpolation of subdiff function. + */ + upper_bin_width = get_position(typcache, upper, + &hist_lower[upper_index], + &hist_lower[upper_index + 1]); + + /* + * In the loop, dist and prev_dist are the distance of the "current" bin's + * lower and upper bounds from the constant upper bound. + * + * bin_width represents the width of the current bin. Normally it is 1.0, + * meaning a full width bin, but can be less in the corner cases: start + * and end of the loop. We start with bin_width = upper_bin_width, because + * we begin at the bin containing the upper bound. + */ + prev_dist = 0.0; + bin_width = upper_bin_width; + + sum_frac = 0.0; + for (i = upper_index; i >= 0; i--) + { + double dist; + double length_hist_frac; + bool final_bin = false; + + /* + * dist -- distance from upper bound of query range to lower bound of + * the current bin in the lower bound histogram. Or to the lower bound + * of the constant range, if this is the final bin, containing the + * constant lower bound. + */ + if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0) + { + dist = get_distance(typcache, lower, upper); + + /* + * Subtract from bin_width the portion of this bin that we want to + * ignore. + */ + bin_width -= get_position(typcache, lower, &hist_lower[i], + &hist_lower[i + 1]); + if (bin_width < 0.0) + bin_width = 0.0; + final_bin = true; + } + else + dist = get_distance(typcache, &hist_lower[i], upper); + + /* + * Estimate the fraction of tuples in this bin that are narrow enough + * to not exceed the distance to the upper bound of the query range. + */ + length_hist_frac = calc_length_hist_frac(length_hist_values, + length_hist_nvalues, + prev_dist, dist, true); + + /* + * Add the fraction of tuples in this bin, with a suitable length, to + * the total. + */ + sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1); + + if (final_bin) + break; + + bin_width = 1.0; + prev_dist = dist; + } + + return sum_frac; +} + +/* + * Calculate selectivity of "var @> const" operator, ie. estimate the fraction + * of multiranges that contain the constant lower and upper bounds. This uses + * the histograms of range lower bounds and range lengths, on the assumption + * that the range lengths are independent of the lower bounds. + */ +static double +calc_hist_selectivity_contains(TypeCacheEntry *typcache, + const RangeBound *lower, const RangeBound *upper, + const RangeBound *hist_lower, int hist_nvalues, + Datum *length_hist_values, int length_hist_nvalues) +{ + int i, + lower_index; + double bin_width, + lower_bin_width; + double sum_frac; + float8 prev_dist; + + /* Find the bin containing the lower bound of query range. */ + lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues, + true); + + /* + * If the lower bound value is below the histogram's lower limit, there + * are no matches. + */ + if (lower_index < 0) + return 0.0; + + /* + * If the lower bound value is at or beyond the histogram's upper limit, + * start our loop at the last actual bin, as though the upper bound were + * within that bin; get_position will clamp its result to 1.0 anyway. + * (This corresponds to assuming that the data population above the + * histogram's upper limit is empty, exactly like what we just assumed for + * the lower limit.) + */ + lower_index = Min(lower_index, hist_nvalues - 2); + + /* + * Calculate lower_bin_width, ie. the fraction of the of (lower_index, + * lower_index + 1) bin which is greater than lower bound of query range + * using linear interpolation of subdiff function. + */ + lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index], + &hist_lower[lower_index + 1]); + + /* + * Loop through all the lower bound bins, smaller than the query lower + * bound. In the loop, dist and prev_dist are the distance of the + * "current" bin's lower and upper bounds from the constant upper bound. + * We begin from query lower bound, and walk backwards, so the first bin's + * upper bound is the query lower bound, and its distance to the query + * upper bound is the length of the query range. + * + * bin_width represents the width of the current bin. Normally it is 1.0, + * meaning a full width bin, except for the first bin, which is only + * counted up to the constant lower bound. + */ + prev_dist = get_distance(typcache, lower, upper); + sum_frac = 0.0; + bin_width = lower_bin_width; + for (i = lower_index; i >= 0; i--) + { + float8 dist; + double length_hist_frac; + + /* + * dist -- distance from upper bound of query range to current value + * of lower bound histogram or lower bound of query range (if we've + * reach it). + */ + dist = get_distance(typcache, &hist_lower[i], upper); + + /* + * Get average fraction of length histogram which covers intervals + * longer than (or equal to) distance to upper bound of query range. + */ + length_hist_frac = + 1.0 - calc_length_hist_frac(length_hist_values, + length_hist_nvalues, + prev_dist, dist, false); + + sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1); + + bin_width = 1.0; + prev_dist = dist; + } + + return sum_frac; +} diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c index 8a5953881ee..521ebaaab6d 100644 --- a/src/backend/utils/adt/pg_upgrade_support.c +++ b/src/backend/utils/adt/pg_upgrade_support.c @@ -53,6 +53,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS) } Datum +binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS) +{ + Oid typoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_mrng_pg_type_oid = typoid; + + PG_RETURN_VOID(); +} + +Datum +binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS) +{ + Oid typoid = PG_GETARG_OID(0); + + CHECK_IS_BINARY_UPGRADE; + binary_upgrade_next_mrng_array_pg_type_oid = typoid; + + PG_RETURN_VOID(); +} + +Datum binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS) { Oid reloid = PG_GETARG_OID(0); diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index 3d6b2f90935..99a93271fe1 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -26,6 +26,7 @@ #include "utils/array.h" #include "utils/builtins.h" #include "utils/rangetypes.h" +#include "utils/multirangetypes.h" /* @@ -228,6 +229,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS) } /* + * anycompatiblemultirange + * + * We may as well allow output, since multirange_out will in fact work. + */ +PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange); + +Datum +anycompatiblemultirange_out(PG_FUNCTION_ARGS) +{ + return multirange_out(fcinfo); +} + +/* + * anymultirange_in - input routine for pseudo-type ANYMULTIRANGE. + */ +Datum +anymultirange_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", "anymultirange"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +/* + * anymultirange_out - output routine for pseudo-type ANYMULTIRANGE. + * + * We may as well allow this, since multirange_out will in fact work. + */ +Datum +anymultirange_out(PG_FUNCTION_ARGS) +{ + return multirange_out(fcinfo); +} + +/* * void * * We support void_in so that PL functions can return VOID without any diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c index 01ad8bc240b..8957cc19842 100644 --- a/src/backend/utils/adt/rangetypes.c +++ b/src/backend/utils/adt/rangetypes.c @@ -43,8 +43,6 @@ #include "utils/timestamp.h" -#define RANGE_EMPTY_LITERAL "empty" - /* fn_extra cache entry for one of the range I/O functions */ typedef struct RangeIOData { @@ -957,7 +955,25 @@ range_minus(PG_FUNCTION_ARGS) { RangeType *r1 = PG_GETARG_RANGE_P(0); RangeType *r2 = PG_GETARG_RANGE_P(1); + RangeType *ret; TypeCacheEntry *typcache; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + ret = range_minus_internal(typcache, r1, r2); + if (ret) + PG_RETURN_RANGE_P(ret); + else + PG_RETURN_NULL(); +} + +RangeType * +range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2) +{ RangeBound lower1, lower2; RangeBound upper1, @@ -969,18 +985,12 @@ range_minus(PG_FUNCTION_ARGS) cmp_u1l2, cmp_u1u2; - /* Different types should be prevented by ANYRANGE matching rules */ - if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) - elog(ERROR, "range types do not match"); - - typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); - range_deserialize(typcache, r1, &lower1, &upper1, &empty1); range_deserialize(typcache, r2, &lower2, &upper2, &empty2); /* if either is empty, r1 is the correct answer */ if (empty1 || empty2) - PG_RETURN_RANGE_P(r1); + return r1; cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2); cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2); @@ -993,34 +1003,34 @@ range_minus(PG_FUNCTION_ARGS) errmsg("result of range difference would not be contiguous"))); if (cmp_l1u2 > 0 || cmp_u1l2 < 0) - PG_RETURN_RANGE_P(r1); + return r1; if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0) - PG_RETURN_RANGE_P(make_empty_range(typcache)); + return make_empty_range(typcache); if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0) { lower2.inclusive = !lower2.inclusive; lower2.lower = false; /* it will become the upper bound */ - PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false)); + return make_range(typcache, &lower1, &lower2, false); } if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0) { upper2.inclusive = !upper2.inclusive; upper2.lower = true; /* it will become the lower bound */ - PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false)); + return make_range(typcache, &upper2, &upper1, false); } elog(ERROR, "unexpected case in range_minus"); - PG_RETURN_NULL(); + return NULL; } /* * Set union. If strict is true, it is an error that the two input ranges * are not adjacent or overlapping. */ -static RangeType * +RangeType * range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2, bool strict) { @@ -1101,6 +1111,19 @@ range_intersect(PG_FUNCTION_ARGS) RangeType *r1 = PG_GETARG_RANGE_P(0); RangeType *r2 = PG_GETARG_RANGE_P(1); TypeCacheEntry *typcache; + + /* Different types should be prevented by ANYRANGE matching rules */ + if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) + elog(ERROR, "range types do not match"); + + typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); + + PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2)); +} + +RangeType * +range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2) +{ RangeBound lower1, lower2; RangeBound upper1, @@ -1110,17 +1133,11 @@ range_intersect(PG_FUNCTION_ARGS) RangeBound *result_lower; RangeBound *result_upper; - /* Different types should be prevented by ANYRANGE matching rules */ - if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2)) - elog(ERROR, "range types do not match"); - - typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1)); - range_deserialize(typcache, r1, &lower1, &upper1, &empty1); range_deserialize(typcache, r2, &lower2, &upper2, &empty2); - if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo))) - PG_RETURN_RANGE_P(make_empty_range(typcache)); + if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2)) + return make_empty_range(typcache); if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0) result_lower = &lower1; @@ -1132,9 +1149,81 @@ range_intersect(PG_FUNCTION_ARGS) else result_upper = &upper2; - PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false)); + return make_range(typcache, result_lower, result_upper, false); } +/* range, range -> range, range functions */ + +/* + * range_split_internal - if r2 intersects the middle of r1, leaving non-empty + * ranges on both sides, then return true and set output1 and output2 to the + * results of r1 - r2 (in order). Otherwise return false and don't set output1 + * or output2. Neither input range should be empty. + */ +bool +range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2, + RangeType **output1, RangeType **output2) +{ + RangeBound lower1, + lower2; + RangeBound upper1, + upper2; + bool empty1, + empty2; + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 && + range_cmp_bounds(typcache, &upper1, &upper2) > 0) + { + /* + * Need to invert inclusive/exclusive for the lower2 and upper2 + * points. They can't be infinite though. We're allowed to overwrite + * these RangeBounds since they only exist locally. + */ + lower2.inclusive = !lower2.inclusive; + lower2.lower = false; + upper2.inclusive = !upper2.inclusive; + upper2.lower = true; + + *output1 = make_range(typcache, &lower1, &lower2, false); + *output2 = make_range(typcache, &upper2, &upper1, false); + return true; + } + + return false; +} + +/* range -> range aggregate functions */ + +Datum +range_intersect_agg_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggContext; + Oid rngtypoid; + TypeCacheEntry *typcache; + RangeType *result; + RangeType *current; + + if (!AggCheckCallContext(fcinfo, &aggContext)) + elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context"); + + rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1); + if (!type_is_range(rngtypoid)) + ereport(ERROR, (errmsg("range_intersect_agg must be called with a range"))); + + typcache = range_get_typcache(fcinfo, rngtypoid); + + /* strictness ensures these are non-null */ + result = PG_GETARG_RANGE_P(0); + current = PG_GETARG_RANGE_P(1); + + result = range_intersect_internal(typcache, result, current); + PG_RETURN_RANGE_P(result); +} + + /* Btree support */ /* btree comparator */ @@ -1938,6 +2027,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1, } /* + * qsort callback for sorting ranges. + * + * Two empty ranges compare equal; an empty range sorts to the left of any + * non-empty range. Two non-empty ranges are sorted by lower bound first + * and by upper bound next. + */ +int +range_compare(const void *key1, const void *key2, void *arg) +{ + RangeType *r1 = *(RangeType **) key1; + RangeType *r2 = *(RangeType **) key2; + TypeCacheEntry *typcache = (TypeCacheEntry *) arg; + RangeBound lower1; + RangeBound upper1; + RangeBound lower2; + RangeBound upper2; + bool empty1; + bool empty2; + int cmp; + + range_deserialize(typcache, r1, &lower1, &upper1, &empty1); + range_deserialize(typcache, r2, &lower2, &upper2, &empty2); + + if (empty1 && empty2) + cmp = 0; + else if (empty1) + cmp = -1; + else if (empty2) + cmp = 1; + else + { + cmp = range_cmp_bounds(typcache, &lower1, &lower2); + if (cmp == 0) + cmp = range_cmp_bounds(typcache, &upper1, &upper2); + } + + return cmp; +} + +/* * Build an empty range value of the type indicated by the typcache entry. */ RangeType * diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c index 57413b07201..1376cf06940 100644 --- a/src/backend/utils/adt/rangetypes_typanalyze.c +++ b/src/backend/utils/adt/rangetypes_typanalyze.c @@ -30,11 +30,13 @@ #include "utils/fmgrprotos.h" #include "utils/lsyscache.h" #include "utils/rangetypes.h" +#include "utils/multirangetypes.h" static int float8_qsort_cmp(const void *a1, const void *a2); static int range_bound_qsort_cmp(const void *a1, const void *a2, void *arg); static void compute_range_stats(VacAttrStats *stats, - AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows); + AnalyzeAttrFetchFunc fetchfunc, int samplerows, + double totalrows); /* * range_typanalyze -- typanalyze function for range columns @@ -61,6 +63,33 @@ range_typanalyze(PG_FUNCTION_ARGS) } /* + * multirange_typanalyze -- typanalyze function for multirange columns + * + * We do the same analysis as for ranges, but on the smallest range that + * completely includes the multirange. + */ +Datum +multirange_typanalyze(PG_FUNCTION_ARGS) +{ + VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); + TypeCacheEntry *typcache; + Form_pg_attribute attr = stats->attr; + + /* Get information about multirange type; note column might be a domain */ + typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid)); + + if (attr->attstattarget < 0) + attr->attstattarget = default_statistics_target; + + stats->compute_stats = compute_range_stats; + stats->extra_data = typcache; + /* same as in std_typanalyze */ + stats->minrows = 300 * attr->attstattarget; + + PG_RETURN_BOOL(true); +} + +/* * Comparison function for sorting float8s, used for range lengths. */ static int @@ -98,7 +127,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows) { TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data; - bool has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + TypeCacheEntry *mltrng_typcache = NULL; + bool has_subdiff; int null_cnt = 0; int non_null_cnt = 0; int non_empty_cnt = 0; @@ -112,6 +142,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, *uppers; double total_width = 0; + if (typcache->typtype == TYPTYPE_MULTIRANGE) + { + mltrng_typcache = typcache; + typcache = typcache->rngtype; + } + else + Assert(typcache->typtype == TYPTYPE_RANGE); + has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid); + /* Allocate memory to hold range bounds and lengths of the sample ranges. */ lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows); uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows); @@ -123,6 +162,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, Datum value; bool isnull, empty; + MultirangeType *multirange; RangeType *range; RangeBound lower, upper; @@ -145,8 +185,31 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, total_width += VARSIZE_ANY(DatumGetPointer(value)); /* Get range and deserialize it for further analysis. */ - range = DatumGetRangeTypeP(value); - range_deserialize(typcache, range, &lower, &upper, &empty); + if (mltrng_typcache != NULL) + { + /* Treat multiranges like a big range without gaps. */ + multirange = DatumGetMultirangeTypeP(value); + if (!MultirangeIsEmpty(multirange)) + { + RangeBound tmp; + + multirange_get_bounds(typcache, multirange, 0, + &lower, &tmp); + multirange_get_bounds(typcache, multirange, + multirange->rangeCount - 1, + &tmp, &upper); + empty = false; + } + else + { + empty = true; + } + } + else + { + range = DatumGetRangeTypeP(value); + range_deserialize(typcache, range, &lower, &upper, &empty); + } if (!empty) { @@ -262,6 +325,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM; stats->stavalues[slot_idx] = bound_hist_values; stats->numvalues[slot_idx] = num_hist; + + /* Store ranges even if we're analyzing a multirange column */ + stats->statypid[slot_idx] = typcache->type_id; + stats->statyplen[slot_idx] = typcache->typlen; + stats->statypbyval[slot_idx] = typcache->typbyval; + stats->statypalign[slot_idx] = 'd'; + slot_idx++; } diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 204bcd03c0b..ad92636f7f1 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -2611,6 +2611,16 @@ type_is_range(Oid typid) } /* + * type_is_multirange + * Returns true if the given type is a multirange type. + */ +bool +type_is_multirange(Oid typid) +{ + return (get_typtype(typid) == TYPTYPE_MULTIRANGE); +} + +/* * get_type_category_preferred * * Given the type OID, fetch its category and preferred-type status. @@ -3308,7 +3318,7 @@ get_namespace_name_or_temp(Oid nspid) return get_namespace_name(nspid); } -/* ---------- PG_RANGE CACHE ---------- */ +/* ---------- PG_RANGE CACHES ---------- */ /* * get_range_subtype @@ -3361,6 +3371,56 @@ get_range_collation(Oid rangeOid) return InvalidOid; } +/* + * get_range_multirange + * Returns the multirange type of a given range type + * + * Returns InvalidOid if the type is not a range type. + */ +Oid +get_range_multirange(Oid rangeOid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rangeOid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp); + Oid result; + + result = rngtup->rngmultitypid; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + +/* + * get_multirange_range + * Returns the range type of a given multirange + * + * Returns InvalidOid if the type is not a multirange. + */ +Oid +get_multirange_range(Oid multirangeOid) +{ + HeapTuple tp; + + tp = SearchSysCache1(RANGEMULTIRANGE, ObjectIdGetDatum(multirangeOid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp); + Oid result; + + result = rngtup->rngtypid; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} + /* ---------- PG_INDEX CACHE ---------- */ /* diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 809b27a038f..89f08a4f43c 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -650,6 +650,18 @@ static const struct cachedesc cacheinfo[] = { }, 64 }, + {RangeRelationId, /* RANGEMULTIRANGE */ + RangeMultirangeTypidIndexId, + 1, + { + Anum_pg_range_rngmultitypid, + 0, + 0, + 0 + }, + 4 + }, + {RangeRelationId, /* RANGETYPE */ RangeTypidIndexId, 1, diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 1e331098c0d..8c97ef39557 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -287,6 +287,7 @@ static uint64 tupledesc_id_counter = INVALID_TUPLEDESC_IDENTIFIER; static void load_typcache_tupdesc(TypeCacheEntry *typentry); static void load_rangetype_info(TypeCacheEntry *typentry); +static void load_multirangetype_info(TypeCacheEntry *typentry); static void load_domaintype_info(TypeCacheEntry *typentry); static int dcs_cmp(const void *a, const void *b); static void decr_dcc_refcount(DomainConstraintCache *dcc); @@ -305,6 +306,9 @@ static void cache_record_field_properties(TypeCacheEntry *typentry); static bool range_element_has_hashing(TypeCacheEntry *typentry); static bool range_element_has_extended_hashing(TypeCacheEntry *typentry); static void cache_range_element_properties(TypeCacheEntry *typentry); +static bool multirange_element_has_hashing(TypeCacheEntry *typentry); +static bool multirange_element_has_extended_hashing(TypeCacheEntry *typentry); +static void cache_multirange_element_properties(TypeCacheEntry *typentry); static void TypeCacheRelCallback(Datum arg, Oid relid); static void TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue); static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue); @@ -557,8 +561,8 @@ lookup_type_cache(Oid type_id, int flags) * to see if the element type or column types support equality. If * not, array_eq or record_eq would fail at runtime, so we don't want * to report that the type has equality. (We can omit similar - * checking for ranges because ranges can't be created in the first - * place unless their subtypes support equality.) + * checking for ranges and multiranges because ranges can't be created + * in the first place unless their subtypes support equality.) */ if (eq_opr == ARRAY_EQ_OP && !array_element_has_equality(typentry)) @@ -595,7 +599,7 @@ lookup_type_cache(Oid type_id, int flags) /* * As above, make sure array_cmp or record_cmp will succeed; but again - * we need no special check for ranges. + * we need no special check for ranges or multiranges. */ if (lt_opr == ARRAY_LT_OP && !array_element_has_compare(typentry)) @@ -620,7 +624,7 @@ lookup_type_cache(Oid type_id, int flags) /* * As above, make sure array_cmp or record_cmp will succeed; but again - * we need no special check for ranges. + * we need no special check for ranges or multiranges. */ if (gt_opr == ARRAY_GT_OP && !array_element_has_compare(typentry)) @@ -645,7 +649,7 @@ lookup_type_cache(Oid type_id, int flags) /* * As above, make sure array_cmp or record_cmp will succeed; but again - * we need no special check for ranges. + * we need no special check for ranges or multiranges. */ if (cmp_proc == F_BTARRAYCMP && !array_element_has_compare(typentry)) @@ -695,6 +699,13 @@ lookup_type_cache(Oid type_id, int flags) !range_element_has_hashing(typentry)) hash_proc = InvalidOid; + /* + * Likewise for hash_multirange. + */ + if (hash_proc == F_HASH_MULTIRANGE && + !multirange_element_has_hashing(typentry)) + hash_proc = InvalidOid; + /* Force update of hash_proc_finfo only if we're changing state */ if (typentry->hash_proc != hash_proc) typentry->hash_proc_finfo.fn_oid = InvalidOid; @@ -737,6 +748,13 @@ lookup_type_cache(Oid type_id, int flags) !range_element_has_extended_hashing(typentry)) hash_extended_proc = InvalidOid; + /* + * Likewise for hash_multirange_extended. + */ + if (hash_extended_proc == F_HASH_MULTIRANGE_EXTENDED && + !multirange_element_has_extended_hashing(typentry)) + hash_extended_proc = InvalidOid; + /* Force update of proc finfo only if we're changing state */ if (typentry->hash_extended_proc != hash_extended_proc) typentry->hash_extended_proc_finfo.fn_oid = InvalidOid; @@ -817,6 +835,16 @@ lookup_type_cache(Oid type_id, int flags) } /* + * If requested, get information about a multirange type + */ + if ((flags & TYPECACHE_MULTIRANGE_INFO) && + typentry->rngtype == NULL && + typentry->typtype == TYPTYPE_MULTIRANGE) + { + load_multirangetype_info(typentry); + } + + /* * If requested, get information about a domain type */ if ((flags & TYPECACHE_DOMAIN_BASE_INFO) && @@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry) typentry->rngelemtype = lookup_type_cache(subtypeOid, 0); } +/* + * load_multirangetype_info --- helper routine to set up multirange type + * information + */ +static void +load_multirangetype_info(TypeCacheEntry *typentry) +{ + Oid rangetypeOid; + + rangetypeOid = get_multirange_range(typentry->type_id); + if (!OidIsValid(rangetypeOid)) + elog(ERROR, "cache lookup failed for multirange type %u", + typentry->type_id); + + typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO); +} /* * load_domaintype_info --- helper routine to set up domain constraint info @@ -1559,11 +1603,11 @@ cache_record_field_properties(TypeCacheEntry *typentry) } /* - * Likewise, some helper functions for range types. + * Likewise, some helper functions for range and multirange types. * * We can borrow the flag bits for array element properties to use for range * element properties, since those flag bits otherwise have no use in a - * range type's typcache entry. + * range or multirange type's typcache entry. */ static bool @@ -1606,6 +1650,46 @@ cache_range_element_properties(TypeCacheEntry *typentry) typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES; } +static bool +multirange_element_has_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_multirange_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) != 0; +} + +static bool +multirange_element_has_extended_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES)) + cache_multirange_element_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_ELEM_EXTENDED_HASHING) != 0; +} + +static void +cache_multirange_element_properties(TypeCacheEntry *typentry) +{ + /* load up range link if we didn't already */ + if (typentry->rngtype == NULL && + typentry->typtype == TYPTYPE_MULTIRANGE) + load_multirangetype_info(typentry); + + if (typentry->rngtype != NULL && typentry->rngtype->rngelemtype != NULL) + { + TypeCacheEntry *elementry; + + /* might need to calculate subtype's hash function properties */ + elementry = lookup_type_cache(typentry->rngtype->rngelemtype->type_id, + TYPECACHE_HASH_PROC | + TYPECACHE_HASH_EXTENDED_PROC); + if (OidIsValid(elementry->hash_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING; + if (OidIsValid(elementry->hash_extended_proc)) + typentry->flags |= TCFLAGS_HAVE_ELEM_EXTENDED_HASHING; + } + typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES; +} + /* * Make sure that RecordCacheArray and RecordIdentifierArray are large enough * to store 'typmod'. diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 9696c88f241..f6fa4ab2fb2 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -35,6 +35,7 @@ typedef struct polymorphic_actuals Oid anyelement_type; /* anyelement mapping, if known */ Oid anyarray_type; /* anyarray mapping, if known */ Oid anyrange_type; /* anyrange mapping, if known */ + Oid anymultirange_type; /* anymultirange mapping, if known */ } polymorphic_actuals; static void shutdown_MultiFuncCall(Datum arg); @@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid, static void resolve_anyelement_from_others(polymorphic_actuals *actuals); static void resolve_anyarray_from_others(polymorphic_actuals *actuals); static void resolve_anyrange_from_others(polymorphic_actuals *actuals); +static void resolve_anymultirange_from_others(polymorphic_actuals *actuals); static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, Node *call_expr); @@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals) format_type_be(range_base_type)))); actuals->anyelement_type = range_typelem; } + else if (OidIsValid(actuals->anymultirange_type)) + { + /* Use the element type based on the multirange type */ + Oid multirange_base_type; + Oid multirange_typelem; + Oid range_base_type; + Oid range_typelem; + + multirange_base_type = getBaseType(actuals->anymultirange_type); + multirange_typelem = get_multirange_range(multirange_base_type); + if (!OidIsValid(multirange_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a multirange type but type %s", + "anymultirange", + format_type_be(multirange_base_type)))); + + range_base_type = getBaseType(multirange_typelem); + range_typelem = get_range_subtype(range_base_type); + + if (!OidIsValid(range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s does not contain a range type but type %s", + "anymultirange", + format_type_be(range_base_type)))); + actuals->anyelement_type = range_typelem; + } else elog(ERROR, "could not determine polymorphic type"); } @@ -540,10 +570,53 @@ static void resolve_anyrange_from_others(polymorphic_actuals *actuals) { /* - * We can't deduce a range type from other polymorphic inputs, because - * there may be multiple range types with the same subtype. + * We can't deduce a range type from other polymorphic array or base + * types, because there may be multiple range types with the same subtype, + * but we can deduce it from a polymorphic multirange type. + */ + if (OidIsValid(actuals->anymultirange_type)) + { + /* Use the element type based on the multirange type */ + Oid multirange_base_type = getBaseType(actuals->anymultirange_type); + Oid multirange_typelem = get_multirange_range(multirange_base_type); + + if (!OidIsValid(multirange_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared %s is not a multirange type but type %s", + "anymultirange", + format_type_be(multirange_base_type)))); + actuals->anyrange_type = multirange_typelem; + } + else + elog(ERROR, "could not determine polymorphic type"); +} + +/* + * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs + */ +static void +resolve_anymultirange_from_others(polymorphic_actuals *actuals) +{ + /* + * We can't deduce a multirange type from polymorphic array or base types, + * because there may be multiple range types with the same subtype, but we + * can deduce it from a polymorphic range type. */ - elog(ERROR, "could not determine polymorphic type"); + if (OidIsValid(actuals->anyrange_type)) + { + Oid range_base_type = getBaseType(actuals->anyrange_type); + Oid multirange_typeid = get_range_multirange(range_base_type); + + if (!OidIsValid(multirange_typeid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find multirange type for data type %s", + format_type_be(actuals->anyrange_type)))); + actuals->anymultirange_type = multirange_typeid; + } + else + elog(ERROR, "could not determine polymorphic type"); } /* @@ -566,9 +639,11 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, bool have_anyelement_result = false; bool have_anyarray_result = false; bool have_anyrange_result = false; + bool have_anymultirange_result = false; bool have_anycompatible_result = false; bool have_anycompatible_array_result = false; bool have_anycompatible_range_result = false; + bool have_anycompatible_multirange_result = false; polymorphic_actuals poly_actuals; polymorphic_actuals anyc_actuals; Oid anycollation = InvalidOid; @@ -594,6 +669,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, have_polymorphic_result = true; have_anyrange_result = true; break; + case ANYMULTIRANGEOID: + have_polymorphic_result = true; + have_anymultirange_result = true; + break; case ANYCOMPATIBLEOID: case ANYCOMPATIBLENONARRAYOID: have_polymorphic_result = true; @@ -607,6 +686,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, have_polymorphic_result = true; have_anycompatible_range_result = true; break; + case ANYCOMPATIBLEMULTIRANGEOID: + have_polymorphic_result = true; + have_anycompatible_multirange_result = true; + break; default: break; } @@ -660,6 +743,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, return false; } break; + case ANYMULTIRANGEOID: + if (!OidIsValid(poly_actuals.anymultirange_type)) + { + poly_actuals.anymultirange_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(poly_actuals.anymultirange_type)) + return false; + } + break; case ANYCOMPATIBLEOID: case ANYCOMPATIBLENONARRAYOID: if (!OidIsValid(anyc_actuals.anyelement_type)) @@ -688,6 +780,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, return false; } break; + case ANYCOMPATIBLEMULTIRANGEOID: + if (!OidIsValid(anyc_actuals.anymultirange_type)) + { + anyc_actuals.anymultirange_type = + get_call_expr_argtype(call_expr, i); + if (!OidIsValid(anyc_actuals.anymultirange_type)) + return false; + } + break; default: break; } @@ -703,6 +804,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type)) resolve_anyrange_from_others(&poly_actuals); + if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type)) + resolve_anymultirange_from_others(&poly_actuals); + if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type)) resolve_anyelement_from_others(&anyc_actuals); @@ -712,6 +816,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type)) resolve_anyrange_from_others(&anyc_actuals); + if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type)) + resolve_anymultirange_from_others(&anyc_actuals); + /* * Identify the collation to use for polymorphic OUT parameters. (It'll * necessarily be the same for both anyelement and anyarray, likewise for @@ -780,6 +887,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, 0); /* no collation should be attached to a range type */ break; + case ANYMULTIRANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + poly_actuals.anymultirange_type, + -1, + 0); + /* no collation should be attached to a multirange type */ + break; case ANYCOMPATIBLEOID: case ANYCOMPATIBLENONARRAYOID: TupleDescInitEntry(tupdesc, i + 1, @@ -805,6 +920,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, 0); /* no collation should be attached to a range type */ break; + case ANYCOMPATIBLEMULTIRANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(att->attname), + anyc_actuals.anymultirange_type, + -1, + 0); + /* no collation should be attached to a multirange type */ + break; default: break; } @@ -834,9 +957,11 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, bool have_anyelement_result = false; bool have_anyarray_result = false; bool have_anyrange_result = false; + bool have_anymultirange_result = false; bool have_anycompatible_result = false; bool have_anycompatible_array_result = false; bool have_anycompatible_range_result = false; + bool have_anycompatible_multirange_result = false; polymorphic_actuals poly_actuals; polymorphic_actuals anyc_actuals; int inargno; @@ -912,6 +1037,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, argtypes[i] = poly_actuals.anyrange_type; } break; + case ANYMULTIRANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anymultirange_result = true; + } + else + { + if (!OidIsValid(poly_actuals.anymultirange_type)) + { + poly_actuals.anymultirange_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(poly_actuals.anymultirange_type)) + return false; + } + argtypes[i] = poly_actuals.anymultirange_type; + } + break; case ANYCOMPATIBLEOID: case ANYCOMPATIBLENONARRAYOID: if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) @@ -967,6 +1110,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, argtypes[i] = anyc_actuals.anyrange_type; } break; + case ANYCOMPATIBLEMULTIRANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + { + have_polymorphic_result = true; + have_anycompatible_multirange_result = true; + } + else + { + if (!OidIsValid(anyc_actuals.anymultirange_type)) + { + anyc_actuals.anymultirange_type = + get_call_expr_argtype(call_expr, inargno); + if (!OidIsValid(anyc_actuals.anymultirange_type)) + return false; + } + argtypes[i] = anyc_actuals.anymultirange_type; + } + break; default: break; } @@ -988,6 +1149,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type)) resolve_anyrange_from_others(&poly_actuals); + if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type)) + resolve_anymultirange_from_others(&poly_actuals); + if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type)) resolve_anyelement_from_others(&anyc_actuals); @@ -997,6 +1161,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type)) resolve_anyrange_from_others(&anyc_actuals); + if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type)) + resolve_anymultirange_from_others(&anyc_actuals); + /* And finally replace the output column types as needed */ for (i = 0; i < numargs; i++) { @@ -1013,6 +1180,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, case ANYRANGEOID: argtypes[i] = poly_actuals.anyrange_type; break; + case ANYMULTIRANGEOID: + argtypes[i] = poly_actuals.anymultirange_type; + break; case ANYCOMPATIBLEOID: case ANYCOMPATIBLENONARRAYOID: argtypes[i] = anyc_actuals.anyelement_type; @@ -1023,6 +1193,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, case ANYCOMPATIBLERANGEOID: argtypes[i] = anyc_actuals.anyrange_type; break; + case ANYCOMPATIBLEMULTIRANGEOID: + argtypes[i] = anyc_actuals.anymultirange_type; + break; default: break; } @@ -1052,6 +1225,7 @@ get_type_func_class(Oid typid, Oid *base_typeid) case TYPTYPE_BASE: case TYPTYPE_ENUM: case TYPTYPE_RANGE: + case TYPTYPE_MULTIRANGE: return TYPEFUNC_SCALAR; case TYPTYPE_DOMAIN: *base_typeid = typid = getBaseType(typid); |