diff options
Diffstat (limited to 'src/backend')
| -rw-r--r-- | src/backend/catalog/Makefile | 6 | ||||
| -rw-r--r-- | src/backend/catalog/pg_proc.c | 63 | ||||
| -rw-r--r-- | src/backend/catalog/pg_range.c | 136 | ||||
| -rw-r--r-- | src/backend/commands/typecmds.c | 445 | ||||
| -rw-r--r-- | src/backend/executor/functions.c | 1 | ||||
| -rw-r--r-- | src/backend/nodes/copyfuncs.c | 14 | ||||
| -rw-r--r-- | src/backend/nodes/equalfuncs.c | 12 | ||||
| -rw-r--r-- | src/backend/parser/gram.y | 7 | ||||
| -rw-r--r-- | src/backend/parser/parse_coerce.c | 180 | ||||
| -rw-r--r-- | src/backend/tcop/utility.c | 13 | ||||
| -rw-r--r-- | src/backend/utils/adt/Makefile | 4 | ||||
| -rw-r--r-- | src/backend/utils/adt/date.c | 1 | ||||
| -rw-r--r-- | src/backend/utils/adt/pseudotypes.c | 24 | ||||
| -rw-r--r-- | src/backend/utils/adt/rangetypes.c | 2153 | ||||
| -rw-r--r-- | src/backend/utils/adt/rangetypes_gist.c | 587 | ||||
| -rw-r--r-- | src/backend/utils/cache/lsyscache.c | 30 | ||||
| -rw-r--r-- | src/backend/utils/cache/syscache.c | 12 | ||||
| -rw-r--r-- | src/backend/utils/fmgr/funcapi.c | 115 |
18 files changed, 3736 insertions, 67 deletions
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 7e0b7d65a8..5a4419d3a8 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -13,8 +13,8 @@ include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \ pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ - pg_operator.o pg_proc.o pg_db_role_setting.o pg_shdepend.o pg_type.o \ - storage.o toasting.o + pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \ + pg_type.o storage.o toasting.o BKIFILES = postgres.bki postgres.description postgres.shdescription @@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_ts_parser.h pg_ts_template.h pg_extension.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h \ - pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h \ + pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 5f7d7f6b68..8378c360b9 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -91,8 +91,11 @@ ProcedureCreate(const char *procedureName, int parameterCount; int allParamCount; Oid *allParams; + char *modes = NULL; bool genericInParam = false; bool genericOutParam = false; + bool anyrangeInParam = false; + bool anyrangeOutParam = false; bool internalInParam = false; bool internalOutParam = false; Oid variadicType = InvalidOid; @@ -152,6 +155,24 @@ ProcedureCreate(const char *procedureName, allParams = parameterTypes->values; } + if (parameterModes != PointerGetDatum(NULL)) + { + /* + * We expect the array to be a 1-D CHAR array; verify that. We don't + * need to use deconstruct_array() since the array data is just going + * to look like a C array of char values. + */ + ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes); + + if (ARR_NDIM(modesArray) != 1 || + ARR_DIMS(modesArray)[0] != allParamCount || + ARR_HASNULL(modesArray) || + ARR_ELEMTYPE(modesArray) != CHAROID) + elog(ERROR, "parameterModes is not a 1-D char array"); + modes = (char *) ARR_DATA_PTR(modesArray); + } + + /* * Do not allow polymorphic return type unless at least one input argument * is polymorphic. Also, do not allow return type INTERNAL unless at @@ -161,6 +182,9 @@ ProcedureCreate(const char *procedureName, { switch (parameterTypes->values[i]) { + case ANYRANGEOID: + anyrangeInParam = true; + /* FALL THROUGH */ case ANYARRAYOID: case ANYELEMENTOID: case ANYNONARRAYOID: @@ -177,14 +201,17 @@ ProcedureCreate(const char *procedureName, { for (i = 0; i < allParamCount; i++) { - /* - * We don't bother to distinguish input and output params here, so - * if there is, say, just an input INTERNAL param then we will - * still set internalOutParam. This is OK since we don't really - * care. - */ + if (modes == NULL || + (modes[i] != PROARGMODE_OUT && + modes[i] != PROARGMODE_INOUT && + modes[i] != PROARGMODE_TABLE)) + continue; + switch (allParams[i]) { + case ANYRANGEOID: + anyrangeOutParam = true; + /* FALL THROUGH */ case ANYARRAYOID: case ANYELEMENTOID: case ANYNONARRAYOID: @@ -205,6 +232,13 @@ ProcedureCreate(const char *procedureName, errmsg("cannot determine result data type"), errdetail("A function returning a polymorphic type must have at least one polymorphic argument."))); + if ((returnType == ANYRANGEOID || anyrangeOutParam) && + !anyrangeInParam) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine result data type"), + errdetail("A function returning ANYRANGE must have at least one ANYRANGE argument."))); + if ((returnType == INTERNALOID || internalOutParam) && !internalInParam) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), @@ -225,24 +259,9 @@ ProcedureCreate(const char *procedureName, procedureName, format_type_be(parameterTypes->values[0])))); - if (parameterModes != PointerGetDatum(NULL)) + if (modes != NULL) { /* - * We expect the array to be a 1-D CHAR array; verify that. We don't - * need to use deconstruct_array() since the array data is just going - * to look like a C array of char values. - */ - ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes); - char *modes; - - if (ARR_NDIM(modesArray) != 1 || - ARR_DIMS(modesArray)[0] != allParamCount || - ARR_HASNULL(modesArray) || - ARR_ELEMTYPE(modesArray) != CHAROID) - elog(ERROR, "parameterModes is not a 1-D char array"); - modes = (char *) ARR_DATA_PTR(modesArray); - - /* * Only the last input parameter can be variadic; if it is, save its * element type. Errors here are just elog since caller should have * checked this already. diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c new file mode 100644 index 0000000000..3b9003395e --- /dev/null +++ b/src/backend/catalog/pg_range.c @@ -0,0 +1,136 @@ +/*------------------------------------------------------------------------- + * + * pg_range.c + * routines to support manipulation of the pg_range relation + * + * Copyright (c) 2006-2010, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/catalog/pg_range.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_range.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/tqual.h" +#include "utils/rel.h" + +/* + * RangeCreate + * Create an entry in pg_range. + */ +void +RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, + Oid rangeSubOpclass, RegProcedure rangeCanonical, + RegProcedure rangeSubDiff) +{ + Relation pg_range; + Datum values[Natts_pg_range]; + bool nulls[Natts_pg_range]; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + pg_range = heap_open(RangeRelationId, RowExclusiveLock); + + memset(nulls, 0, Natts_pg_range * sizeof(bool)); + + values[Anum_pg_range_rngtypid - 1] = ObjectIdGetDatum(rangeTypeOid); + values[Anum_pg_range_rngsubtype - 1] = ObjectIdGetDatum(rangeSubType); + values[Anum_pg_range_rngcollation - 1] = ObjectIdGetDatum(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); + + tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls); + simple_heap_insert(pg_range, tup); + CatalogUpdateIndexes(pg_range, tup); + heap_freetuple(tup); + + /* record dependencies */ + + myself.classId = TypeRelationId; + myself.objectId = rangeTypeOid; + myself.objectSubId = 0; + + referenced.classId = TypeRelationId; + referenced.objectId = rangeSubType; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + referenced.classId = OperatorClassRelationId; + referenced.objectId = rangeSubOpclass; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + if (OidIsValid(rangeCollation)) + { + referenced.classId = CollationRelationId; + referenced.objectId = rangeCollation; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + if (OidIsValid(rangeCanonical)) + { + referenced.classId = ProcedureRelationId; + referenced.objectId = rangeCanonical; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + if (OidIsValid(rangeSubDiff)) + { + referenced.classId = ProcedureRelationId; + referenced.objectId = rangeSubDiff; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + heap_close(pg_range, RowExclusiveLock); +} + + +/* + * RangeDelete + * Remove the pg_range entry. + */ +void +RangeDelete(Oid rangeTypeOid) +{ + Relation pg_range; + ScanKeyData key[1]; + SysScanDesc scan; + HeapTuple tup; + + pg_range = heap_open(RangeRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_range_rngtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(rangeTypeOid)); + + scan = systable_beginscan(pg_range, RangeTypidIndexId, true, + SnapshotNow, 1, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + simple_heap_delete(pg_range, &tup->t_self); + } + + systable_endscan(scan); + + heap_close(pg_range, RowExclusiveLock); +} diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 5069c5759e..91488bbbf5 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -42,7 +42,11 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_enum.h" +#include "catalog/pg_language.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_proc_fn.h" +#include "catalog/pg_range.h" #include "catalog/pg_type.h" #include "catalog/pg_type_fn.h" #include "commands/defrem.h" @@ -63,6 +67,7 @@ #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/rangetypes.h" #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" @@ -87,6 +92,9 @@ static Oid findTypeSendFunction(List *procname, Oid typeOid); static Oid findTypeTypmodinFunction(List *procname); static Oid findTypeTypmodoutFunction(List *procname); static Oid findTypeAnalyzeFunction(List *procname, Oid typeOid); +static Oid findRangeCanonicalFunction(List *procname, Oid typeOid); +static Oid findRangeSubOpclass(List *procname, Oid typeOid); +static Oid findRangeSubtypeDiffFunction(List *procname, Oid typeOid); static void validateDomainConstraint(Oid domainoid, char *ccbin); static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode); static void checkDomainOwner(HeapTuple tup); @@ -95,6 +103,8 @@ static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, int typMod, Constraint *constr, char *domainName); +static void makeRangeConstructor(char *name, Oid namespace, Oid rettype, + Oid subtype); /* @@ -643,6 +653,14 @@ RemoveTypeById(Oid typeOid) if (((Form_pg_type) GETSTRUCT(tup))->typtype == TYPTYPE_ENUM) EnumValuesDelete(typeOid); + /* + * If it is a range type, delete the pg_range entries too; we + * don't bother with making dependency entries for those, so it + * has to be done "by hand" here. + */ + if (((Form_pg_type) GETSTRUCT(tup))->typtype == TYPTYPE_RANGE) + RangeDelete(typeOid); + ReleaseSysCache(tup); heap_close(relation, RowExclusiveLock); @@ -724,14 +742,15 @@ DefineDomain(CreateDomainStmt *stmt) basetypeoid = HeapTupleGetOid(typeTup); /* - * Base type must be a plain base type, another domain or an enum. Domains - * over pseudotypes would create a security hole. Domains over composite - * types might be made to work in the future, but not today. + * Base type must be a plain base type, another domain, an enum or a range + * type. Domains over pseudotypes would create a security hole. Domains + * over composite types might be made to work in the future, but not today. */ typtype = baseType->typtype; if (typtype != TYPTYPE_BASE && typtype != TYPTYPE_DOMAIN && - typtype != TYPTYPE_ENUM) + typtype != TYPTYPE_ENUM && + typtype != TYPTYPE_RANGE) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("\"%s\" is not a valid base type for a domain", @@ -1135,6 +1154,327 @@ DefineEnum(CreateEnumStmt *stmt) } /* + * DefineRange + * Registers a new range type. + */ +void +DefineRange(CreateRangeStmt *stmt) +{ + char *typeName; + char *rangeArrayName; + Oid typeNamespace; + Oid typoid; + Oid rangeArrayOid; + List *parameters = stmt->params; + + ListCell *lc; + List *rangeSubOpclassName = NIL; + List *rangeSubtypeDiffName = NIL; + List *rangeCollationName = NIL; + Oid rangeCollation = InvalidOid; + regproc rangeAnalyze = InvalidOid; + Oid rangeSubtype = InvalidOid; + regproc rangeSubOpclass = InvalidOid; + regproc rangeCanonical = InvalidOid; + regproc rangeSubtypeDiff = InvalidOid; + + AclResult aclresult; + + /* Convert list of names to a name and namespace */ + typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName, + &typeName); + + /* Check we have creation rights in target namespace */ + aclresult = pg_namespace_aclcheck(typeNamespace, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_NAMESPACE, + get_namespace_name(typeNamespace)); + + /* + * Look to see if type already exists (presumably as a shell; if not, + * TypeCreate will complain). + */ + typoid = GetSysCacheOid2(TYPENAMENSP, + CStringGetDatum(typeName), + ObjectIdGetDatum(typeNamespace)); + + /* + * 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(typoid) && get_typisdefined(typoid)) + { + if (moveArrayTypeName(typoid, typeName, typeNamespace)) + typoid = InvalidOid; + } + + /* + * If it doesn't exist, create it as a shell, so that the OID is known for + * use in the I/O function definitions. + */ + if (!OidIsValid(typoid)) + { + typoid = TypeShellMake(typeName, typeNamespace, GetUserId()); + /* Make new shell type visible for modification below */ + CommandCounterIncrement(); + + /* + * If the command was a parameterless CREATE TYPE, we're done --- + * creating the shell type was all we're supposed to do. + */ + if (parameters == NIL) + return; + } + else + { + /* Complain if dummy CREATE TYPE and entry already exists */ + if (parameters == NIL) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("type \"%s\" already exists", typeName))); + } + + foreach(lc, stmt->params) + { + DefElem *defel = lfirst(lc); + + if (pg_strcasecmp(defel->defname, "subtype") == 0) + { + if (OidIsValid(rangeSubtype)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeSubtype = typenameTypeId(NULL, defGetTypeName(defel)); + } + else if (pg_strcasecmp(defel->defname, "canonical") == 0) + { + if (OidIsValid(rangeCanonical)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeCanonical = findRangeCanonicalFunction( + defGetQualifiedName(defel), typoid); + } + else if (pg_strcasecmp(defel->defname, "collation") == 0) + { + if (rangeCollationName != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeCollationName = defGetQualifiedName(defel); + } + else if (pg_strcasecmp(defel->defname, "analyze") == 0) + { + if (OidIsValid(rangeAnalyze)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeAnalyze = findTypeAnalyzeFunction(defGetQualifiedName(defel), + typoid); + } + else if (pg_strcasecmp(defel->defname, "subtype_opclass") == 0) + { + if (rangeSubOpclassName != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeSubOpclassName = defGetQualifiedName(defel); + } + else if (pg_strcasecmp(defel->defname, "subtype_diff") == 0) + { + if (rangeSubtypeDiffName != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + rangeSubtypeDiffName = defGetQualifiedName(defel); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("type attribute \"%s\" not recognized", + defel->defname))); + continue; + } + } + + if (!OidIsValid(rangeSubtype)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("type attribute \"subtype\" is required"))); + + if (type_is_collatable(rangeSubtype)) + { + if (rangeCollationName == NIL) + rangeCollation = get_typcollation(rangeSubtype); + else + rangeCollation = get_collation_oid(rangeCollationName, false); + } + else if (rangeCollationName != NIL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("range collation provided but subtype does not support collation"))); + + rangeSubOpclass = findRangeSubOpclass(rangeSubOpclassName, rangeSubtype); + + if (rangeSubtypeDiffName != NIL) + rangeSubtypeDiff = findRangeSubtypeDiffFunction( + rangeSubtypeDiffName, rangeSubtype); + + rangeArrayOid = AssignTypeArrayOid(); + + /* Create the pg_type entry */ + typoid = + TypeCreate(InvalidOid, /* no predetermined type OID */ + typeName, /* type name */ + typeNamespace, /* namespace */ + InvalidOid, /* relation oid (n/a here) */ + 0, /* relation kind (ditto) */ + GetUserId(), /* owner's ID */ + -1, /* internal size */ + TYPTYPE_RANGE, /* type-type (range type) */ + TYPCATEGORY_RANGE, /* type-category (range type) */ + false, /* range types are never preferred */ + DEFAULT_TYPDELIM, /* array element delimiter */ + F_RANGE_IN, /* input procedure */ + F_RANGE_OUT, /* output procedure */ + F_RANGE_RECV, /* receive procedure */ + F_RANGE_SEND, /* send procedure */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + rangeAnalyze, /* analyze procedure - default */ + InvalidOid, /* element type ID */ + false, /* this is not an array type */ + rangeArrayOid, /* array type we are about to create */ + InvalidOid, /* base type ID (only for domains) */ + NULL, /* never a default type value */ + NULL, /* binary default isn't sent either */ + false, /* never passed by value */ + 'i', /* int alignment */ + 'x', /* TOAST strategy always plain */ + -1, /* typMod (Domains only) */ + 0, /* Array dimensions of typbasetype */ + false, /* Type NOT NULL */ + InvalidOid); /* typcollation */ + + /* create the entry in pg_range */ + RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass, + rangeCanonical, rangeSubtypeDiff); + + /* + * Create the array type that goes with it. + */ + rangeArrayName = makeArrayTypeName(typeName, typeNamespace); + + TypeCreate(rangeArrayOid, /* force assignment of this type OID */ + rangeArrayName, /* type name */ + typeNamespace, /* 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 */ + InvalidOid, /* analyze procedure - default */ + typoid, /* 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 */ + 'i', /* align 'i' */ + 'x', /* ARRAY is always toastable */ + -1, /* typMod (Domains only) */ + 0, /* Array dimensions of typbasetype */ + false, /* Type NOT NULL */ + InvalidOid); /* typcollation */ + + pfree(rangeArrayName); + + makeRangeConstructor(typeName, typeNamespace, typoid, rangeSubtype); +} + +/* + * Because there may exist several range types over one subtype, the range type + * can't be determined from the subtype. This means that constructors can't be + * polymorphic, and so we must generate a new constructor for every range type + * defined. + * + * We actually define 4 functions with 0 through 3 arguments. This is just to + * offer more convenience for the user. + */ +static void +makeRangeConstructor(char *name, Oid namespace, Oid rangeOid, Oid subtype) +{ + ObjectAddress referenced; + Oid constructorArgTypes[3]; + int i; + + referenced.classId = TypeRelationId; + referenced.objectId = rangeOid; + referenced.objectSubId = 0; + + constructorArgTypes[0] = subtype; + constructorArgTypes[1] = subtype; + constructorArgTypes[2] = TEXTOID; + + for (i = 0; i < 4; i++) + { + oidvector *constructorArgTypesVector; + ObjectAddress myself; + Oid procOid; + char *prosrc[4] = { "range_constructor0", + "range_constructor1", + "range_constructor2", + "range_constructor3"}; + + constructorArgTypesVector = buildoidvector(constructorArgTypes, i); + + procOid = ProcedureCreate( + name, /* name */ + namespace, /* namespace */ + false, /* replace */ + false, /* return set */ + rangeOid, /* return type */ + INTERNALlanguageId, /* language */ + F_FMGR_INTERNAL_VALIDATOR, /* language validator */ + prosrc[i], /* prosrc */ + NULL, /* probin */ + false, /* agg */ + false, /* window */ + false, /* security definer */ + false, /* strict */ + PROVOLATILE_IMMUTABLE, /* volatility */ + constructorArgTypesVector, /* param types */ + PointerGetDatum(NULL), /* allParameterTypes */ + PointerGetDatum(NULL), /* parameterModes */ + PointerGetDatum(NULL), /* parameterNames */ + NIL, /* parameterDefaults */ + PointerGetDatum(NULL), /* proconfig */ + 1.0, /* procost */ + 0.0); /* prorows */ + + /* + * Make the constructor internally-dependent on the range type so that + * the user doesn't have to treat them as separate objects. + */ + myself.classId = ProcedureRelationId; + myself.objectId = procOid; + myself.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL); + } +} + +/* * AlterEnum * Adds a new label to an existing enum. */ @@ -1450,6 +1790,103 @@ findTypeAnalyzeFunction(List *procname, Oid typeOid) } /* + * Find named btree opclass for subtype, or default btree opclass if + * opcname is NIL. This will be used for comparing values of subtype. + */ +static Oid +findRangeSubOpclass(List *opcname, Oid subtype) +{ + Oid opcid; + + if (opcname == NIL) + { + opcid = GetDefaultOpClass(subtype, BTREE_AM_OID); + if (!OidIsValid(opcid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("data type %s has no default operator class for access method \"btree\"", + format_type_be(subtype)), + errhint("You must specify an operator class for the data type or define a default operator class for the data type."))); + } + return opcid; + } + + opcid = get_opclass_oid(BTREE_AM_OID, opcname, false); + + return opcid; +} + +/* + * Used to find a range's 'canonical' function. + */ +static Oid +findRangeSubtypeDiffFunction(List *procname, Oid typeOid) +{ + Oid argList[2]; + Oid procOid; + + argList[0] = typeOid; + argList[1] = typeOid; + + procOid = LookupFuncName(procname, 2, argList, true); + + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 2, NIL, argList)))); + + if (get_func_rettype(procOid) != FLOAT8OID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("range subtype diff function %s must return type \"float8\"", + func_signature_string(procname, 2, NIL, argList)))); + + if (func_volatile(procOid) != PROVOLATILE_IMMUTABLE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("range subtype diff function %s must be immutable", + func_signature_string(procname, 2, NIL, argList)))); + + return procOid; +} + +/* + * Used to find a range's 'canonical' function. + */ +static Oid +findRangeCanonicalFunction(List *procname, Oid typeOid) +{ + Oid argList[1]; + Oid procOid; + + argList[0] = typeOid; + + procOid = LookupFuncName(procname, 1, argList, true); + + if (!OidIsValid(procOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(procname, 1, NIL, argList)))); + + if (get_func_rettype(procOid) != typeOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("range canonical function %s must return range type", + NameListToString(procname)))); + + if (func_volatile(procOid) != PROVOLATILE_IMMUTABLE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("range canonical function %s must be immutable", + func_signature_string(procname, 1, NIL, argList)))); + + return procOid; +} + +/* * AssignTypeArrayOid * * Pre-assign the type's array OID for use in pg_type.typarray diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 398bc40c49..45ca5ec4c7 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -1352,6 +1352,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, if (fn_typtype == TYPTYPE_BASE || fn_typtype == TYPTYPE_DOMAIN || fn_typtype == TYPTYPE_ENUM || + fn_typtype == TYPTYPE_RANGE || rettype == VOIDOID) { /* diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 24ac5295f6..63958c3afc 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3055,6 +3055,17 @@ _copyCreateEnumStmt(CreateEnumStmt *from) return newnode; } +static CreateRangeStmt * +_copyCreateRangeStmt(CreateRangeStmt *from) +{ + CreateRangeStmt *newnode = makeNode(CreateRangeStmt); + + COPY_NODE_FIELD(typeName); + COPY_NODE_FIELD(params); + + return newnode; +} + static AlterEnumStmt * _copyAlterEnumStmt(AlterEnumStmt *from) { @@ -4297,6 +4308,9 @@ copyObject(void *from) case T_CreateEnumStmt: retval = _copyCreateEnumStmt(from); break; + case T_CreateRangeStmt: + retval = _copyCreateRangeStmt(from); + break; case T_AlterEnumStmt: retval = _copyAlterEnumStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 4052a9a1fc..f3a34a12e2 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1439,6 +1439,15 @@ _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b) } static bool +_equalCreateRangeStmt(CreateRangeStmt *a, CreateRangeStmt *b) +{ + COMPARE_NODE_FIELD(typeName); + COMPARE_NODE_FIELD(params); + + return true; +} + +static bool _equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b) { COMPARE_NODE_FIELD(typeName); @@ -2826,6 +2835,9 @@ equal(void *a, void *b) case T_CreateEnumStmt: retval = _equalCreateEnumStmt(a, b); break; + case T_CreateRangeStmt: + retval = _equalCreateRangeStmt(a, b); + break; case T_AlterEnumStmt: retval = _equalAlterEnumStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index f0a4b0efa4..c135465fbd 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -4336,6 +4336,13 @@ DefineStmt: n->vals = $7; $$ = (Node *)n; } + | CREATE TYPE_P any_name AS RANGE definition + { + CreateRangeStmt *n = makeNode(CreateRangeStmt); + n->typeName = $3; + n->params = $6; + $$ = (Node *)n; + } | CREATE TEXT_P SEARCH PARSER any_name definition { DefineStmt *n = makeNode(DefineStmt); diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 127818abde..a461ac9aef 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -158,7 +158,8 @@ coerce_type(ParseState *pstate, Node *node, return node; } if (targetTypeId == ANYARRAYOID || - targetTypeId == ANYENUMOID) + targetTypeId == ANYENUMOID || + targetTypeId == ANYRANGEOID) { /* * Assume can_coerce_type verified that implicit coercion is okay. @@ -1275,9 +1276,11 @@ coerce_to_common_type(ParseState *pstate, Node *node, * 1) All arguments declared ANYARRAY must have matching datatypes, * and must in fact be varlena arrays. * 2) All arguments declared ANYELEMENT must have matching datatypes. - * 3) If there are arguments of both ANYELEMENT and ANYARRAY, make sure - * the actual ANYELEMENT datatype is in fact the element type for - * the actual ANYARRAY datatype. + * 3) If there are arguments of both ANYELEMENT and ANYARRAY, make sure the + * actual ANYELEMENT datatype is in fact the element type for the actual + * ANYARRAY datatype. Similarly, if there are arguments of both ANYELEMENT + * and ANYRANGE, make sure the actual ANYELEMENT datatype is in fact the + * subtype for the actual ANYRANGE type. * 4) ANYENUM is treated the same as ANYELEMENT except that if it is used * (alone or in combination with plain ANYELEMENT), we add the extra * condition that the ANYELEMENT type must be an enum. @@ -1311,6 +1314,8 @@ check_generic_type_consistency(Oid *actual_arg_types, Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; Oid array_typelem; + Oid range_typeid = InvalidOid; + Oid range_typelem; bool have_anyelement = false; bool have_anynonarray = false; bool have_anyenum = false; @@ -1348,6 +1353,15 @@ check_generic_type_consistency(Oid *actual_arg_types, return false; array_typeid = actual_type; } + else if (decl_type == ANYRANGEOID) + { + if (actual_type == UNKNOWNOID) + continue; + actual_type = getBaseType(actual_type); + if (OidIsValid(range_typeid) && actual_type != range_typeid) + return false; + range_typeid = actual_type; + } } /* Get the element type based on the array type, if we have one */ @@ -1393,6 +1407,27 @@ check_generic_type_consistency(Oid *actual_arg_types, return false; } + /* Get the element type based on the range type, if we have one */ + if (OidIsValid(range_typeid)) + { + range_typelem = get_range_subtype(range_typeid); + if (!OidIsValid(range_typelem)) + return false; /* should be a range, but isn't */ + + 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; + } + } + /* Looks valid */ return true; } @@ -1416,23 +1451,28 @@ check_generic_type_consistency(Oid *actual_arg_types, * if it is declared as a polymorphic type: * * 1) If return type is ANYARRAY, and any argument is ANYARRAY, use the - * argument's actual type as the function's return type. - * 2) If return type is ANYARRAY, no argument is ANYARRAY, but any argument - * is ANYELEMENT, use the actual type of the argument to determine - * the function's return type, i.e. the element type's corresponding - * array type. + * argument's actual type as the function's return type. Similarly, if + * return type is ANYRANGE, and any argument is ANYRANGE, use the argument's + * actual type as the function's return type. + * 2) If return type is ANYARRAY, no argument is ANYARRAY, but any argument is + * ANYELEMENT, use the actual type of the argument to determine the + * function's return type, i.e. the element type's corresponding array + * type. Note: similar behavior does not exist for ANYRANGE, because it's + * impossble to determine the range type from the subtype alone.\ * 3) If return type is ANYARRAY, no argument is ANYARRAY or ANYELEMENT, - * generate an ERROR. This condition is prevented by CREATE FUNCTION - * and is therefore not expected here. + * generate an ERROR. Similarly, if the return type is ANYRANGE, and no + * argument is ANYRANGE or ANYELEMENT, generate an error. These conditions + * are prevented by CREATE FUNCTION and is therefore not expected here. * 4) If return type is ANYELEMENT, and any argument is ANYELEMENT, use the * argument's actual type as the function's return type. - * 5) If return type is ANYELEMENT, no argument is ANYELEMENT, but any - * argument is ANYARRAY, use the actual type of the argument to determine - * the function's return type, i.e. the array type's corresponding - * element type. - * 6) If return type is ANYELEMENT, no argument is ANYARRAY or ANYELEMENT, - * generate an ERROR. This condition is prevented by CREATE FUNCTION - * and is therefore not expected here. + * 5) If return type is ANYELEMENT, no argument is ANYELEMENT, but any argument + * is ANYARRAY or ANYRANGE, use the actual type of the argument to determine + * the function's return type; i.e. the array type's corresponding element + * type or the range type's corresponding subtype (or both, in which case + * they must match). + * 6) If return type is ANYELEMENT, no argument is ANYARRAY, ANYRANGE, or + * ANYELEMENT, generate an ERROR. This condition is prevented by CREATE + * FUNCTION and is therefore not expected here. * 7) ANYENUM is treated the same as ANYELEMENT except that if it is used * (alone or in combination with plain ANYELEMENT), we add the extra * condition that the ANYELEMENT type must be an enum. @@ -1441,9 +1481,10 @@ check_generic_type_consistency(Oid *actual_arg_types, * (This is a no-op if used in combination with ANYARRAY or ANYENUM, but * is an extra restriction if not.) * - * Domains over arrays match ANYARRAY arguments, and are immediately flattened - * to their base type. (In particular, if the return type is also ANYARRAY, - * we'll set it to the base type not the domain type.) + * Domains over arrays or ranges match ANYARRAY or ANYRANGE arguments, + * respectively, and are immediately flattened to their base type. (In + * particular, if the return type is also ANYARRAY or ANYRANGE, we'll set it to + * the base type not the domain type.) * * When allow_poly is false, we are not expecting any of the actual_arg_types * to be polymorphic, and we should not return a polymorphic result type @@ -1473,7 +1514,9 @@ enforce_generic_type_consistency(Oid *actual_arg_types, bool have_unknowns = false; Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; + Oid range_typeid = InvalidOid; Oid array_typelem; + Oid range_typelem; bool have_anyelement = (rettype == ANYELEMENTOID || rettype == ANYNONARRAYOID || rettype == ANYENUMOID); @@ -1534,6 +1577,26 @@ enforce_generic_type_consistency(Oid *actual_arg_types, format_type_be(actual_type)))); array_typeid = actual_type; } + else if (decl_type == ANYRANGEOID) + { + have_generics = true; + if (actual_type == UNKNOWNOID) + { + have_unknowns = true; + continue; + } + if (allow_poly && decl_type == actual_type) + continue; /* no new information here */ + actual_type = getBaseType(actual_type); /* flatten domains */ + if (OidIsValid(range_typeid) && actual_type != range_typeid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("arguments declared \"anyrange\" are not all alike"), + errdetail("%s versus %s", + format_type_be(range_typeid), + format_type_be(actual_type)))); + range_typeid = actual_type; + } } /* @@ -1579,6 +1642,34 @@ enforce_generic_type_consistency(Oid *actual_arg_types, format_type_be(elem_typeid)))); } } + /* Get the element type based on the range type, if we have one */ + else if (OidIsValid(range_typeid)) + { + range_typelem = get_range_subtype(range_typeid); + if (!OidIsValid(range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyrange\" is not a range but type %s", + 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 \"anyrange\" is not consistent with argument declared \"anyelement\""), + errdetail("%s versus %s", + format_type_be(range_typeid), + format_type_be(elem_typeid)))); + } + } else if (!OidIsValid(elem_typeid)) { if (allow_poly) @@ -1645,6 +1736,17 @@ enforce_generic_type_consistency(Oid *actual_arg_types, } declared_arg_types[j] = array_typeid; } + else if (decl_type == ANYRANGEOID) + { + if (!OidIsValid(range_typeid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find range type for data type %s", + format_type_be(elem_typeid)))); + } + declared_arg_types[j] = range_typeid; + } } } @@ -1663,6 +1765,19 @@ enforce_generic_type_consistency(Oid *actual_arg_types, return array_typeid; } + /* if we return ANYRANGE use the appropriate argument type */ + if (rettype == ANYRANGEOID) + { + if (!OidIsValid(range_typeid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find range type for data type %s", + format_type_be(elem_typeid)))); + } + return range_typeid; + } + /* if we return ANYELEMENT use the appropriate argument type */ if (rettype == ANYELEMENTOID || rettype == ANYNONARRAYOID || @@ -1711,7 +1826,8 @@ resolve_generic_type(Oid declared_type, } else if (context_declared_type == ANYELEMENTOID || context_declared_type == ANYNONARRAYOID || - context_declared_type == ANYENUMOID) + context_declared_type == ANYENUMOID || + context_declared_type == ANYRANGEOID) { /* Use the array type corresponding to actual type */ Oid array_typeid = get_array_type(context_actual_type); @@ -1726,7 +1842,8 @@ resolve_generic_type(Oid declared_type, } else if (declared_type == ANYELEMENTOID || declared_type == ANYNONARRAYOID || - declared_type == ANYENUMOID) + declared_type == ANYENUMOID || + declared_type == ANYRANGEOID) { if (context_declared_type == ANYARRAYOID) { @@ -1741,6 +1858,18 @@ resolve_generic_type(Oid declared_type, format_type_be(context_base_type)))); return array_typelem; } + else if (context_declared_type == ANYRANGEOID) + { + /* Use the element type corresponding to actual type */ + Oid range_typelem = get_range_subtype(context_actual_type); + + if (!OidIsValid(range_typelem)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("argument declared \"anyrange\" is not a range but type %s", + format_type_be(context_actual_type)))); + return range_typelem; + } else if (context_declared_type == ANYELEMENTOID || context_declared_type == ANYNONARRAYOID || context_declared_type == ANYENUMOID) @@ -1854,6 +1983,11 @@ IsBinaryCoercible(Oid srctype, Oid targettype) if (type_is_enum(srctype)) return true; + /* Also accept any range type as coercible to ANYRANGE */ + if (targettype == ANYRANGEOID) + if (type_is_range(srctype)) + return true; + /* Also accept any composite type as coercible to RECORD */ if (targettype == RECORDOID) if (ISCOMPLEX(srctype)) diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index a74019a0d6..5b0633398c 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree) case T_CreateTrigStmt: case T_CompositeTypeStmt: case T_CreateEnumStmt: + case T_CreateRangeStmt: case T_AlterEnumStmt: case T_ViewStmt: case T_DropCastStmt: @@ -870,6 +871,10 @@ standard_ProcessUtility(Node *parsetree, DefineEnum((CreateEnumStmt *) parsetree); break; + case T_CreateRangeStmt: + DefineRange((CreateRangeStmt *) parsetree); + break; + case T_AlterEnumStmt: /* ALTER TYPE (enum) */ /* @@ -1854,6 +1859,10 @@ CreateCommandTag(Node *parsetree) tag = "CREATE TYPE"; break; + case T_CreateRangeStmt: + tag = "CREATE TYPE"; + break; + case T_AlterEnumStmt: tag = "ALTER TYPE"; break; @@ -2401,6 +2410,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_CreateRangeStmt: + lev = LOGSTMT_DDL; + break; + case T_AlterEnumStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index ce28abd9fe..5f968b011f 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -20,8 +20,8 @@ OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \ enum.o float.o format_type.o \ geo_ops.o geo_selfuncs.o int.o int8.o like.o lockfuncs.o \ misc.o nabstime.o name.o numeric.o numutils.o \ - oid.o oracle_compat.o pseudotypes.o rowtypes.o \ - regexp.o regproc.o ruleutils.o selfuncs.o \ + oid.o oracle_compat.o pseudotypes.o rangetypes.o rangetypes_gist.o \ + rowtypes.o regexp.o regproc.o ruleutils.o selfuncs.o \ tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \ network.o mac.o inet_cidr_ntop.o inet_net_pton.o \ ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \ diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index b06faf0c72..271b57a830 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -889,7 +889,6 @@ date_timestamp(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMP(result); } - /* timestamp_date() * Convert timestamp to date data type. */ diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index ddb1bd2b71..3b78c36a7e 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -25,6 +25,7 @@ #include "libpq/pqformat.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/rangetypes.h" /* @@ -187,6 +188,29 @@ anyenum_out(PG_FUNCTION_ARGS) return enum_out(fcinfo); } +/* + * anyrange_in - input routine for pseudo-type ANYRANGE. + */ +Datum +anyrange_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type anyrange"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +/* + * anyrange_out - output routine for pseudo-type ANYRANGE. + * + * We may as well allow this, since range_out will in fact work. + */ +Datum +anyrange_out(PG_FUNCTION_ARGS) +{ + return range_out(fcinfo); +} /* * void_in - input routine for pseudo-type VOID. diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c new file mode 100644 index 0000000000..6327d33f3a --- /dev/null +++ b/src/backend/utils/adt/rangetypes.c @@ -0,0 +1,2153 @@ +/*------------------------------------------------------------------------- + * + * rangetypes.c + * I/O functions, operators, and support functions for range types + * + * Copyright (c) 2006-2011, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/hash.h" +#include "access/nbtree.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_range.h" +#include "catalog/pg_type.h" +#include "fmgr.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/fmgroids.h" +#include "utils/int8.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" +#include "utils/syscache.h" +#include "utils/timestamp.h" +#include "utils/typcache.h" + +#define TYPE_IS_PACKABLE(typlen, typstorage) \ + (typlen == -1 && typstorage != 'p') + +/* flags */ +#define RANGE_EMPTY 0x01 +#define RANGE_LB_INC 0x02 +#define RANGE_LB_NULL 0x04 /* NOT USED */ +#define RANGE_LB_INF 0x08 +#define RANGE_UB_INC 0x10 +#define RANGE_UB_NULL 0x20 /* NOT USED */ +#define RANGE_UB_INF 0x40 + +#define RANGE_HAS_LBOUND(flags) (!(flags & (RANGE_EMPTY | \ + RANGE_LB_NULL | \ + RANGE_LB_INF))) + +#define RANGE_HAS_UBOUND(flags) (!(flags & (RANGE_EMPTY | \ + RANGE_UB_NULL | \ + RANGE_UB_INF))) + +#define RANGE_EMPTY_LITERAL "empty" + +static void range_parse(char *input_str, char *flags, char **lbound_str, + char **ubound_str); +static char *range_parse_bound(char *string, char *ptr, char **bound_str, + bool *infinite); +static char *range_deparse(char flags, char *lbound_str, char *ubound_str); +static char *range_bound_escape(char *in_str); +static bool range_contains_internal(FunctionCallInfo fcinfo, RangeType *r1, + RangeType *r2); +static Size datum_compute_size(Size sz, Datum datum, bool typbyval, + char typalign, int16 typlen, char typstorage); +static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval, + char typalign, int16 typlen, char typstorage); + +/* + *---------------------------------------------------------- + * I/O FUNCTIONS + *---------------------------------------------------------- + */ + +Datum +range_in(PG_FUNCTION_ARGS) +{ + char *input_str = PG_GETARG_CSTRING(0); + Oid rngtypoid = PG_GETARG_OID(1); + Oid typmod = PG_GETARG_INT32(2); + + char flags; + Datum range; + char *lbound_str; + char *ubound_str; + + regproc subInput; + FmgrInfo subInputFn; + Oid ioParam; + + RangeTypeInfo rngtypinfo; + RangeBound lower; + RangeBound upper; + + if (rngtypoid == ANYRANGEOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type anyrange"))); + + range_gettypinfo(fcinfo, rngtypoid, &rngtypinfo); + + /* parse */ + range_parse(input_str, &flags, &lbound_str, &ubound_str); + + /* input */ + getTypeInputInfo(rngtypinfo.subtype, &subInput, &ioParam); + fmgr_info(subInput, &subInputFn); + + lower.rngtypid = rngtypoid; + lower.infinite = (flags & RANGE_LB_INF) != 0; + lower.inclusive = (flags & RANGE_LB_INC) != 0; + lower.lower = true; + upper.rngtypid = rngtypoid; + upper.infinite = (flags & RANGE_UB_INF) != 0; + upper.inclusive = (flags & RANGE_UB_INC) != 0; + upper.lower = false; + + if (RANGE_HAS_LBOUND(flags)) + lower.val = InputFunctionCall(&subInputFn, lbound_str, + ioParam, typmod); + if (RANGE_HAS_UBOUND(flags)) + upper.val = InputFunctionCall(&subInputFn, ubound_str, + ioParam, typmod); + + /* serialize and canonicalize */ + range = make_range(fcinfo, &lower, &upper, flags & RANGE_EMPTY); + + PG_RETURN_RANGE(range); +} + +Datum +range_out(PG_FUNCTION_ARGS) +{ + RangeType *range = PG_GETARG_RANGE(0); + + regproc subOutput; + FmgrInfo subOutputFn; + bool isVarlena; + + char flags = 0; + char *lbound_str = NULL; + char *ubound_str = NULL; + char *output_str; + + bool empty; + + RangeTypeInfo rngtypinfo; + RangeBound lower; + RangeBound upper; + + /* deserialize */ + range_deserialize(fcinfo, range, &lower, &upper, &empty); + + if (lower.rngtypid != upper.rngtypid) + elog(ERROR, "range types do not match"); + + range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); + + if (empty) + flags |= RANGE_EMPTY; + + flags |= (lower.inclusive) ? RANGE_LB_INC : 0; + flags |= (lower.infinite) ? RANGE_LB_INF : 0; + flags |= (upper.inclusive) ? RANGE_UB_INC : 0; + flags |= (upper.infinite) ? RANGE_UB_INF : 0; + + /* output */ + getTypeOutputInfo(rngtypinfo.subtype, &subOutput, &isVarlena); + fmgr_info(subOutput, &subOutputFn); + + if (RANGE_HAS_LBOUND(flags)) + lbound_str = OutputFunctionCall(&subOutputFn, lower.val); + if (RANGE_HAS_UBOUND(flags)) + ubound_str = OutputFunctionCall(&subOutputFn, upper.val); + + /* deparse */ + output_str = range_deparse(flags, lbound_str, ubound_str); + + PG_RETURN_CSTRING(output_str); +} + +/* + * Binary representation: The first byte is the flags, then 4 bytes are the + * range type Oid, then the lower bound (if present) then the upper bound (if + * present). Each bound is represented by a 4-byte length header and the binary + * representation of that bound (as returned by a call to the send function for + * the subtype). + */ + +Datum +range_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid rngtypid = PG_GETARG_OID(1); + int32 typmod = PG_GETARG_INT32(2); + Oid subrecv; + Oid ioparam; + Datum range; + char flags; + RangeBound lower; + RangeBound upper; + + RangeTypeInfo rngtypinfo; + + flags = (unsigned char) pq_getmsgbyte(buf); + + range_gettypinfo(fcinfo, rngtypid, &rngtypinfo); + + getTypeBinaryInputInfo(rngtypinfo.subtype, &subrecv, &ioparam); + + if (RANGE_HAS_LBOUND(flags)) + { + uint32 bound_len = pq_getmsgint(buf, 4); + const char *bound_data = pq_getmsgbytes(buf, bound_len); + StringInfoData bound_buf; + + initStringInfo(&bound_buf); + appendBinaryStringInfo(&bound_buf, bound_data, bound_len); + + lower.val = OidReceiveFunctionCall(subrecv, + &bound_buf, + ioparam, + typmod); + pfree(bound_buf.data); + } + else + lower.val = (Datum) 0; + + if (RANGE_HAS_UBOUND(flags)) + { + uint32 bound_len = pq_getmsgint(buf, 4); + const char *bound_data = pq_getmsgbytes(buf, bound_len); + StringInfoData bound_buf; + + initStringInfo(&bound_buf); + appendBinaryStringInfo(&bound_buf, bound_data, bound_len); + + upper.val = OidReceiveFunctionCall(subrecv, + &bound_buf, + ioparam, + typmod); + pfree(bound_buf.data); + } + else + upper.val = (Datum) 0; + + pq_getmsgend(buf); + + lower.rngtypid = rngtypid; + lower.infinite = (flags & RANGE_LB_INF) != 0; + lower.inclusive = (flags & RANGE_LB_INC) != 0; + lower.lower = true; + upper.rngtypid = rngtypid; + upper.infinite = (flags & RANGE_UB_INF) != 0; + upper.inclusive = (flags & RANGE_UB_INC) != 0; + upper.lower = false; + + /* serialize and canonicalize */ + range = make_range(fcinfo, &lower, &upper, flags & RANGE_EMPTY); + + /* + * XXX if the subtype is pass-by-val, we should pfree the upper and + * lower bounds here. + */ + + PG_RETURN_RANGE(range); +} + +Datum +range_send(PG_FUNCTION_ARGS) +{ + RangeType *range = PG_GETARG_RANGE(0); + StringInfo buf = makeStringInfo(); + char flags = 0; + RangeBound lower; + RangeBound upper; + bool empty; + Oid subsend; + bool typIsVarlena; + + RangeTypeInfo rngtypinfo; + + pq_begintypsend(buf); + + range_deserialize(fcinfo, range, &lower, &upper, &empty); + + if (empty) + flags |= RANGE_EMPTY; + + flags |= (lower.inclusive) ? RANGE_LB_INC : 0; + flags |= (lower.infinite) ? RANGE_LB_INF : 0; + flags |= (upper.inclusive) ? RANGE_UB_INC : 0; + flags |= (upper.infinite) ? RANGE_UB_INF : 0; + + range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); + + getTypeBinaryOutputInfo(rngtypinfo.subtype, + &subsend, &typIsVarlena); + + pq_sendbyte(buf, flags); + + if (RANGE_HAS_LBOUND(flags)) + { + Datum bound = PointerGetDatum( + OidSendFunctionCall(subsend, lower.val)); + uint32 bound_len = VARSIZE(bound) - VARHDRSZ; + char *bound_data = VARDATA(bound); + + pq_sendint(buf, bound_len, 4); + pq_sendbytes(buf, bound_data, bound_len); + } + + if (RANGE_HAS_UBOUND(flags)) + { + Datum bound = PointerGetDatum( + OidSendFunctionCall(subsend, upper.val)); + uint32 bound_len = VARSIZE(bound) - VARHDRSZ; + char *bound_data = VARDATA(bound); + + pq_sendint(buf, bound_len, 4); + pq_sendbytes(buf, bound_data, bound_len); + } + + PG_RETURN_BYTEA_P(pq_endtypsend(buf)); +} + + +/* + *---------------------------------------------------------- + * GENERIC FUNCTIONS + *---------------------------------------------------------- + */ + +Datum +range_constructor0(PG_FUNCTION_ARGS) +{ + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + RangeBound lower; + RangeBound upper; + + lower.rngtypid = rngtypid; + lower.val = (Datum) 0; + lower.inclusive = false; + lower.infinite = false; + lower.lower = true; + + upper.rngtypid = rngtypid; + upper.val = (Datum) 0; + upper.inclusive = false; + upper.infinite = false; + upper.lower = false; + + range = DatumGetRangeType(make_range(fcinfo, &lower, &upper, true)); + + PG_RETURN_RANGE(range); +} + +Datum +range_constructor1(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + RangeBound lower; + RangeBound upper; + + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("argument must not be NULL"))); + + lower.rngtypid = rngtypid; + lower.val = arg1; + lower.inclusive = true; + lower.infinite = false; + lower.lower = true; + + upper.rngtypid = rngtypid; + upper.val = arg1; + upper.inclusive = true; + upper.infinite = false; + upper.lower = false; + + range = DatumGetRangeType(make_range(fcinfo, &lower, &upper, false)); + + PG_RETURN_RANGE(range); +} + +Datum +range_constructor2(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + RangeBound lower; + RangeBound upper; + char flags; + + flags = range_parse_flags(RANGE_DEFAULT_FLAGS); + + lower.rngtypid = rngtypid; + lower.val = PG_ARGISNULL(0) ? (Datum)0 : arg1; + lower.inclusive = flags & RANGE_LB_INC; + lower.infinite = PG_ARGISNULL(0); + lower.lower = true; + + upper.rngtypid = rngtypid; + upper.val = PG_ARGISNULL(1) ? (Datum)0 : arg2; + upper.inclusive = flags & RANGE_UB_INC; + upper.infinite = PG_ARGISNULL(1); + upper.lower = false; + + range = DatumGetRangeType(make_range(fcinfo, &lower, &upper, false)); + + PG_RETURN_RANGE(range); +} + +Datum +range_constructor3(PG_FUNCTION_ARGS) +{ + Datum arg1 = PG_GETARG_DATUM(0); + Datum arg2 = PG_GETARG_DATUM(1); + Oid rngtypid = get_fn_expr_rettype(fcinfo->flinfo); + RangeType *range; + RangeBound lower; + RangeBound upper; + char flags; + + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("flags argument must not be NULL"))); + flags = range_parse_flags(text_to_cstring(PG_GETARG_TEXT_P(2))); + + lower.rngtypid = rngtypid; + lower.val = PG_ARGISNULL(0) ? (Datum)0 : arg1; + lower.inclusive = flags & RANGE_LB_INC; + lower.infinite = PG_ARGISNULL(0); + lower.lower = true; + + upper.rngtypid = rngtypid; + upper.val = PG_ARGISNULL(1) ? (Datum)0 : arg2; + upper.inclusive = flags & RANGE_UB_INC; + upper.infinite = PG_ARGISNULL(1); + upper.lower = false; + + range = DatumGetRangeType(make_range(fcinfo, &lower, &upper, false)); + + PG_RETURN_RANGE(range); +} + +/* range -> subtype */ +Datum +range_lower(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + if (empty) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("empty range has no lower bound"))); + if (lower.infinite) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range lower bound is infinite"))); + + PG_RETURN_DATUM(lower.val); +} + +Datum +range_upper(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + if (empty) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("empty range has no upper bound"))); + if (upper.infinite) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range upper bound is infinite"))); + + PG_RETURN_DATUM(upper.val); +} + + +/* range -> bool */ +Datum +range_empty(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(empty); +} + +Datum +range_lower_inc(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(lower.inclusive); +} + +Datum +range_upper_inc(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(upper.inclusive); +} + +Datum +range_lower_inf(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(lower.infinite); +} + +Datum +range_upper_inf(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r1, &lower, &upper, &empty); + + PG_RETURN_BOOL(upper.infinite); +} + + +/* range, range -> bool */ +Datum +range_eq(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 && empty2) + PG_RETURN_BOOL(true); + if (empty1 != empty2) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) != 0) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) != 0) + PG_RETURN_BOOL(false); + + PG_RETURN_BOOL(true); +} + +Datum +range_ne(PG_FUNCTION_ARGS) +{ + bool eq = DatumGetBool(range_eq(fcinfo)); + + PG_RETURN_BOOL(!eq); +} + +Datum +range_contains_elem(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2; + Datum val = PG_GETARG_DATUM(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + + lower2.rngtypid = lower1.rngtypid; + lower2.inclusive = true; + lower2.infinite = false; + lower2.lower = true; + lower2.val = val; + + upper2.rngtypid = lower1.rngtypid; + upper2.inclusive = true; + upper2.infinite = false; + upper2.lower = false; + upper2.val = val; + + r2 = DatumGetRangeType(make_range(fcinfo, &lower2, &upper2, false)); + + PG_RETURN_BOOL(range_contains_internal(fcinfo, r1, r2)); +} + +Datum +range_contains(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + PG_RETURN_BOOL(range_contains_internal(fcinfo, r1, r2)); +} + +Datum +elem_contained_by_range(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(1); + RangeType *r2; + Datum val = PG_GETARG_DATUM(0); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + + lower2.rngtypid = lower1.rngtypid; + lower2.inclusive = true; + lower2.infinite = false; + lower2.lower = true; + lower2.val = val; + + upper2.rngtypid = lower1.rngtypid; + upper2.inclusive = true; + upper2.infinite = false; + upper2.lower = false; + upper2.val = val; + + r2 = DatumGetRangeType(make_range(fcinfo, &lower2, &upper2, false)); + + PG_RETURN_BOOL(range_contains_internal(fcinfo, r1, r2)); +} + +Datum +range_contained_by(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + PG_RETURN_BOOL(range_contains_internal(fcinfo, r2, r1)); +} + +Datum +range_before(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("input range is empty"))); + + if (range_cmp_bounds(fcinfo, &upper1, &lower2) < 0) + PG_RETURN_BOOL(true); + else + PG_RETURN_BOOL(false); +} + +Datum +range_after(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("input range is empty"))); + + if (range_cmp_bounds(fcinfo, &lower1, &upper2) > 0) + PG_RETURN_BOOL(true); + else + PG_RETURN_BOOL(false); +} + +Datum range_adjacent(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeTypeInfo rngtypinfo; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("undefined for empty ranges"))); + + /* + * For two ranges to be adjacent, the lower boundary of one range + * has to match the upper boundary of the other. However, the + * inclusivity of those two boundaries must also be different. + * + * The semantics for range_cmp_bounds aren't quite what we need + * here, so we do the comparison more directly. + */ + + range_gettypinfo(fcinfo, lower1.rngtypid, &rngtypinfo); + + if (lower1.inclusive != upper2.inclusive) + { + if (DatumGetInt32(FunctionCall2Coll(&rngtypinfo.cmpFn, + rngtypinfo.collation, + lower1.val, upper2.val)) == 0) + PG_RETURN_BOOL(true); + } + + if (upper1.inclusive != lower2.inclusive) + { + if (DatumGetInt32(FunctionCall2Coll(&rngtypinfo.cmpFn, + rngtypinfo.collation, + upper1.val, lower2.val)) == 0) + PG_RETURN_BOOL(true); + } + + PG_RETURN_BOOL(false); +} + +Datum +range_overlaps(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) >= 0 && + range_cmp_bounds(fcinfo, &lower1, &upper2) <= 0) + PG_RETURN_BOOL(true); + + if (range_cmp_bounds(fcinfo, &lower2, &lower1) >= 0 && + range_cmp_bounds(fcinfo, &lower2, &upper1) <= 0) + PG_RETURN_BOOL(true); + + PG_RETURN_BOOL(false); +} + +Datum +range_overleft(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) <= 0) + PG_RETURN_BOOL(true); + + PG_RETURN_BOOL(false); +} + +Datum +range_overright(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + PG_RETURN_BOOL(false); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) >= 0) + PG_RETURN_BOOL(true); + + PG_RETURN_BOOL(false); +} + + +/* range, range -> range */ +Datum +range_minus(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + int cmp_l1l2, cmp_l1u2, cmp_u1l2, cmp_u1u2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 || empty2) + PG_RETURN_RANGE(r1); + + cmp_l1l2 = range_cmp_bounds(fcinfo, &lower1, &lower2); + cmp_l1u2 = range_cmp_bounds(fcinfo, &lower1, &upper2); + cmp_u1l2 = range_cmp_bounds(fcinfo, &upper1, &lower2); + cmp_u1u2 = range_cmp_bounds(fcinfo, &upper1, &upper2); + + if (cmp_l1l2 < 0 && cmp_u1u2 > 0) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("result range is not contiguous"))); + + if (cmp_l1u2 > 0 || cmp_u1l2 < 0) + PG_RETURN_RANGE(r1); + + if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0) + PG_RETURN_RANGE(make_empty_range(fcinfo, lower1.rngtypid)); + + 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(make_range(fcinfo, &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(make_range(fcinfo, &upper2, &upper1, false)); + } + + elog(ERROR, "unexpected error in range_minus"); + PG_RETURN_VOID(); +} + +Datum +range_union(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + RangeBound *result_lower; + RangeBound *result_upper; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (empty1) + PG_RETURN_RANGE(r2); + if (empty2) + PG_RETURN_RANGE(r1); + + if (!DatumGetBool(range_overlaps(fcinfo)) && + !DatumGetBool(range_adjacent(fcinfo))) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("result range is not contiguous"))); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) < 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) > 0) + result_upper = &upper1; + else + result_upper = &upper2; + + PG_RETURN_RANGE(make_range(fcinfo, result_lower, result_upper, false)); +} + +Datum +range_intersect(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + RangeBound *result_lower; + RangeBound *result_upper; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo))) + PG_RETURN_RANGE(make_empty_range(fcinfo, lower1.rngtypid)); + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) >= 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) <= 0) + result_upper = &upper1; + else + result_upper = &upper2; + + PG_RETURN_RANGE(make_range(fcinfo, result_lower, result_upper, false)); +} + +/* Btree support */ + +Datum +range_cmp(PG_FUNCTION_ARGS) +{ + RangeType *r1 = PG_GETARG_RANGE(0); + RangeType *r2 = PG_GETARG_RANGE(1); + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + int cmp; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty1 && empty2) + PG_RETURN_INT32(0); + else if (empty1) + PG_RETURN_INT32(-1); + else if (empty2) + PG_RETURN_INT32(1); + + if ((cmp = range_cmp_bounds(fcinfo, &lower1, &lower2)) != 0) + PG_RETURN_INT32(cmp); + + PG_RETURN_INT32(range_cmp_bounds(fcinfo, &upper1, &upper2)); +} + +Datum +range_lt(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + PG_RETURN_BOOL(cmp < 0); +} + +Datum +range_le(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +range_ge(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + PG_RETURN_BOOL(cmp >= 0); +} + +Datum +range_gt(PG_FUNCTION_ARGS) +{ + int cmp = range_cmp(fcinfo); + PG_RETURN_BOOL(cmp > 0); +} + +/* Hash support */ +Datum +hash_range(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE(0); + RangeBound lower; + RangeBound upper; + bool empty; + char flags = 0; + uint32 lower_hash = 0; + uint32 upper_hash = 0; + uint32 result = 0; + + RangeTypeInfo rngtypinfo; + + TypeCacheEntry *typentry; + Oid subtype; + FunctionCallInfoData locfcinfo; + + + range_deserialize(fcinfo, r, &lower, &upper, &empty); + + if (lower.rngtypid != upper.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty) + flags |= RANGE_EMPTY; + + flags |= (lower.inclusive) ? RANGE_LB_INC : 0; + flags |= (lower.infinite) ? RANGE_LB_INF : 0; + flags |= (upper.inclusive) ? RANGE_UB_INC : 0; + flags |= (upper.infinite) ? RANGE_UB_INF : 0; + + range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); + subtype = rngtypinfo.subtype; + + /* + * We arrange to look up the hash function only once per series of + * calls, assuming the subtype doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being + * used as an index support function. + */ + typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (typentry == NULL || typentry->type_id != subtype) + { + typentry = lookup_type_cache(subtype, TYPECACHE_HASH_PROC_FINFO); + if (!OidIsValid(typentry->hash_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(subtype)))); + fcinfo->flinfo->fn_extra = (void *) typentry; + } + + /* + * Apply the hash function to each bound (the hash function shouldn't + * care about the collation). + */ + InitFunctionCallInfoData(locfcinfo, &typentry->hash_proc_finfo, 1, + InvalidOid, NULL, NULL); + + if (RANGE_HAS_LBOUND(flags)) + { + locfcinfo.arg[0] = lower.val; + locfcinfo.argnull[0] = false; + locfcinfo.isnull = false; + lower_hash = DatumGetUInt32(FunctionCallInvoke(&locfcinfo)); + } + if (RANGE_HAS_UBOUND(flags)) + { + locfcinfo.arg[0] = upper.val; + locfcinfo.argnull[0] = false; + locfcinfo.isnull = false; + upper_hash = DatumGetUInt32(FunctionCallInvoke(&locfcinfo)); + } + + result = hash_uint32((uint32) flags); + result ^= lower_hash; + result = (result << 1) | (result >> 31); + result ^= upper_hash; + + PG_RETURN_INT32(result); +} + +/* + *---------------------------------------------------------- + * CANONICAL FUNCTIONS + * + * Functions for specific built-in range types. + *---------------------------------------------------------- + */ + +Datum +int4range_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE(0); + + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE(r); + + if (!lower.infinite && !lower.inclusive) + { + lower.val = DirectFunctionCall2(int4pl, lower.val, Int32GetDatum(1)); + lower.inclusive = true; + } + + if (!upper.infinite && upper.inclusive) + { + upper.val = DirectFunctionCall2(int4pl, upper.val, Int32GetDatum(1)); + upper.inclusive = false; + } + + PG_RETURN_RANGE(range_serialize(fcinfo, &lower, &upper, false)); +} + +Datum +int8range_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE(0); + + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE(r); + + if (!lower.infinite && !lower.inclusive) + { + lower.val = DirectFunctionCall2(int8pl, lower.val, Int64GetDatum(1)); + lower.inclusive = true; + } + + if (!upper.infinite && upper.inclusive) + { + upper.val = DirectFunctionCall2(int8pl, upper.val, Int64GetDatum(1)); + upper.inclusive = false; + } + + PG_RETURN_RANGE(range_serialize(fcinfo, &lower, &upper, false)); +} + +Datum +daterange_canonical(PG_FUNCTION_ARGS) +{ + RangeType *r = PG_GETARG_RANGE(0); + + RangeBound lower; + RangeBound upper; + bool empty; + + range_deserialize(fcinfo, r, &lower, &upper, &empty); + + if (empty) + PG_RETURN_RANGE(r); + + if (!lower.infinite && !lower.inclusive) + { + lower.val = DirectFunctionCall2(date_pli, lower.val, Int32GetDatum(1)); + lower.inclusive = true; + } + + if (!upper.infinite && upper.inclusive) + { + upper.val = DirectFunctionCall2(date_pli, upper.val, Int32GetDatum(1)); + upper.inclusive = false; + } + + PG_RETURN_RANGE(range_serialize(fcinfo, &lower, &upper, false)); +} + +/* + *---------------------------------------------------------- + * SUBTYPE_DIFF FUNCTIONS + * + * Functions for specific built-in range types. + *---------------------------------------------------------- + */ + +Datum +int4range_subdiff(PG_FUNCTION_ARGS) +{ + int32 v1 = PG_GETARG_INT32(0); + int32 v2 = PG_GETARG_INT32(1); + + PG_RETURN_FLOAT8((float8)(v1-v2)); +} + +Datum +int8range_subdiff(PG_FUNCTION_ARGS) +{ + int64 v1 = PG_GETARG_INT64(0); + int64 v2 = PG_GETARG_INT64(1); + + PG_RETURN_FLOAT8((float8)(v1-v2)); +} + +Datum +daterange_subdiff(PG_FUNCTION_ARGS) +{ + int32 v1 = PG_GETARG_INT32(0); + int32 v2 = PG_GETARG_INT32(1); + + PG_RETURN_FLOAT8((float8)(v1-v2)); +} + +Datum +numrange_subdiff(PG_FUNCTION_ARGS) +{ + Datum v1 = PG_GETARG_DATUM(0); + Datum v2 = PG_GETARG_DATUM(1); + Datum numresult; + float8 floatresult; + + numresult = DirectFunctionCall2(numeric_sub, v1, v2); + + floatresult = DatumGetFloat8( + DirectFunctionCall1(numeric_float8, numresult)); + + PG_RETURN_FLOAT8(floatresult); +} + +Datum +tsrange_subdiff(PG_FUNCTION_ARGS) +{ + Timestamp v1 = PG_GETARG_TIMESTAMP(0); + Timestamp v2 = PG_GETARG_TIMESTAMP(1); + float8 result; + +#ifdef HAVE_INT64_TIMESTAMP + result = ((float8)(v1-v2)) / USECS_PER_SEC; +#else + result = timestamp; +#endif + + PG_RETURN_FLOAT8(result); +} + +Datum +tstzrange_subdiff(PG_FUNCTION_ARGS) +{ + Timestamp v1 = PG_GETARG_TIMESTAMP(0); + Timestamp v2 = PG_GETARG_TIMESTAMP(1); + float8 result; + +#ifdef HAVE_INT64_TIMESTAMP + result = ((float8)(v1-v2)) / USECS_PER_SEC; +#else + result = timestamp; +#endif + + PG_RETURN_FLOAT8(result); +} + +/* + *---------------------------------------------------------- + * SUPPORT FUNCTIONS + * + * These functions aren't in pg_proc, but are useful if + * defining new generic range functions in C. + *---------------------------------------------------------- + */ + +/* + * Serialized format is: + * + * 4 bytes: Range type Oid + * Lower boundary, if any, aligned according to subtype's typalign + * Upper boundary, if any, aligned according to subtype's typalign + * 1 byte for flags + * + * This representation is chosen to be compact when the boundary + * values need to be MAXALIGNed. A palloc chunk always starts out + * MAXALIGNed, and the first 4 bytes will be the length header (range + * types are always variable-length), then the next 4 bytes will be + * the range type Oid. That leaves the first boundary item MAXALIGNed + * without the need for padding. + * + * However, it requires a slightly odd deserialization strategy, + * because we have to read the flags byte before we know whether to + * read a boundary value. + */ + +/* + * This serializes a range, but does not canonicalize it. This should + * only be called by a canonicalization function. + */ +Datum +range_serialize(FunctionCallInfo fcinfo, RangeBound *lower, RangeBound *upper, + bool empty) +{ + Datum range; + size_t msize; + Pointer ptr; + int16 typlen; + char typalign; + bool typbyval; + char typstorage; + char flags = 0; + + RangeTypeInfo rngtypinfo; + + if (lower->rngtypid != upper->rngtypid) + elog(ERROR, "range types do not match"); + + range_gettypinfo(fcinfo, lower->rngtypid, &rngtypinfo); + + typlen = rngtypinfo.subtyplen; + typalign = rngtypinfo.subtypalign; + typbyval = rngtypinfo.subtypbyval; + typstorage = rngtypinfo.subtypstorage; + + if (empty) + flags |= RANGE_EMPTY; + else if (range_cmp_bounds(fcinfo, lower, upper) > 0) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("range lower bound must be less than or equal to range upper bound"))); + + flags |= (lower->inclusive) ? RANGE_LB_INC : 0; + flags |= (lower->infinite) ? RANGE_LB_INF : 0; + flags |= (upper->inclusive) ? RANGE_UB_INC : 0; + flags |= (upper->infinite) ? RANGE_UB_INF : 0; + + msize = VARHDRSZ; + msize += sizeof(Oid); + + if (RANGE_HAS_LBOUND(flags)) + { + msize = datum_compute_size(msize, lower->val, typbyval, typalign, + typlen, typstorage); + } + + if (RANGE_HAS_UBOUND(flags)) + { + msize = datum_compute_size(msize, upper->val, typbyval, typalign, + typlen, typstorage); + } + + msize += sizeof(char); + + ptr = palloc0(msize); + range = (Datum) ptr; + + ptr += VARHDRSZ; + + memcpy(ptr, &lower->rngtypid, sizeof(Oid)); + ptr += sizeof(Oid); + + if (RANGE_HAS_LBOUND(flags)) + { + Assert(lower->lower); + ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen, + typstorage); + } + + if (RANGE_HAS_UBOUND(flags)) + { + Assert(!upper->lower); + ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen, + typstorage); + } + + memcpy(ptr, &flags, sizeof(char)); + ptr += sizeof(char); + + SET_VARSIZE(range, msize); + PG_RETURN_RANGE(range); +} + +void +range_deserialize(FunctionCallInfo fcinfo, RangeType *range, RangeBound *lower, + RangeBound *upper, bool *empty) +{ + Pointer ptr = VARDATA(range); + char typalign; + int16 typlen; + int16 typbyval; + char flags; + Oid rngtypid; + Datum lbound; + Datum ubound; + Pointer flags_ptr; + + RangeTypeInfo rngtypinfo; + + memset(lower, 0, sizeof(RangeBound)); + memset(upper, 0, sizeof(RangeBound)); + + /* peek at last byte to read the flag byte */ + flags_ptr = ptr + VARSIZE(range) - VARHDRSZ - 1; + memcpy(&flags, flags_ptr, sizeof(char)); + + memcpy(&rngtypid, ptr, sizeof(Oid)); + ptr += sizeof(Oid); + + if (rngtypid == ANYRANGEOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot output a value of type anyrange"))); + + range_gettypinfo(fcinfo, rngtypid, &rngtypinfo); + + typalign = rngtypinfo.subtypalign; + typlen = rngtypinfo.subtyplen; + typbyval = rngtypinfo.subtypbyval; + + if (RANGE_HAS_LBOUND(flags)) + { + ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + lbound = fetch_att(ptr, typbyval, typlen); + ptr = (Pointer) att_addlength_datum(ptr, typlen, PointerGetDatum(ptr)); + if (typlen == -1) + lbound = PointerGetDatum(PG_DETOAST_DATUM(lbound)); + } + else + lbound = (Datum) 0; + + if (RANGE_HAS_UBOUND(flags)) + { + ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + ubound = fetch_att(ptr, typbyval, typlen); + ptr = (Pointer) att_addlength_datum(ptr, typlen, PointerGetDatum(ptr)); + if (typlen == -1) + ubound = PointerGetDatum(PG_DETOAST_DATUM(ubound)); + } + else + ubound = (Datum) 0; + + *empty = flags & RANGE_EMPTY; + + lower->rngtypid = rngtypid; + lower->val = lbound; + lower->inclusive = flags & RANGE_LB_INC; + lower->infinite = flags & RANGE_LB_INF; + lower->lower = true; + + upper->rngtypid = rngtypid; + upper->val = ubound; + upper->inclusive = flags & RANGE_UB_INC; + upper->infinite = flags & RANGE_UB_INF; + upper->lower = false; +} + +/* + * This both serializes and caonicalizes (if applicable) the + * range. This should be used by most callers. + */ +Datum +make_range(FunctionCallInfo fcinfo, RangeBound *lower, RangeBound *upper, + bool empty) +{ + Datum range; + + RangeTypeInfo rngtypinfo; + + range_gettypinfo(fcinfo, lower->rngtypid, &rngtypinfo); + + if (lower->rngtypid != upper->rngtypid) + elog(ERROR, "range types do not match"); + + range = range_serialize(fcinfo, lower, upper, empty); + + if (rngtypinfo.canonicalFn.fn_addr != NULL) + range = FunctionCall1(&rngtypinfo.canonicalFn, range); + + PG_RETURN_RANGE(range); +} + +int +range_cmp_bounds(FunctionCallInfo fcinfo, RangeBound *b1, RangeBound *b2) +{ + int result; + + RangeTypeInfo rngtypinfo; + + if (b1->infinite && b2->infinite) + { + if (b1->lower == b2->lower) + return 0; + else + return (b1->lower) ? -1 : 1; + } + else if (b1->infinite && !b2->infinite) + return (b1->lower) ? -1 : 1; + else if (!b1->infinite && b2->infinite) + return (b2->lower) ? 1 : -1; + + range_gettypinfo(fcinfo, b1->rngtypid, &rngtypinfo); + result = DatumGetInt32(FunctionCall2Coll(&rngtypinfo.cmpFn, + rngtypinfo.collation, + b1->val, b2->val)); + + if (result == 0) + { + if (b1->inclusive && !b2->inclusive) + return (b2->lower) ? -1 : 1; + else if (!b1->inclusive && b2->inclusive) + return (b1->lower) ? 1 : -1; + } + + return result; +} + +RangeType * +make_empty_range(FunctionCallInfo fcinfo, Oid rngtypid) +{ + RangeBound lower; + RangeBound upper; + + memset(&lower, 0, sizeof(RangeBound)); + memset(&upper, 0, sizeof(RangeBound)); + + lower.rngtypid = rngtypid; + lower.lower = true; + upper.rngtypid = rngtypid; + upper.lower = false; + + return DatumGetRangeType(make_range(fcinfo, &lower, &upper, true)); +} + +/* + * Fills in rngtypinfo, from a cached copy if available. + */ +void +range_gettypinfo(FunctionCallInfo fcinfo, Oid rngtypid, + RangeTypeInfo *rngtypinfo) +{ + RangeTypeInfo *cached = (RangeTypeInfo *) fcinfo->flinfo->fn_extra; + + if (cached == NULL) + { + fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(RangeTypeInfo)); + cached = (RangeTypeInfo *) fcinfo->flinfo->fn_extra; + cached->rngtypid = ~rngtypid; + } + + if (cached->rngtypid != rngtypid) + { + Form_pg_range pg_range; + Form_pg_opclass pg_opclass; + Form_pg_type pg_type; + HeapTuple tup; + + Oid subtypeOid; + Oid collationOid; + Oid canonicalOid; + Oid subdiffOid; + Oid opclassOid; + Oid cmpFnOid; + Oid opfamilyOid; + Oid opcintype; + int16 subtyplen; + char subtypalign; + char subtypstorage; + bool subtypbyval; + + /* get information from pg_range */ + tup = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(rngtypid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for range type %u", rngtypid); + + pg_range = (Form_pg_range) GETSTRUCT(tup); + + subtypeOid = pg_range->rngsubtype; + collationOid = pg_range->rngcollation; + canonicalOid = pg_range->rngcanonical; + opclassOid = pg_range->rngsubopc; + subdiffOid = pg_range->rngsubdiff; + + ReleaseSysCache(tup); + + /* get information from pg_opclass */ + tup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassOid)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("operator class with OID %u does not exist", + opclassOid))); + + pg_opclass = (Form_pg_opclass) GETSTRUCT(tup); + + opfamilyOid = pg_opclass->opcfamily; + opcintype = pg_opclass->opcintype; + + ReleaseSysCache(tup); + + cmpFnOid = get_opfamily_proc(opfamilyOid, opcintype, opcintype, + BTORDER_PROC); + + if (!OidIsValid(cmpFnOid)) + elog(ERROR, "unable to find compare function for operator class %d", + opclassOid); + + /* get information from pg_type */ + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(subtypeOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", subtypeOid); + + pg_type = (Form_pg_type) GETSTRUCT(tup); + + subtyplen = pg_type->typlen; + subtypalign = pg_type->typalign; + subtypstorage = pg_type->typstorage; + subtypbyval = pg_type->typbyval; + + ReleaseSysCache(tup); + + /* set up the cache */ + + if (OidIsValid(canonicalOid)) + fmgr_info(canonicalOid, &cached->canonicalFn); + else + cached->canonicalFn.fn_addr = NULL; + + if (OidIsValid(subdiffOid)) + fmgr_info(subdiffOid, &cached->subdiffFn); + else + cached->subdiffFn.fn_addr = NULL; + + fmgr_info(cmpFnOid, &cached->cmpFn); + cached->subtype = subtypeOid; + cached->collation = collationOid; + cached->subtyplen = subtyplen; + cached->subtypalign = subtypalign; + cached->subtypstorage = subtypstorage; + cached->subtypbyval = subtypbyval; + cached->rngtypid = rngtypid; + } + + memcpy(rngtypinfo, cached, sizeof(RangeTypeInfo)); +} + +/* + * Given a string representing the flags for the range type, return the flags + * represented as a char. + * + * Exported so that it can be called by DefineRange to check the default flags. + */ +char +range_parse_flags(char *flags_str) +{ + char flags = 0; + + if (flags_str[0] == '\0' || + flags_str[1] == '\0' || + flags_str[2] != '\0') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are '[]', '[)', '(]', and '()'."))); + + switch (flags_str[0]) + { + case '[': + flags |= RANGE_LB_INC; + break; + case '(': + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are '[]', '[)', '(]', and '()'."))); + } + + switch (flags_str[1]) + { + case ']': + flags |= RANGE_UB_INC; + break; + case ')': + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid range bound flags"), + errhint("Valid values are '[]', '[)', '(]', and '()'."))); + } + + return flags; +} + +/* + *---------------------------------------------------------- + * STATIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* + * Parse range input, modeled after record_in in rowtypes.c. + * + * <range> := EMPTY + * | <lb-inc> <string>, <string> <ub-inc> + * <lb-inc> := '[' | '(' + * <ub-inc> := ']' | ')' + * + * Whitespace before or after <range> is ignored. Whitespace within a <string> + * is taken literally and becomes the input string for that bound. + * + * A <string> of length zero is taken as "infinite" (i.e. no bound); unless it + * is surrounded by double-quotes, in which case it is the literal empty + * string. + * + * Within a <string>, special characters (such as comma, parenthesis, or + * brackets) can be enclosed in double-quotes or escaped with backslash. Within + * double-quotes, a double-quote can be escaped with double-quote or backslash. + */ +static void +range_parse(char *string, char *flags, char **lbound_str, + char **ubound_str) +{ + char *ptr = string; + bool infinite; + + *flags = 0; + + /* consume whitespace */ + while (*ptr != '\0' && isspace(*ptr)) + ptr++; + + /* check for empty range */ + if (pg_strncasecmp(ptr, RANGE_EMPTY_LITERAL, + strlen(RANGE_EMPTY_LITERAL)) == 0) + { + *flags = RANGE_EMPTY; + + ptr += strlen(RANGE_EMPTY_LITERAL); + + /* the rest should be whitespace */ + while (*ptr != '\0' && isspace(*ptr)) + ptr++; + + /* should have consumed everything */ + if (*ptr != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + + return; + } + + if (*ptr == '[' || *ptr == '(') + { + if (*ptr == '[') + *flags |= RANGE_LB_INC; + ptr++; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Missing left parenthesis or bracket."))); + } + + ptr = range_parse_bound(string, ptr, lbound_str, &infinite); + if (infinite) + { + *flags |= RANGE_LB_INF; + *flags &= ~RANGE_LB_INC; + } + + if (*ptr != ',') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Missing upper bound."))); + ptr++; + + ptr = range_parse_bound(string, ptr, ubound_str, &infinite); + + if (*ptr == ')' || *ptr == ']') + { + if (*ptr == ']') + *flags |= RANGE_UB_INC; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Too many boundaries."))); + } + + ptr++; + + if (infinite) + { + *flags |= RANGE_UB_INF; + *flags &= ~RANGE_UB_INC; + } + + /* consume whitespace */ + while (*ptr != '\0' && isspace(*ptr)) + ptr++; + + if (*ptr != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Junk after right parenthesis or bracket."))); + + return; +} + +static char * +range_parse_bound(char *string, char *ptr, char **bound_str, bool *infinite) +{ + StringInfoData buf; + + /* Check for null: completely empty input means null */ + if (*ptr == ',' || *ptr == ')' || *ptr == ']') + { + *bound_str = NULL; + *infinite = true; + } + else + { + /* Extract string for this column */ + bool inquote = false; + + initStringInfo(&buf); + while (inquote || !(*ptr == ',' || *ptr == ')' || *ptr == ']')) + { + char ch = *ptr++; + + if (ch == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + if (ch == '\\') + { + if (*ptr == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed range literal: \"%s\"", + string), + errdetail("Unexpected end of input."))); + appendStringInfoChar(&buf, *ptr++); + } + else if (ch == '\"') + { + if (!inquote) + inquote = true; + else if (*ptr == '\"') + { + /* doubled quote within quote sequence */ + appendStringInfoChar(&buf, *ptr++); + } + else + inquote = false; + } + else + appendStringInfoChar(&buf, ch); + } + + *bound_str = buf.data; + *infinite = false; + } + + return ptr; +} + +static char * +range_deparse(char flags, char *lbound_str, char *ubound_str) +{ + StringInfoData buf; + + initStringInfo(&buf); + + if (flags & RANGE_EMPTY) + return pstrdup(RANGE_EMPTY_LITERAL); + + appendStringInfoString(&buf, (flags & RANGE_LB_INC) ? "[" : "("); + + if (RANGE_HAS_LBOUND(flags)) + appendStringInfoString(&buf, range_bound_escape(lbound_str)); + + appendStringInfoString(&buf, ","); + + if (RANGE_HAS_UBOUND(flags)) + appendStringInfoString(&buf, range_bound_escape(ubound_str)); + + appendStringInfoString(&buf, (flags & RANGE_UB_INC) ? "]" : ")"); + + return buf.data; +} + +static char * +range_bound_escape(char *value) +{ + bool nq; + char *tmp; + StringInfoData buf; + + initStringInfo(&buf); + + /* Detect whether we need double quotes for this value */ + nq = (value[0] == '\0'); /* force quotes for empty string */ + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\' || + ch == '(' || ch == ')' || + ch == '[' || ch == ']' || + ch == ',' || + isspace((unsigned char) ch)) + { + nq = true; + break; + } + } + + /* And emit the string */ + if (nq) + appendStringInfoChar(&buf, '"'); + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\') + appendStringInfoChar(&buf, ch); + appendStringInfoChar(&buf, ch); + } + if (nq) + appendStringInfoChar(&buf, '"'); + + return buf.data; +} + +static bool +range_contains_internal(FunctionCallInfo fcinfo, RangeType *r1, RangeType *r2) +{ + RangeBound lower1; + RangeBound upper1; + bool empty1; + RangeBound lower2; + RangeBound upper2; + bool empty2; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (lower1.rngtypid != upper1.rngtypid || + lower1.rngtypid != lower2.rngtypid || + lower1.rngtypid != upper2.rngtypid) + elog(ERROR, "range types do not match"); + + if (empty2) + return true; + else if (empty1) + return false; + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) > 0) + return false; + if (range_cmp_bounds(fcinfo, &upper1, &upper2) < 0) + return false; + + return true; +} + +/* + * datum_compute_size() and datum_write() are modeled after + * heap_compute_data_size() and heap_fill_tuple(). + */ + +static Size +datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign, + int16 typlen, char typstorage) +{ + if (TYPE_IS_PACKABLE(typlen, typstorage) && + VARATT_CAN_MAKE_SHORT(DatumGetPointer(val))) + { + /* + * we're anticipating converting to a short varlena header, so + * adjust length and don't count any alignment + */ + data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val)); + } + else + { + data_length = att_align_datum(data_length, typalign, typlen, val); + data_length = att_addlength_datum(data_length, typlen, val); + } + + return data_length; +} + +/* + * Modified version of the code in heap_fill_tuple(). Writes the datum to ptr + * using the correct alignment, and also uses short varlena header if + * applicable. + */ +static Pointer +datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign, + int16 typlen, char typstorage) +{ + Size data_length; + + if (typbyval) + { + /* pass-by-value */ + ptr = (char *) att_align_nominal(ptr, typalign); + store_att_byval(ptr, datum, typlen); + data_length = typlen; + } + else if (typlen == -1) + { + /* varlena */ + Pointer val = DatumGetPointer(datum); + + if (VARATT_IS_EXTERNAL(val)) + { + /* no alignment, since it's short by definition */ + data_length = VARSIZE_EXTERNAL(val); + memcpy(ptr, val, data_length); + } + else if (VARATT_IS_SHORT(val)) + { + /* no alignment for short varlenas */ + data_length = VARSIZE_SHORT(val); + memcpy(ptr, val, data_length); + } + else if (TYPE_IS_PACKABLE(typlen, typstorage) && + VARATT_CAN_MAKE_SHORT(val)) + { + /* convert to short varlena -- no alignment */ + data_length = VARATT_CONVERTED_SHORT_SIZE(val); + SET_VARSIZE_SHORT(ptr, data_length); + memcpy(ptr + 1, VARDATA(val), data_length - 1); + } + else + { + /* full 4-byte header varlena */ + ptr = (char *) att_align_nominal(ptr, typalign); + data_length = VARSIZE(val); + memcpy(ptr, val, data_length); + } + } + else if (typlen == -2) + { + /* cstring ... never needs alignment */ + Assert(typalign == 'c'); + data_length = strlen(DatumGetCString(datum)) + 1; + memcpy(ptr, DatumGetPointer(datum), data_length); + } + else + { + /* fixed-length pass-by-reference */ + ptr = (char *) att_align_nominal(ptr, typalign); + Assert(typlen > 0); + data_length = typlen; + memcpy(ptr, DatumGetPointer(datum), data_length); + } + + ptr += data_length; + + return ptr; +} diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c new file mode 100644 index 0000000000..9dc7fd1b5a --- /dev/null +++ b/src/backend/utils/adt/rangetypes_gist.c @@ -0,0 +1,587 @@ +/*------------------------------------------------------------------------- + * + * rangetypes_gist.c + * GiST support for range types. + * + * Copyright (c) 2006-2011, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/rangetypes_gist.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/gist.h" +#include "access/skey.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rangetypes.h" + +#define RANGESTRAT_EQ 1 +#define RANGESTRAT_NE 2 +#define RANGESTRAT_OVERLAPS 3 +#define RANGESTRAT_CONTAINS_ELEM 4 +#define RANGESTRAT_ELEM_CONTAINED_BY 5 +#define RANGESTRAT_CONTAINS 6 +#define RANGESTRAT_CONTAINED_BY 7 +#define RANGESTRAT_BEFORE 8 +#define RANGESTRAT_AFTER 9 +#define RANGESTRAT_OVERLEFT 10 +#define RANGESTRAT_OVERRIGHT 11 +#define RANGESTRAT_ADJACENT 12 + +static RangeType *range_super_union(FunctionCallInfo fcinfo, RangeType *r1, + RangeType *r2); +static bool range_gist_consistent_int(FunctionCallInfo fcinfo, + StrategyNumber strategy, RangeType *key, + RangeType *query); +static bool range_gist_consistent_leaf(FunctionCallInfo fcinfo, + StrategyNumber strategy, RangeType *key, + RangeType *query); +static int sort_item_cmp(const void *a, const void *b); + +/* + * Auxiliary structure for picksplit method. + */ +typedef struct +{ + int index; + RangeType *data; + FunctionCallInfo fcinfo; +} PickSplitSortItem; + + +Datum +range_gist_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Datum dquery = PG_GETARG_DATUM(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + RangeType *key = DatumGetRangeType(entry->key); + RangeType *query; + + RangeBound lower; + RangeBound upper; + bool empty; + Oid rngtypid; + + *recheck = false; + range_deserialize(fcinfo, key, &lower, &upper, &empty); + rngtypid = lower.rngtypid; + + switch (strategy) + { + RangeBound lower; + RangeBound upper; + + /* + * For contains and contained by operators, the other operand is a + * "point" of the subtype. Construct a singleton range containing just + * that value. + */ + case RANGESTRAT_CONTAINS_ELEM: + case RANGESTRAT_ELEM_CONTAINED_BY: + lower.rngtypid = rngtypid; + lower.inclusive = true; + lower.val = dquery; + lower.lower = true; + lower.infinite = false; + upper.rngtypid = rngtypid; + upper.inclusive = true; + upper.val = dquery; + upper.lower = false; + upper.infinite = false; + query = DatumGetRangeType( + make_range(fcinfo, &lower, &upper, false)); + break; + + default: + query = DatumGetRangeType(dquery); + break; + } + + if (GIST_LEAF(entry)) + PG_RETURN_BOOL(range_gist_consistent_leaf( + fcinfo, strategy, key, query)); + else + PG_RETURN_BOOL(range_gist_consistent_int( + fcinfo, strategy, key, query)); +} + +Datum +range_gist_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GISTENTRY *ent = entryvec->vector; + RangeType *result_range; + int i; + + result_range = DatumGetRangeType(ent[0].key); + + for (i = 1; i < entryvec->n; i++) + { + result_range = range_super_union(fcinfo, result_range, + DatumGetRangeType(ent[i].key)); + } + + PG_RETURN_RANGE(result_range); +} + +Datum +range_gist_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + PG_RETURN_POINTER(entry); +} + +Datum +range_gist_decompress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + PG_RETURN_POINTER(entry); +} + +Datum +range_gist_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + RangeType *orig = DatumGetRangeType(origentry->key); + RangeType *new = DatumGetRangeType(newentry->key); + RangeType *s_union = range_super_union(fcinfo, orig, new); + + FmgrInfo *subtype_diff; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + float lower_diff, upper_diff; + + RangeTypeInfo rngtypinfo; + + range_deserialize(fcinfo, orig, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, s_union, &lower2, &upper2, &empty2); + + range_gettypinfo(fcinfo, lower1.rngtypid, &rngtypinfo); + subtype_diff = &rngtypinfo.subdiffFn; + + Assert(empty1 || !empty2); + + if (empty1 && empty2) + return 0; + else if (empty1 && !empty2) + { + if (lower2.infinite || upper2.infinite) + /* from empty to infinite */ + return get_float8_infinity(); + else if (subtype_diff->fn_addr != NULL) + /* from empty to upper2-lower2 */ + return DatumGetFloat8(FunctionCall2(subtype_diff, + upper2.val, lower2.val)); + else + /* wild guess */ + return 1.0; + } + + Assert(lower2.infinite || !lower1.infinite); + + if (lower2.infinite && !lower1.infinite) + lower_diff = get_float8_infinity(); + else if (lower2.infinite && lower1.infinite) + lower_diff = 0; + else if (subtype_diff->fn_addr != NULL) + { + lower_diff = DatumGetFloat8(FunctionCall2(subtype_diff, + lower1.val, lower2.val)); + if (lower_diff < 0) + lower_diff = 0; /* subtype_diff is broken */ + } + else /* only know whether there is a difference or not */ + lower_diff = (float) range_cmp_bounds(fcinfo, &lower1, &lower2); + + Assert(upper2.infinite || !upper1.infinite); + + if (upper2.infinite && !upper1.infinite) + upper_diff = get_float8_infinity(); + else if (upper2.infinite && upper1.infinite) + upper_diff = 0; + else if (subtype_diff->fn_addr != NULL) + { + upper_diff = DatumGetFloat8(FunctionCall2(subtype_diff, + upper2.val, upper1.val)); + if (upper_diff < 0) + upper_diff = 0; /* subtype_diff is broken */ + } + else /* only know whether there is a difference or not */ + upper_diff = (float) range_cmp_bounds(fcinfo, &upper2, &upper1); + + Assert(lower_diff >= 0 && upper_diff >= 0); + + *penalty = (float) (lower_diff + upper_diff); + PG_RETURN_POINTER(penalty); +} + +/* + * The GiST PickSplit method for ranges + * + * Algorithm based on sorting. Incoming array of periods is sorted using + * sort_item_cmp function. After that first half of periods goes to the left + * datum, and the second half of periods goes to the right datum. + */ +Datum +range_gist_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + OffsetNumber i; + RangeType *pred_left; + RangeType *pred_right; + PickSplitSortItem *sortItems; + int nbytes; + OffsetNumber split_idx; + OffsetNumber *left; + OffsetNumber *right; + OffsetNumber maxoff; + + maxoff = entryvec->n - 1; + nbytes = (maxoff + 1) * sizeof(OffsetNumber); + sortItems = (PickSplitSortItem *) palloc( + maxoff * sizeof(PickSplitSortItem)); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + /* + * Preparing auxiliary array and sorting. + */ + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + sortItems[i - 1].index = i; + sortItems[i - 1].data = DatumGetRangeType(entryvec->vector[i].key); + sortItems[i - 1].fcinfo = fcinfo; + } + qsort(sortItems, maxoff, sizeof(PickSplitSortItem), sort_item_cmp); + split_idx = maxoff / 2; + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + /* + * First half of segs goes to the left datum. + */ + pred_left = DatumGetRangeType(sortItems[0].data); + *left++ = sortItems[0].index; + v->spl_nleft++; + for (i = 1; i < split_idx; i++) + { + pred_left = range_super_union(fcinfo, pred_left, + DatumGetRangeType(sortItems[i].data)); + *left++ = sortItems[i].index; + v->spl_nleft++; + } + + /* + * Second half of segs goes to the right datum. + */ + pred_right = DatumGetRangeType(sortItems[split_idx].data); + *right++ = sortItems[split_idx].index; + v->spl_nright++; + for (i = split_idx + 1; i < maxoff; i++) + { + pred_right = range_super_union(fcinfo, pred_right, + DatumGetRangeType(sortItems[i].data)); + *right++ = sortItems[i].index; + v->spl_nright++; + } + + *left = *right = FirstOffsetNumber; /* sentinel value, see dosplit() */ + + v->spl_ldatum = RangeTypeGetDatum(pred_left); + v->spl_rdatum = RangeTypeGetDatum(pred_right); + + PG_RETURN_POINTER(v); +} + +Datum +range_gist_same(PG_FUNCTION_ARGS) +{ + Datum r1 = PG_GETARG_DATUM(0); + Datum r2 = PG_GETARG_DATUM(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = DatumGetBool(OidFunctionCall2(F_RANGE_EQ, r1, r2)); + PG_RETURN_POINTER(result); +} + +/* + *---------------------------------------------------------- + * STATIC FUNCTIONS + *---------------------------------------------------------- + */ + +/* return the smallest range that contains r1 and r2 */ +static RangeType * +range_super_union(FunctionCallInfo fcinfo, RangeType *r1, RangeType *r2) +{ + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + RangeBound *result_lower; + RangeBound *result_upper; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (empty1) + return r2; + if (empty2) + return r1; + + if (range_cmp_bounds(fcinfo, &lower1, &lower2) <= 0) + result_lower = &lower1; + else + result_lower = &lower2; + + if (range_cmp_bounds(fcinfo, &upper1, &upper2) >= 0) + result_upper = &upper1; + else + result_upper = &upper2; + + /* optimization to avoid constructing a new range */ + if (result_lower == &lower1 && result_upper == &upper1) + return r1; + if (result_lower == &lower2 && result_upper == &upper2) + return r2; + + return DatumGetRangeType( + make_range(fcinfo, result_lower, result_upper, false)); +} + +static bool +range_gist_consistent_int(FunctionCallInfo fcinfo, StrategyNumber strategy, + RangeType *key, RangeType *query) +{ + Oid proc = InvalidOid; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + bool retval; + bool negate = false; + + range_deserialize(fcinfo, key, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, query, &lower2, &upper2, &empty2); + + switch (strategy) + { + case RANGESTRAT_EQ: + proc = F_RANGE_CONTAINS; + break; + case RANGESTRAT_NE: + return true; + break; + case RANGESTRAT_OVERLAPS: + proc = F_RANGE_OVERLAPS; + break; + case RANGESTRAT_CONTAINS_ELEM: + case RANGESTRAT_CONTAINS: + proc = F_RANGE_CONTAINS; + break; + case RANGESTRAT_ELEM_CONTAINED_BY: + case RANGESTRAT_CONTAINED_BY: + return true; + break; + case RANGESTRAT_BEFORE: + if (empty1) + return false; + proc = F_RANGE_OVERRIGHT; + negate = true; + break; + case RANGESTRAT_AFTER: + if (empty1) + return false; + proc = F_RANGE_OVERLEFT; + negate = true; + break; + case RANGESTRAT_OVERLEFT: + if (empty1) + return false; + proc = F_RANGE_AFTER; + negate = true; + break; + case RANGESTRAT_OVERRIGHT: + if (empty1) + return false; + proc = F_RANGE_BEFORE; + negate = true; + break; + case RANGESTRAT_ADJACENT: + if (empty1 || empty2) + return false; + if (DatumGetBool( + OidFunctionCall2(F_RANGE_ADJACENT, + RangeTypeGetDatum(key), + RangeTypeGetDatum(query)))) + return true; + proc = F_RANGE_OVERLAPS; + break; + } + + retval = DatumGetBool(OidFunctionCall2(proc, RangeTypeGetDatum(key), + RangeTypeGetDatum(query))); + + if (negate) + retval = !retval; + + PG_RETURN_BOOL(retval); +} + +static bool +range_gist_consistent_leaf(FunctionCallInfo fcinfo, StrategyNumber strategy, + RangeType *key, RangeType *query) +{ + Oid proc = InvalidOid; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + range_deserialize(fcinfo, key, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, query, &lower2, &upper2, &empty2); + + switch (strategy) + { + case RANGESTRAT_EQ: + proc = F_RANGE_EQ; + break; + case RANGESTRAT_NE: + proc = F_RANGE_NE; + break; + case RANGESTRAT_OVERLAPS: + proc = F_RANGE_OVERLAPS; + break; + case RANGESTRAT_CONTAINS_ELEM: + case RANGESTRAT_CONTAINS: + proc = F_RANGE_CONTAINS; + break; + case RANGESTRAT_ELEM_CONTAINED_BY: + case RANGESTRAT_CONTAINED_BY: + proc = F_RANGE_CONTAINED_BY; + break; + case RANGESTRAT_BEFORE: + if (empty1 || empty2) + return false; + proc = F_RANGE_BEFORE; + break; + case RANGESTRAT_AFTER: + if (empty1 || empty2) + return false; + proc = F_RANGE_AFTER; + break; + case RANGESTRAT_OVERLEFT: + if (empty1 || empty2) + return false; + proc = F_RANGE_OVERLEFT; + break; + case RANGESTRAT_OVERRIGHT: + if (empty1 || empty2) + return false; + proc = F_RANGE_OVERRIGHT; + break; + case RANGESTRAT_ADJACENT: + if (empty1 || empty2) + return false; + proc = F_RANGE_ADJACENT; + break; + } + + return DatumGetBool(OidFunctionCall2(proc, RangeTypeGetDatum(key), + RangeTypeGetDatum(query))); +} + +/* + * Compare function for PickSplitSortItem. This is actually the + * interesting part of the picksplit algorithm. + * + * We want to separate out empty ranges, bounded ranges, and unbounded + * ranges. We assume that "contains" and "overlaps" are the most + * important queries, so empty ranges will rarely match and unbounded + * ranges frequently will. Bounded ranges should be in the middle. + * + * Empty ranges we push all the way to the left, then bounded ranges + * (sorted on lower bound, then upper), then ranges with no lower + * bound, then ranges with no upper bound; and finally, ranges with no + * upper or lower bound all the way to the right. + */ +static int +sort_item_cmp(const void *a, const void *b) +{ + PickSplitSortItem *i1 = (PickSplitSortItem *)a; + PickSplitSortItem *i2 = (PickSplitSortItem *)b; + RangeType *r1 = i1->data; + RangeType *r2 = i2->data; + + RangeBound lower1, lower2; + RangeBound upper1, upper2; + bool empty1, empty2; + + FunctionCallInfo fcinfo = i1->fcinfo; + + int cmp; + + range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); + range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + + if (empty1 || empty2) + { + if (empty1 && empty2) + return 0; + else if (empty1) + return -1; + else if (empty2) + return 1; + else + Assert(false); + } + + /* + * If both lower or both upper bounds are infinite, we sort by + * ascending range size. That means that if both upper bounds are + * infinite, we sort by the lower bound _descending_. That creates + * a slightly odd total order, but keeps the pages with very + * unselective predicates grouped more closely together on the + * right. + */ + if (lower1.infinite || upper1.infinite || + lower2.infinite || upper2.infinite) + { + if (lower1.infinite && lower2.infinite) + return range_cmp_bounds(fcinfo, &upper1, &upper2); + else if (lower1.infinite) + return -1; + else if (lower2.infinite) + return 1; + else if (upper1.infinite && upper2.infinite) + return -1 * range_cmp_bounds(fcinfo, &lower1, &lower2); + else if (upper1.infinite) + return 1; + else if (upper2.infinite) + return -1; + else + Assert(false); + } + + if ((cmp = range_cmp_bounds(fcinfo, &lower1, &lower2)) != 0) + return cmp; + + return range_cmp_bounds(fcinfo, &upper1, &upper2); +} diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 326f1eee92..1b4d26d659 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -26,6 +26,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" +#include "catalog/pg_range.h" #include "catalog/pg_statistic.h" #include "catalog/pg_type.h" #include "miscadmin.h" @@ -2251,6 +2252,16 @@ type_is_enum(Oid typid) } /* + * type_is_range + * Returns true if the given type is an range type. + */ +bool +type_is_range(Oid typid) +{ + return (get_typtype(typid) == TYPTYPE_RANGE); +} + +/* * get_type_category_preferred * * Given the type OID, fetch its category and preferred-type status. @@ -2855,3 +2866,22 @@ get_namespace_name(Oid nspid) else return NULL; } + +Oid +get_range_subtype(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->rngsubtype; + ReleaseSysCache(tp); + return result; + } + else + return InvalidOid; +} diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 99e5f1d9fe..71b09abb23 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -43,6 +43,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_proc.h" +#include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" @@ -554,6 +555,17 @@ static const struct cachedesc cacheinfo[] = { }, 2048 }, + {RangeRelationId, /* RANGETYPE */ + RangeTypidIndexId, + 1, + { + Anum_pg_range_rngtypid, + 0, + 0, + 0 + }, + 1024 + }, {RelationRelationId, /* RELNAMENSP */ ClassNameNspIndexId, 2, diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 0911c8083b..3cc2a7ee07 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -407,11 +407,13 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, int nargs = declared_args->dim1; bool have_anyelement_result = false; bool have_anyarray_result = false; + bool have_anyrange_result = false; bool have_anynonarray = false; bool have_anyenum = false; Oid anyelement_type = InvalidOid; Oid anyarray_type = InvalidOid; - Oid anycollation; + Oid anyrange_type = InvalidOid; + Oid anycollation = InvalidOid; int i; /* See if there are any polymorphic outputs; quick out if not */ @@ -433,11 +435,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, have_anyelement_result = true; have_anyenum = true; break; + case ANYRANGEOID: + have_anyrange_result = true; + break; default: break; } } - if (!have_anyelement_result && !have_anyarray_result) + if (!have_anyelement_result && !have_anyarray_result && + !have_anyrange_result) return true; /* @@ -461,20 +467,47 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, if (!OidIsValid(anyarray_type)) anyarray_type = get_call_expr_argtype(call_expr, i); break; + case ANYRANGEOID: + if (!OidIsValid(anyrange_type)) + anyrange_type = get_call_expr_argtype(call_expr, i); + break; default: break; } } /* If nothing found, parser messed up */ - if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) + if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type) && + !OidIsValid(anyrange_type)) + return false; + + /* + * We can't deduce a range type from the subtype, because there may be + * multiple range types for a single subtype. + */ + if (have_anyrange_result && !OidIsValid(anyrange_type)) return false; /* If needed, deduce one polymorphic type from the other */ if (have_anyelement_result && !OidIsValid(anyelement_type)) - anyelement_type = resolve_generic_type(ANYELEMENTOID, - anyarray_type, - ANYARRAYOID); + { + if (OidIsValid(anyarray_type)) + anyelement_type = resolve_generic_type(ANYELEMENTOID, + anyarray_type, + ANYARRAYOID); + if (OidIsValid(anyrange_type)) + { + Oid subtype = resolve_generic_type(ANYELEMENTOID, + anyrange_type, + ANYRANGEOID); + if (OidIsValid(anyelement_type) && + anyelement_type != subtype) + return false; + else + anyelement_type = subtype; + } + } + if (have_anyarray_result && !OidIsValid(anyarray_type)) anyarray_type = resolve_generic_type(ANYARRAYOID, anyelement_type, @@ -492,7 +525,12 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, * Identify the collation to use for polymorphic OUT parameters. (It'll * necessarily be the same for both anyelement and anyarray.) */ - anycollation = get_typcollation(OidIsValid(anyelement_type) ? anyelement_type : anyarray_type); + + if (OidIsValid(anyelement_type)) + anycollation = get_typcollation(anyelement_type); + else if (OidIsValid(anyarray_type)) + anycollation = get_typcollation(anyarray_type); + if (OidIsValid(anycollation)) { /* @@ -529,6 +567,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, 0); TupleDescInitEntryCollation(tupdesc, i + 1, anycollation); break; + case ANYRANGEOID: + TupleDescInitEntry(tupdesc, i + 1, + NameStr(tupdesc->attrs[i]->attname), + anyrange_type, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, i + 1, anycollation); + break; default: break; } @@ -552,8 +598,10 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, { bool have_anyelement_result = false; bool have_anyarray_result = false; + bool have_anyrange_result = false; Oid anyelement_type = InvalidOid; Oid anyarray_type = InvalidOid; + Oid anyrange_type = InvalidOid; int inargno; int i; @@ -597,6 +645,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, argtypes[i] = anyarray_type; } break; + case ANYRANGEOID: + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE) + have_anyrange_result = true; + else + { + if (!OidIsValid(anyrange_type)) + { + anyrange_type = get_call_expr_argtype(call_expr, + inargno); + if (!OidIsValid(anyrange_type)) + return false; + } + argtypes[i] = anyrange_type; + } + break; default: break; } @@ -605,18 +668,42 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, } /* Done? */ - if (!have_anyelement_result && !have_anyarray_result) + if (!have_anyelement_result && !have_anyarray_result && + !have_anyrange_result) return true; + /* + * We can't deduce a range type from the subtype, because there may be + * multiple range types for a single subtype. + */ + if (have_anyrange_result && !OidIsValid(anyrange_type)) + return false; + /* If no input polymorphics, parser messed up */ - if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) + if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type) && + !OidIsValid(anyrange_type)) return false; /* If needed, deduce one polymorphic type from the other */ if (have_anyelement_result && !OidIsValid(anyelement_type)) - anyelement_type = resolve_generic_type(ANYELEMENTOID, - anyarray_type, - ANYARRAYOID); + { + if (OidIsValid(anyarray_type)) + anyelement_type = resolve_generic_type(ANYELEMENTOID, + anyarray_type, + ANYARRAYOID); + if (OidIsValid(anyrange_type)) + { + Oid subtype = resolve_generic_type(ANYELEMENTOID, + anyrange_type, + ANYRANGEOID); + if (OidIsValid(anyelement_type) && + anyelement_type != subtype) + return false; + else + anyelement_type = subtype; + } + } + if (have_anyarray_result && !OidIsValid(anyarray_type)) anyarray_type = resolve_generic_type(ANYARRAYOID, anyelement_type, @@ -637,6 +724,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, case ANYARRAYOID: argtypes[i] = anyarray_type; break; + case ANYRANGEOID: + argtypes[i] = anyrange_type; + break; default: break; } @@ -663,6 +753,7 @@ get_type_func_class(Oid typid) case TYPTYPE_BASE: case TYPTYPE_DOMAIN: case TYPTYPE_ENUM: + case TYPTYPE_RANGE: return TYPEFUNC_SCALAR; case TYPTYPE_PSEUDO: if (typid == RECORDOID) |
