diff options
Diffstat (limited to 'src')
49 files changed, 5868 insertions, 87 deletions
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 7e0b7d65a87..5a4419d3a80 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 5f7d7f6b68b..8378c360b97 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 00000000000..3b9003395e5 --- /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 5069c5759ec..91488bbbf5c 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 398bc40c490..45ca5ec4c7a 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 24ac5295f60..63958c3afc6 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 4052a9a1fc9..f3a34a12e20 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 f0a4b0efa4a..c135465fbd3 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 127818abdea..a461ac9aef1 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 a74019a0d65..5b0633398cf 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 ce28abd9fe1..5f968b011fc 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 b06faf0c720..271b57a8302 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 ddb1bd2b71c..3b78c36a7ec 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 00000000000..6327d33f3ae --- /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 00000000000..9dc7fd1b5a5 --- /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 326f1eee92d..1b4d26d6593 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 99e5f1d9fe6..71b09abb232 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 0911c8083be..3cc2a7ee075 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) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index c17b52cea8d..88a867fe8e3 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -52,6 +52,7 @@ #include "catalog/pg_largeobject.h" #include "catalog/pg_largeobject_metadata.h" #include "catalog/pg_proc.h" +#include "catalog/pg_range.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "libpq/libpq-fs.h" @@ -167,6 +168,7 @@ static void dumpExtension(Archive *fout, ExtensionInfo *extinfo); static void dumpType(Archive *fout, TypeInfo *tyinfo); static void dumpBaseType(Archive *fout, TypeInfo *tyinfo); static void dumpEnumType(Archive *fout, TypeInfo *tyinfo); +static void dumpRangeType(Archive *fout, TypeInfo *tyinfo); static void dumpDomain(Archive *fout, TypeInfo *tyinfo); static void dumpCompositeType(Archive *fout, TypeInfo *tyinfo); static void dumpCompositeTypeColComments(Archive *fout, TypeInfo *tyinfo); @@ -2989,7 +2991,8 @@ getTypes(int *numTypes) * should copy the base type's catId, but then it might capture the * pg_depend entries for the type, which we don't want. */ - if (tyinfo[i].dobj.dump && tyinfo[i].typtype == TYPTYPE_BASE) + if (tyinfo[i].dobj.dump && (tyinfo[i].typtype == TYPTYPE_BASE || + tyinfo[i].typtype == TYPTYPE_RANGE)) { stinfo = (ShellTypeInfo *) malloc(sizeof(ShellTypeInfo)); stinfo->dobj.objType = DO_SHELL_TYPE; @@ -3700,7 +3703,32 @@ getFuncs(int *numFuncs) * so be sure to fetch any such functions. */ - if (g_fout->remoteVersion >= 70300) + if (g_fout->remoteVersion >= 90200) + { + appendPQExpBuffer(query, + "SELECT tableoid, oid, proname, prolang, " + "pronargs, proargtypes, prorettype, proacl, " + "pronamespace, " + "(%s proowner) AS rolname " + "FROM pg_proc p " + "WHERE NOT proisagg AND " + " NOT EXISTS (SELECT 1 FROM pg_depend " + " WHERE classid = 'pg_proc'::regclass AND " + " objid = p.oid AND deptype = 'i') AND " + "(pronamespace != " + "(SELECT oid FROM pg_namespace " + "WHERE nspname = 'pg_catalog')", + username_subquery); + if (binary_upgrade && g_fout->remoteVersion >= 90100) + appendPQExpBuffer(query, + " OR EXISTS(SELECT 1 FROM pg_depend WHERE " + "classid = 'pg_proc'::regclass AND " + "objid = p.oid AND " + "refclassid = 'pg_extension'::regclass AND " + "deptype = 'e')"); + appendPQExpBuffer(query, ")"); + } + else if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname, prolang, " @@ -7309,6 +7337,8 @@ dumpType(Archive *fout, TypeInfo *tyinfo) dumpCompositeType(fout, tyinfo); else if (tyinfo->typtype == TYPTYPE_ENUM) dumpEnumType(fout, tyinfo); + else if (tyinfo->typtype == TYPTYPE_RANGE) + dumpRangeType(fout, tyinfo); } /* @@ -7433,6 +7463,156 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo) } /* + * dumpRangeType + * writes out to fout the queries to recreate a user-defined range type + */ +static void +dumpRangeType(Archive *fout, TypeInfo *tyinfo) +{ + PQExpBuffer q = createPQExpBuffer(); + PQExpBuffer delq = createPQExpBuffer(); + PQExpBuffer labelq = createPQExpBuffer(); + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + + Oid collationOid; + Oid opclassOid; + Oid analyzeOid; + Oid canonicalOid; + Oid subdiffOid; + + /* Set proper schema search path */ + selectSourceSchema("pg_catalog"); + + appendPQExpBuffer(query, + "SELECT rngtypid, " + "format_type(rngsubtype, NULL) as rngsubtype, " + "rngsubtype::oid as rngsubtypeoid, " + "opc.opcname AS opcname, " + "CASE WHEN rngcollation = st.typcollation THEN 0 " + " ELSE rngcollation END AS collation, " + "CASE WHEN opcdefault THEN 0 ELSE rngsubopc END " + " AS rngsubopc, " + "(SELECT nspname FROM pg_namespace nsp " + " WHERE nsp.oid = opc.opcnamespace) AS opcnsp, " + "t.typanalyze, t.typanalyze::oid as typanalyzeoid, " + "rngcanonical, rngcanonical::oid as rngcanonicaloid, " + "rngsubdiff, rngsubdiff::oid as rngsubdiffoid " + "FROM pg_catalog.pg_type t, pg_type st, " + " pg_catalog.pg_opclass opc, pg_catalog.pg_range r " + "WHERE t.oid = rngtypid AND st.oid = rngsubtype AND " + " opc.oid = rngsubopc AND rngtypid = '%u'", + tyinfo->dobj.catId.oid); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + /* + * DROP must be fully qualified in case same name appears in pg_catalog. + * CASCADE shouldn't be required here as for normal types since the I/O + * functions are generic and do not get dropped. + */ + appendPQExpBuffer(delq, "DROP TYPE %s.", + fmtId(tyinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delq, "%s;\n", + fmtId(tyinfo->dobj.name)); + + /* We might already have a shell type, but setting pg_type_oid is harmless */ + if (binary_upgrade) + binary_upgrade_set_type_oids_by_type_oid(q, tyinfo->dobj.catId.oid); + + appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (", + fmtId(tyinfo->dobj.name)); + + /* SUBTYPE */ + appendPQExpBuffer(q, "\n SUBTYPE = %s", + PQgetvalue(res, 0, PQfnumber(res, "rngsubtype"))); + + /* COLLATION */ + collationOid = atooid(PQgetvalue(res, 0, + PQfnumber(res, "collation"))); + if (OidIsValid(collationOid)) + { + CollInfo *coll; + + coll = findCollationByOid(collationOid); + if (coll) + { + /* always schema-qualify, don't try to be smart */ + appendPQExpBuffer(q, ",\n COLLATION = %s.", + fmtId(coll->dobj.namespace->dobj.name)); + appendPQExpBuffer(q, "%s", + fmtId(coll->dobj.name)); + } + } + + /* SUBTYPE_OPCLASS */ + opclassOid = atooid(PQgetvalue(res, 0, + PQfnumber(res, "rngsubopc"))); + if (OidIsValid(opclassOid)) + { + char *opcname = PQgetvalue(res, 0, PQfnumber(res, "opcname")); + char *nspname = PQgetvalue(res, 0, PQfnumber(res, "opcnsp")); + + /* always schema-qualify, don't try to be smart */ + appendPQExpBuffer(q, ",\n SUBTYPE_OPCLASS = %s.", + fmtId(nspname)); + appendPQExpBuffer(q, "%s", fmtId(opcname)); + } + + /* ANALYZE */ + analyzeOid = atooid(PQgetvalue(res, 0, + PQfnumber(res, "typanalyzeoid"))); + if (OidIsValid(analyzeOid)) + appendPQExpBuffer(q, ",\n ANALYZE = %s", + PQgetvalue(res, 0, PQfnumber(res, "typanalyze"))); + + /* CANONICAL */ + canonicalOid = atooid(PQgetvalue(res, 0, + PQfnumber(res, "rngcanonicaloid"))); + if (OidIsValid(canonicalOid)) + appendPQExpBuffer(q, ",\n CANONICAL = %s", + PQgetvalue(res, 0, PQfnumber(res, "rngcanonical"))); + + /* SUBTYPE_DIFF */ + subdiffOid = atooid(PQgetvalue(res, 0, PQfnumber(res, "rngsubdiffoid"))); + if (OidIsValid(subdiffOid)) + appendPQExpBuffer(q, ",\n SUBTYPE_DIFF = %s", + PQgetvalue(res, 0, PQfnumber(res, "rngsubdiff"))); + + appendPQExpBuffer(q, "\n);\n"); + + appendPQExpBuffer(labelq, "TYPE %s", fmtId(tyinfo->dobj.name)); + + if (binary_upgrade) + binary_upgrade_extension_member(q, &tyinfo->dobj, labelq->data); + + ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, + tyinfo->dobj.name, + tyinfo->dobj.namespace->dobj.name, + NULL, + tyinfo->rolname, false, + "TYPE", SECTION_PRE_DATA, + q->data, delq->data, NULL, + tyinfo->dobj.dependencies, tyinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Type Comments and Security Labels */ + dumpComment(fout, labelq->data, + tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, + tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); + dumpSecLabel(fout, labelq->data, + tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, + tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); + + PQclear(res); + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); + destroyPQExpBuffer(labelq); + destroyPQExpBuffer(query); +} + +/* * dumpBaseType * writes out to fout the queries to recreate a user-defined base type */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index c6273c12671..0a0ebcf9166 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,7 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201110221 +/* COMMITTER: please set appropriately */ +#define CATALOG_VERSION_NO 201111111 #endif diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 9a8e6ffc8a5..0bf2d4d6409 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -303,6 +303,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(o DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(extname name_ops)); #define ExtensionNameIndexId 3081 +DECLARE_UNIQUE_INDEX(pg_range_rgntypid_index, 3542, on pg_range using btree(rngtypid oid_ops)); +#define RangeTypidIndexId 3542 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index 3b88c41599e..ede0c2ddf64 100644 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -709,4 +709,34 @@ DATA(insert ( 3683 3615 3615 5 s 3679 403 0 )); DATA(insert ( 3702 3615 3615 7 s 3693 783 0 )); DATA(insert ( 3702 3615 3615 8 s 3694 783 0 )); +/* + * btree range_ops + */ +DATA(insert ( 3901 3831 3831 1 s 3884 403 0 )); +DATA(insert ( 3901 3831 3831 2 s 3885 403 0 )); +DATA(insert ( 3901 3831 3831 3 s 3882 403 0 )); +DATA(insert ( 3901 3831 3831 4 s 3886 403 0 )); +DATA(insert ( 3901 3831 3831 5 s 3887 403 0 )); + +/* + * hash range_ops + */ +DATA(insert ( 3903 3831 3831 1 s 3882 405 0 )); + +/* + * GiST range_ops + */ +DATA(insert ( 3919 3831 3831 1 s 3882 783 0 )); +DATA(insert ( 3919 3831 3831 2 s 3883 783 0 )); +DATA(insert ( 3919 3831 3831 3 s 3888 783 0 )); +DATA(insert ( 3919 3831 2776 4 s 3889 783 0 )); +DATA(insert ( 3919 2776 3831 5 s 3891 783 0 )); +DATA(insert ( 3919 3831 3831 6 s 3890 783 0 )); +DATA(insert ( 3919 3831 3831 7 s 3892 783 0 )); +DATA(insert ( 3919 3831 3831 8 s 3893 783 0 )); +DATA(insert ( 3919 3831 3831 9 s 3894 783 0 )); +DATA(insert ( 3919 3831 3831 10 s 3895 783 0 )); +DATA(insert ( 3919 3831 3831 11 s 3896 783 0 )); +DATA(insert ( 3919 3831 3831 12 s 3897 783 0 )); + #endif /* PG_AMOP_H */ diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index 9e2da2c30b2..e5d43f7c1d3 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -336,5 +336,14 @@ DATA(insert ( 3659 3614 3614 4 3658 )); DATA(insert ( 3659 3614 3614 5 2700 )); DATA(insert ( 3626 3614 3614 1 3622 )); DATA(insert ( 3683 3615 3615 1 3668 )); +DATA(insert ( 3901 3831 3831 1 3870 )); +DATA(insert ( 3903 3831 3831 1 3902 )); +DATA(insert ( 3919 3831 3831 1 3875 )); +DATA(insert ( 3919 3831 3831 2 3876 )); +DATA(insert ( 3919 3831 3831 3 3877 )); +DATA(insert ( 3919 3831 3831 4 3878 )); +DATA(insert ( 3919 3831 3831 5 3879 )); +DATA(insert ( 3919 3831 3831 6 3880 )); +DATA(insert ( 3919 3831 3831 7 3881 )); #endif /* PG_AMPROC_H */ diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index d723b2561ae..05ffa0384cf 100644 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -213,5 +213,8 @@ DATA(insert ( 783 tsvector_ops PGNSP PGUID 3655 3614 t 3642 )); DATA(insert ( 2742 tsvector_ops PGNSP PGUID 3659 3614 t 25 )); DATA(insert ( 403 tsquery_ops PGNSP PGUID 3683 3615 t 0 )); DATA(insert ( 783 tsquery_ops PGNSP PGUID 3702 3615 t 20 )); +DATA(insert ( 403 range_ops PGNSP PGUID 3901 3831 t 0 )); +DATA(insert ( 405 range_ops PGNSP PGUID 3903 3831 t 0 )); +DATA(insert ( 783 range_ops PGNSP PGUID 3919 3831 t 0 )); #endif /* PG_OPCLASS_H */ diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index 64f1391b000..f587f5b198d 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -1661,6 +1661,45 @@ DESCR("less than or equal"); DATA(insert OID = 2993 ( ">=" PGNSP PGUID b f f 2249 2249 16 2992 2990 record_ge scalargtsel scalargtjoinsel )); DESCR("greater than or equal"); +/* generic range type operators */ +DATA(insert OID = 3882 ( "=" PGNSP PGUID b t t 3831 3831 16 3882 3883 range_eq eqsel eqjoinsel )); +DESCR("equal"); +DATA(insert OID = 3883 ( "<>" PGNSP PGUID b f f 3831 3831 16 3883 3882 range_ne neqsel neqjoinsel )); +DESCR("not equal"); +DATA(insert OID = 3884 ( "<" PGNSP PGUID b f f 3831 3831 16 3887 3886 range_lt scalarltsel scalarltjoinsel )); +DESCR("less than"); +DATA(insert OID = 3885 ( "<=" PGNSP PGUID b f f 3831 3831 16 3886 3887 range_le scalarltsel scalarltjoinsel )); +DESCR("less than or equal"); +DATA(insert OID = 3886 ( ">=" PGNSP PGUID b f f 3831 3831 16 3885 3884 range_ge scalargtsel scalargtjoinsel )); +DESCR("greater than or equal"); +DATA(insert OID = 3887 ( ">" PGNSP PGUID b f f 3831 3831 16 3884 3885 range_gt scalargtsel scalargtjoinsel )); +DESCR("greater than"); +DATA(insert OID = 3888 ( "&&" PGNSP PGUID b f f 3831 3831 16 3888 0 3857 - - )); +DESCR("overlaps"); +DATA(insert OID = 3889 ( "@>" PGNSP PGUID b f f 3831 2776 16 3891 0 3858 - - )); +DESCR("contains"); +DATA(insert OID = 3890 ( "@>" PGNSP PGUID b f f 3831 3831 16 3892 0 3859 - - )); +DESCR("contains"); +DATA(insert OID = 3891 ( "<@" PGNSP PGUID b f f 2776 3831 16 3889 0 3860 - - )); +DESCR("contained by"); +DATA(insert OID = 3892 ( "<@" PGNSP PGUID b f f 3831 3831 16 3890 0 3861 - - )); +DESCR("contained by"); +DATA(insert OID = 3893 ( "<<" PGNSP PGUID b f f 3831 3831 16 0 0 before scalarltsel scalarltjoinsel )); +DESCR("left of"); +DATA(insert OID = 3894 ( ">>" PGNSP PGUID b f f 3831 3831 16 0 0 after scalargtsel scalargtjoinsel )); +DESCR("right of"); +DATA(insert OID = 3895 ( "&<" PGNSP PGUID b f f 3831 3831 16 0 0 overleft scalarltsel scalarltjoinsel )); +DESCR("overlaps to left"); +DATA(insert OID = 3896 ( "&>" PGNSP PGUID b f f 3831 3831 16 0 0 overright scalargtsel scalargtjoinsel )); +DESCR("overlaps to right"); +DATA(insert OID = 3897 ( "-|-" PGNSP PGUID b f f 3831 3831 16 3897 0 adjacent - - )); +DESCR("adjacent"); +DATA(insert OID = 3898 ( "+" PGNSP PGUID b f f 3831 3831 3831 3898 0 range_union - - )); +DESCR("range union"); +DATA(insert OID = 3899 ( "-" PGNSP PGUID b f f 3831 3831 3831 0 0 minus - - )); +DESCR("range difference"); +DATA(insert OID = 3900 ( "*" PGNSP PGUID b f f 3831 3831 3831 3900 0 range_intersect - - )); +DESCR("intersection"); /* * function prototypes diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index 548727dbd24..5ea949bec6b 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -139,5 +139,8 @@ DATA(insert OID = 3655 ( 783 tsvector_ops PGNSP PGUID )); DATA(insert OID = 3659 ( 2742 tsvector_ops PGNSP PGUID )); DATA(insert OID = 3683 ( 403 tsquery_ops PGNSP PGUID )); DATA(insert OID = 3702 ( 783 tsquery_ops PGNSP PGUID )); +DATA(insert OID = 3901 ( 403 range_ops PGNSP PGUID )); +DATA(insert OID = 3903 ( 405 range_ops PGNSP PGUID )); +DATA(insert OID = 3919 ( 783 range_ops PGNSP PGUID )); #endif /* PG_OPFAMILY_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 64b7a6a314d..3b654ff7c45 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4334,6 +4334,153 @@ DESCR("fetch the last row value"); DATA(insert OID = 3114 ( nth_value PGNSP PGUID 12 1 0 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_nth_value _null_ _null_ _null_ )); DESCR("fetch the Nth row value"); +/* procs for range types */ +DATA(insert OID = 3832 ( anyrange_in PGNSP PGUID 12 1 0 0 0 f f f t f s 3 0 3831 "2275 26 23" _null_ _null_ _null_ _null_ anyrange_in _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3833 ( anyrange_out PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 2275 "3831" _null_ _null_ _null_ _null_ anyrange_out _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3834 ( range_in PGNSP PGUID 12 1 0 0 0 f f f t f s 3 0 3831 "2275 26 23" _null_ _null_ _null_ _null_ range_in _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3835 ( range_out PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 2275 "3831" _null_ _null_ _null_ _null_ range_out _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3836 ( range_recv PGNSP PGUID 12 1 0 0 0 f f f t f s 3 0 3831 "2281 26 23" _null_ _null_ _null_ _null_ range_recv _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3837 ( range_send PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 17 "3831" _null_ _null_ _null_ _null_ range_send _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3848 ( lower PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2283 "3831" _null_ _null_ _null_ _null_ range_lower _null_ _null_ _null_ )); +DESCR("return the range's lower bound"); +DATA(insert OID = 3849 ( upper PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2283 "3831" _null_ _null_ _null_ _null_ range_upper _null_ _null_ _null_ )); +DESCR("return the range's upper bound"); +DATA(insert OID = 3850 ( isempty PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_empty _null_ _null_ _null_ )); +DESCR("is the range empty?"); +DATA(insert OID = 3851 ( lower_inc PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_lower_inc _null_ _null_ _null_ )); +DESCR("is the range's lower bound inclusive?"); +DATA(insert OID = 3852 ( upper_inc PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_upper_inc _null_ _null_ _null_ )); +DESCR("is the range's upper bound inclusive?"); +DATA(insert OID = 3853 ( lower_inf PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_lower_inf _null_ _null_ _null_ )); +DESCR("is the range's lower bound infinite?"); +DATA(insert OID = 3854 ( upper_inf PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 16 "3831" _null_ _null_ _null_ _null_ range_upper_inf _null_ _null_ _null_ )); +DESCR("is the range's upper bound infinite?"); +DATA(insert OID = 3855 ( range_eq PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_eq _null_ _null_ _null_ )); +DESCR("implementation of = operator"); +DATA(insert OID = 3856 ( range_ne PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_ne _null_ _null_ _null_ )); +DESCR("implementation of <> operator"); +DATA(insert OID = 3857 ( overlaps PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_overlaps _null_ _null_ _null_ )); +DESCR("implementation of && operator"); +DATA(insert OID = 3858 ( contains PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 2776" _null_ _null_ _null_ _null_ range_contains_elem _null_ _null_ _null_ )); +DESCR("implementation of @> operator"); +DATA(insert OID = 3859 ( contains PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_contains _null_ _null_ _null_ )); +DESCR("implementation of @> operator"); +DATA(insert OID = 3860 ( contained_by PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "2776 3831" _null_ _null_ _null_ _null_ elem_contained_by_range _null_ _null_ _null_ )); +DESCR("implementation of <@ operator"); +DATA(insert OID = 3861 ( contained_by PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_contained_by _null_ _null_ _null_ )); +DESCR("implementation of <@ operator"); +DATA(insert OID = 3862 ( adjacent PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_adjacent _null_ _null_ _null_ )); +DESCR("implementation of -|- operator"); +DATA(insert OID = 3863 ( before PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_before _null_ _null_ _null_ )); +DESCR("implementation of << operator"); +DATA(insert OID = 3864 ( after PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_after _null_ _null_ _null_ )); +DESCR("implementation of >> operator"); +DATA(insert OID = 3865 ( overleft PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_overleft _null_ _null_ _null_ )); +DESCR("implementation of &< operator"); +DATA(insert OID = 3866 ( overright PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_overright _null_ _null_ _null_ )); +DESCR("implementation of &> operator"); +DATA(insert OID = 3867 ( range_union PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 3831 "3831 3831" _null_ _null_ _null_ _null_ range_union _null_ _null_ _null_ )); +DESCR("implementation of + operator"); +DATA(insert OID = 3868 ( range_intersect PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 3831 "3831 3831" _null_ _null_ _null_ _null_ range_intersect _null_ _null_ _null_ )); +DESCR("implementation of * operator"); +DATA(insert OID = 3869 ( minus PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 3831 "3831 3831" _null_ _null_ _null_ _null_ range_minus _null_ _null_ _null_ )); +DESCR("implementation of - operator"); +DATA(insert OID = 3870 ( range_cmp PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "3831 3831" _null_ _null_ _null_ _null_ range_cmp _null_ _null_ _null_ )); +DESCR("less-equal-greater"); +DATA(insert OID = 3871 ( range_lt PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_lt _null_ _null_ _null_ )); +DATA(insert OID = 3872 ( range_le PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_le _null_ _null_ _null_ )); +DATA(insert OID = 3873 ( range_ge PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_ge _null_ _null_ _null_ )); +DATA(insert OID = 3874 ( range_gt PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "3831 3831" _null_ _null_ _null_ _null_ range_gt _null_ _null_ _null_ )); +DATA(insert OID = 3875 ( range_gist_consistent PGNSP PGUID 12 1 0 0 0 f f f t f i 5 0 16 "2281 3831 21 26 2281" _null_ _null_ _null_ _null_ range_gist_consistent _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3876 ( range_gist_union PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ range_gist_union _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3877 ( range_gist_compress PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ range_gist_compress _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3878 ( range_gist_decompress PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ range_gist_decompress _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3879 ( range_gist_penalty PGNSP PGUID 12 1 0 0 0 f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ range_gist_penalty _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3880 ( range_gist_picksplit PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ range_gist_picksplit _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3881 ( range_gist_same PGNSP PGUID 12 1 0 0 0 f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ range_gist_same _null_ _null_ _null_ )); +DESCR("GiST support"); +DATA(insert OID = 3902 ( hash_range PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 23 "3831" _null_ _null_ _null_ _null_ hash_range _null_ _null_ _null_ )); +DESCR("hash a range"); +DATA(insert OID = 3914 ( int4range_canonical PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 3904 "3904" _null_ _null_ _null_ _null_ int4range_canonical _null_ _null_ _null_ )); +DESCR("convert an int4 range to canonical form"); +DATA(insert OID = 3928 ( int8range_canonical PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 3926 "3926" _null_ _null_ _null_ _null_ int8range_canonical _null_ _null_ _null_ )); +DESCR("convert an int8 range to canonical form"); +DATA(insert OID = 3915 ( daterange_canonical PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 3906 "3906" _null_ _null_ _null_ _null_ daterange_canonical _null_ _null_ _null_ )); +DESCR("convert a date range to canonical form"); +DATA(insert OID = 3922 ( int4range_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "23 23" _null_ _null_ _null_ _null_ int4range_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two int4 values"); +DATA(insert OID = 3923 ( int8range_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "20 20" _null_ _null_ _null_ _null_ int8range_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two int8 values"); +DATA(insert OID = 3924 ( numrange_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "1700 1700" _null_ _null_ _null_ _null_ numrange_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two numeric values"); +DATA(insert OID = 3925 ( daterange_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "1082 1082" _null_ _null_ _null_ _null_ daterange_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two date values"); +DATA(insert OID = 3929 ( tsrange_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "1114 1114" _null_ _null_ _null_ _null_ tsrange_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two timestamp values"); +DATA(insert OID = 3930 ( tstzrange_subdiff PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 701 "1184 1184" _null_ _null_ _null_ _null_ tstzrange_subdiff _null_ _null_ _null_ )); +DESCR("float8 difference of two timestamp with time zone values"); + + +DATA(insert OID = 3838 ( int4range PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3904 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("int4range constructor"); +DATA(insert OID = 3839 ( int4range PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3904 "23" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("int4range constructor"); +DATA(insert OID = 3840 ( int4range PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3904 "23 23" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("int4range constructor"); +DATA(insert OID = 3841 ( int4range PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3904 "23 23 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("int4range constructor"); +DATA(insert OID = 3842 ( numrange PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3906 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("numrange constructor"); +DATA(insert OID = 3843 ( numrange PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3906 "1700" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("numrange constructor"); +DATA(insert OID = 3844 ( numrange PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3906 "1700 1700" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("numrange constructor"); +DATA(insert OID = 3845 ( numrange PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3906 "1700 1700 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("numrange constructor"); +DATA(insert OID = 3846 ( tsrange PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3908 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("tsrange constructor"); +DATA(insert OID = 3847 ( tsrange PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3908 "1114" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("tsrange constructor"); +DATA(insert OID = 3933 ( tsrange PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3908 "1114 1114" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("tsrange constructor"); +DATA(insert OID = 3934 ( tsrange PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3908 "1114 1114 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("tsrange constructor"); +DATA(insert OID = 3935 ( tstzrange PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3910 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("tstzrange constructor"); +DATA(insert OID = 3936 ( tstzrange PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3910 "1184" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("tstzrange constructor"); +DATA(insert OID = 3937 ( tstzrange PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3910 "1184 1184" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("tstzrange constructor"); +DATA(insert OID = 3938 ( tstzrange PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3910 "1184 1184 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("tstzrange constructor"); +DATA(insert OID = 3939 ( daterange PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3912 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("daterange constructor"); +DATA(insert OID = 3940 ( daterange PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3912 "1082" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("daterange constructor"); +DATA(insert OID = 3941 ( daterange PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3912 "1082 1082" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("daterange constructor"); +DATA(insert OID = 3942 ( daterange PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3912 "1082 1082 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("daterange constructor"); +DATA(insert OID = 3943 ( int8range PGNSP PGUID 12 1 0 0 0 f f f f f i 0 0 3926 "" _null_ _null_ _null_ _null_ range_constructor0 _null_ _null_ _null_ )); +DESCR("int8range constructor"); +DATA(insert OID = 3944 ( int8range PGNSP PGUID 12 1 0 0 0 f f f f f i 1 0 3926 "20" _null_ _null_ _null_ _null_ range_constructor1 _null_ _null_ _null_ )); +DESCR("int8range constructor"); +DATA(insert OID = 3945 ( int8range PGNSP PGUID 12 1 0 0 0 f f f f f i 2 0 3926 "20 20" _null_ _null_ _null_ _null_ range_constructor2 _null_ _null_ _null_ )); +DESCR("int8range constructor"); +DATA(insert OID = 3946 ( int8range PGNSP PGUID 12 1 0 0 0 f f f f f i 3 0 3926 "20 20 25" _null_ _null_ _null_ _null_ range_constructor3 _null_ _null_ _null_ )); +DESCR("int8range constructor"); /* * Symbolic values for provolatile column: these indicate whether the result diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h new file mode 100644 index 00000000000..19b437db8dc --- /dev/null +++ b/src/include/catalog/pg_range.h @@ -0,0 +1,84 @@ +/*------------------------------------------------------------------------- + * + * pg_range.h + * definition of the system "range" relation (pg_range) + * along with the relation's initial contents. + * + * + * Copyright (c) 2006-2010, PostgreSQL Global Development Group + * + * src/include/catalog/pg_range.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + * XXX do NOT break up DATA() statements into multiple lines! + * the scripts are not as smart as you might think... + * + *------------------------------------------------------------------------- + */ +#ifndef PG_RANGE_H +#define PG_RANGE_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_range definition. cpp turns this into + * typedef struct FormData_pg_range + * ---------------- + */ +#define RangeRelationId 3541 + +CATALOG(pg_range,3541) BKI_WITHOUT_OIDS +{ + Oid rngtypid; /* OID of owning range type */ + Oid rngsubtype; /* OID of range's subtype */ + Oid rngcollation; /* collation for this range type, or 0 */ + Oid rngsubopc; /* subtype's btree opclass */ + regproc rngcanonical; /* canonicalize range, or 0 */ + regproc rngsubdiff; /* subtype difference as a float8 (for GiST) */ +} FormData_pg_range; + +/* ---------------- + * Form_pg_range corresponds to a pointer to a tuple with + * the format of pg_range relation. + * ---------------- + */ +typedef FormData_pg_range *Form_pg_range; + +/* ---------------- + * compiler constants for pg_range + * ---------------- + */ +#define Natts_pg_range 6 +#define Anum_pg_range_rngtypid 1 +#define Anum_pg_range_rngsubtype 2 +#define Anum_pg_range_rngcollation 3 +#define Anum_pg_range_rngsubopc 4 +#define Anum_pg_range_rngcanonical 5 +#define Anum_pg_range_rngsubdiff 6 + +#define RANGE_DEFAULT_FLAGS "[)" + +/* + * prototypes for functions in pg_range.c + */ + +extern void RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation, + Oid rangeSubOpclass, RegProcedure rangeCanonical, + RegProcedure rangeSubDiff); +extern void RangeDelete(Oid rangeTypeOid); + +/* ---------------- + * initial contents of pg_range + * ---------------- + */ +DATA(insert ( 3904 23 0 1978 int4range_canonical int4range_subdiff)); +DATA(insert ( 3906 1700 0 10037 - numrange_subdiff)); +DATA(insert ( 3908 1114 0 10054 - tsrange_subdiff)); +DATA(insert ( 3910 1184 0 10047 - tstzrange_subdiff)); +DATA(insert ( 3912 1082 0 10019 daterange_canonical daterange_subdiff)); +DATA(insert ( 3926 20 0 10029 int8range_canonical int8range_subdiff)); + +#endif /* PG_RANGE_H */ diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index d72ca2342fa..b24fbc97f4e 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -591,6 +591,28 @@ DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 tx DESCR("txid snapshot"); DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +/* range types */ + +DATA(insert OID = 3904 ( int4range PGNSP PGUID -1 f r R f t \054 0 0 3905 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of int4s"); +#define INT4RANGEOID 3904 +DATA(insert OID = 3905 ( _int4range PGNSP PGUID -1 f b A f t \054 0 3904 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3906 ( numrange PGNSP PGUID -1 f r R f t \054 0 0 3907 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of numerics"); +DATA(insert OID = 3907 ( _numrange PGNSP PGUID -1 f b A f t \054 0 3906 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3908 ( tsrange PGNSP PGUID -1 f r R f t \054 0 0 3909 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of timestamps"); +DATA(insert OID = 3909 ( _tsrange PGNSP PGUID -1 f b A f t \054 0 3908 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3910 ( tstzrange PGNSP PGUID -1 f r R f t \054 0 0 3911 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of timestamps with time zone"); +DATA(insert OID = 3911 ( _tstzrange PGNSP PGUID -1 f b A f t \054 0 3910 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3912 ( daterange PGNSP PGUID -1 f r R f t \054 0 0 3913 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of dates"); +DATA(insert OID = 3913 ( _daterange PGNSP PGUID -1 f b A f t \054 0 3912 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3926 ( int8range PGNSP PGUID -1 f r R f t \054 0 0 3927 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of int8s"); +DATA(insert OID = 3927 ( _int8range PGNSP PGUID -1 f b A f t \054 0 3926 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); + /* * pseudo-types * @@ -632,6 +654,8 @@ DATA(insert OID = 3500 ( anyenum PGNSP PGUID 4 t p P f t \054 0 0 0 anyenum_in #define ANYENUMOID 3500 DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_handler_in fdw_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); #define FDW_HANDLEROID 3115 +DATA(insert OID = 3831 ( anyrange PGNSP PGUID 4 t p P f t \054 0 0 0 anyrange_in anyrange_out - - - - - i p f 0 -1 0 0 _null_ _null_ )); +#define ANYRANGEOID 3831 /* @@ -642,6 +666,7 @@ DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_han #define TYPTYPE_DOMAIN 'd' /* domain over another type */ #define TYPTYPE_ENUM 'e' /* enumerated type */ #define TYPTYPE_PSEUDO 'p' /* pseudo-type */ +#define TYPTYPE_RANGE 'r' /* range type */ #define TYPCATEGORY_INVALID '\0' /* not an allowed category */ #define TYPCATEGORY_ARRAY 'A' @@ -653,6 +678,7 @@ DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_han #define TYPCATEGORY_NETWORK 'I' /* think INET */ #define TYPCATEGORY_NUMERIC 'N' #define TYPCATEGORY_PSEUDOTYPE 'P' +#define TYPCATEGORY_RANGE 'R' #define TYPCATEGORY_STRING 'S' #define TYPCATEGORY_TIMESPAN 'T' #define TYPCATEGORY_USER 'U' @@ -664,6 +690,7 @@ DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_han ((typid) == ANYELEMENTOID || \ (typid) == ANYARRAYOID || \ (typid) == ANYNONARRAYOID || \ - (typid) == ANYENUMOID) + (typid) == ANYENUMOID || \ + (typid) == ANYRANGEOID) #endif /* PG_TYPE_H */ diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index 429a964f913..0c328958635 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -23,6 +23,7 @@ extern void DefineType(List *names, List *parameters); extern void RemoveTypeById(Oid typeOid); extern void DefineDomain(CreateDomainStmt *stmt); extern void DefineEnum(CreateEnumStmt *stmt); +extern void DefineRange(CreateRangeStmt *stmt); extern void AlterEnum(AlterEnumStmt *stmt); extern Oid DefineCompositeType(const RangeVar *typevar, List *coldeflist); extern Oid AssignTypeArrayOid(void); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 7aa299485fc..824d8b5dc9a 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -346,6 +346,7 @@ typedef enum NodeTag T_ReassignOwnedStmt, T_CompositeTypeStmt, T_CreateEnumStmt, + T_CreateRangeStmt, T_AlterEnumStmt, T_AlterTSDictionaryStmt, T_AlterTSConfigurationStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9998e2f24d6..af6565e7e4a 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2335,6 +2335,17 @@ typedef struct AlterEnumStmt } AlterEnumStmt; /* ---------------------- + * Create Type Statement, range types + * ---------------------- + */ +typedef struct CreateRangeStmt +{ + NodeTag type; + List *typeName; /* qualified name (list of Value strings) */ + List *params; /* range parameters (list of DefElem) */ +} CreateRangeStmt; + +/* ---------------------- * Create View Statement * ---------------------- */ diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 215951589af..d3ad4f14282 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -119,6 +119,7 @@ extern Node *get_typdefault(Oid typid); extern char get_typtype(Oid typid); extern bool type_is_rowtype(Oid typid); extern bool type_is_enum(Oid typid); +extern bool type_is_range(Oid typid); extern void get_type_category_preferred(Oid typid, char *typcategory, bool *typispreferred); @@ -147,6 +148,7 @@ extern void free_attstatsslot(Oid atttype, Datum *values, int nvalues, float4 *numbers, int nnumbers); extern char *get_namespace_name(Oid nspid); +extern Oid get_range_subtype(Oid rangeOid); #define type_is_array(typid) (get_element_type(typid) != InvalidOid) /* type_is_array_domain accepts both plain arrays and domains over arrays */ diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h new file mode 100644 index 00000000000..a7595b15f60 --- /dev/null +++ b/src/include/utils/rangetypes.h @@ -0,0 +1,159 @@ +/*------------------------------------------------------------------------- + * + * rangetypes.h + * Declarations for Postgres range types. + * + */ + +#ifndef RANGETYPES_H +#define RANGETYPES_H + +#include "fmgr.h" + +typedef struct varlena RangeType; + +typedef struct +{ + Datum val; + Oid rngtypid; + bool infinite; + bool lower; + bool inclusive; +} RangeBound; + +typedef struct +{ + FmgrInfo canonicalFn; + FmgrInfo cmpFn; + FmgrInfo subdiffFn; + Oid rngtypid; + Oid subtype; + Oid collation; + int16 subtyplen; + char subtypalign; + char subtypstorage; + bool subtypbyval; +} RangeTypeInfo; + +/* + * fmgr macros for range type objects + */ +#define DatumGetRangeType(X) ((RangeType *) PG_DETOAST_DATUM(X)) +#define DatumGetRangeTypeCopy(X) ((RangeType *) PG_DETOAST_DATUM_COPY(X)) +#define RangeTypeGetDatum(X) PointerGetDatum(X) +#define PG_GETARG_RANGE(n) DatumGetRangeType(PG_GETARG_DATUM(n)) +#define PG_GETARG_RANGE_COPY(n) DatumGetRangeTypeCopy(PG_GETARG_DATUM(n)) +#define PG_RETURN_RANGE(x) return RangeTypeGetDatum(x) + +/* + * prototypes for functions defined in rangetypes.c + */ + +/* IO */ +extern Datum anyrange_in(PG_FUNCTION_ARGS); +extern Datum anyrange_out(PG_FUNCTION_ARGS); +extern Datum range_in(PG_FUNCTION_ARGS); +extern Datum range_out(PG_FUNCTION_ARGS); +extern Datum range_recv(PG_FUNCTION_ARGS); +extern Datum range_send(PG_FUNCTION_ARGS); + +/* constructors */ +extern Datum range_constructor0(PG_FUNCTION_ARGS); +extern Datum range_constructor1(PG_FUNCTION_ARGS); +extern Datum range_constructor2(PG_FUNCTION_ARGS); +extern Datum range_constructor3(PG_FUNCTION_ARGS); +extern Datum range_make1(PG_FUNCTION_ARGS); +extern Datum range_linf_(PG_FUNCTION_ARGS); +extern Datum range_uinf_(PG_FUNCTION_ARGS); +extern Datum range_linfi(PG_FUNCTION_ARGS); +extern Datum range_uinfi(PG_FUNCTION_ARGS); +extern Datum range(PG_FUNCTION_ARGS); +extern Datum range__(PG_FUNCTION_ARGS); +extern Datum range_i(PG_FUNCTION_ARGS); +extern Datum rangei_(PG_FUNCTION_ARGS); +extern Datum rangeii(PG_FUNCTION_ARGS); + +/* range -> subtype */ +extern Datum range_lower(PG_FUNCTION_ARGS); +extern Datum range_upper(PG_FUNCTION_ARGS); + +/* range -> bool */ +extern Datum range_empty(PG_FUNCTION_ARGS); +extern Datum range_lower_inc(PG_FUNCTION_ARGS); +extern Datum range_upper_inc(PG_FUNCTION_ARGS); +extern Datum range_lower_inf(PG_FUNCTION_ARGS); +extern Datum range_upper_inf(PG_FUNCTION_ARGS); + +/* range, point -> bool */ +extern Datum range_contains_elem(PG_FUNCTION_ARGS); +extern Datum elem_contained_by_range(PG_FUNCTION_ARGS); + +/* range, range -> bool */ +extern Datum range_eq(PG_FUNCTION_ARGS); +extern Datum range_ne(PG_FUNCTION_ARGS); +extern Datum range_contains(PG_FUNCTION_ARGS); +extern Datum range_contained_by(PG_FUNCTION_ARGS); +extern Datum range_before(PG_FUNCTION_ARGS); +extern Datum range_after(PG_FUNCTION_ARGS); +extern Datum range_adjacent(PG_FUNCTION_ARGS); +extern Datum range_overlaps(PG_FUNCTION_ARGS); +extern Datum range_overleft(PG_FUNCTION_ARGS); +extern Datum range_overright(PG_FUNCTION_ARGS); + +/* range, range -> range */ +extern Datum range_minus(PG_FUNCTION_ARGS); +extern Datum range_union(PG_FUNCTION_ARGS); +extern Datum range_intersect(PG_FUNCTION_ARGS); + +/* BTree support */ +extern Datum range_cmp(PG_FUNCTION_ARGS); +extern Datum range_lt(PG_FUNCTION_ARGS); +extern Datum range_le(PG_FUNCTION_ARGS); +extern Datum range_ge(PG_FUNCTION_ARGS); +extern Datum range_gt(PG_FUNCTION_ARGS); + +/* Hash support */ +extern Datum hash_range(PG_FUNCTION_ARGS); + +/* GiST support (rangetypes_gist.c) */ +extern Datum range_gist_consistent(PG_FUNCTION_ARGS); +extern Datum range_gist_compress(PG_FUNCTION_ARGS); +extern Datum range_gist_decompress(PG_FUNCTION_ARGS); +extern Datum range_gist_union(PG_FUNCTION_ARGS); +extern Datum range_gist_penalty(PG_FUNCTION_ARGS); +extern Datum range_gist_picksplit(PG_FUNCTION_ARGS); +extern Datum range_gist_same(PG_FUNCTION_ARGS); + +/* Canonical functions */ +Datum int4range_canonical(PG_FUNCTION_ARGS); +Datum int8range_canonical(PG_FUNCTION_ARGS); +Datum daterange_canonical(PG_FUNCTION_ARGS); + +/* Subtype Difference functions */ +Datum int4range_subdiff(PG_FUNCTION_ARGS); +Datum int8range_subdiff(PG_FUNCTION_ARGS); +Datum numrange_subdiff(PG_FUNCTION_ARGS); +Datum daterange_subdiff(PG_FUNCTION_ARGS); +Datum tsrange_subdiff(PG_FUNCTION_ARGS); +Datum tstzrange_subdiff(PG_FUNCTION_ARGS); + +/* for defining more generic functions */ +extern Datum make_range(FunctionCallInfo fcinfo, RangeBound *lower, + RangeBound *upper, bool empty); +extern void range_deserialize(FunctionCallInfo fcinfo, RangeType *range, + RangeBound *lower, RangeBound *upper, + bool *empty); +extern int range_cmp_bounds(FunctionCallInfo fcinfo, RangeBound *b1, + RangeBound *b2); +extern RangeType *make_empty_range(FunctionCallInfo fcinfo, Oid rngtypid); +extern void range_gettypinfo(FunctionCallInfo fcinfo, Oid rngtypid, + RangeTypeInfo *rngtypinfo); + +/* for defining a range "canonicalize" function */ +extern Datum range_serialize(FunctionCallInfo fcinfo, RangeBound *lower, + RangeBound *upper, bool empty); + +/* for use in DefineRange */ +extern char range_parse_flags(char *flags_str); + +#endif /* RANGETYPES_H */ diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index 55d22303a7a..35782fc3e7b 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -70,6 +70,7 @@ enum SysCacheIdentifier OPFAMILYOID, PROCNAMEARGSNSP, PROCOID, + RANGETYPE, RELNAMENSP, RELOID, RULERELNAME, diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 578cae57346..48399d3929c 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -490,6 +490,8 @@ do_compile(FunctionCallInfo fcinfo, { if (rettypeid == ANYARRAYOID) rettypeid = INT4ARRAYOID; + else if (rettypeid == ANYRANGEOID) + rettypeid = INT4RANGEOID; else /* ANYELEMENT or ANYNONARRAY */ rettypeid = INT4OID; /* XXX what could we use for ANYENUM? */ @@ -2119,6 +2121,7 @@ build_datatype(HeapTuple typeTup, int32 typmod, Oid collation) case TYPTYPE_BASE: case TYPTYPE_DOMAIN: case TYPTYPE_ENUM: + case TYPTYPE_RANGE: typ->ttype = PLPGSQL_TTYPE_SCALAR; break; case TYPTYPE_COMPOSITE: @@ -2373,8 +2376,8 @@ compute_function_hashkey(FunctionCallInfo fcinfo, /* * This is the same as the standard resolve_polymorphic_argtypes() function, * but with a special case for validation: assume that polymorphic arguments - * are integer or integer-array. Also, we go ahead and report the error - * if we can't resolve the types. + * are integer, integer-range or integer-array. Also, we go ahead and report + * the error if we can't resolve the types. */ static void plpgsql_resolve_polymorphic_argtypes(int numargs, @@ -2407,6 +2410,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs, case ANYENUMOID: /* XXX dubious */ argtypes[i] = INT4OID; break; + case ANYRANGEOID: + argtypes[i] = INT4RANGEOID; + break; case ANYARRAYOID: argtypes[i] = INT4ARRAYOID; break; diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out index 8cee6ed8127..f9659f7739e 100644 --- a/src/test/regress/expected/collate.linux.utf8.out +++ b/src/test/regress/expected/collate.linux.utf8.out @@ -1049,3 +1049,20 @@ Composite type "public.collate_dep_test2" DROP TABLE collate_dep_test1, collate_dep_test4t; DROP TYPE collate_dep_test2; +-- test range types and collations +create type textrange_c as range(subtype=text, collation="C"); +create type textrange_en_us as range(subtype=text, collation="en_US"); +select textrange_c('A','Z') @> 'b'::text; + ?column? +---------- + f +(1 row) + +select textrange_en_us('A','Z') @> 'b'::text; + ?column? +---------- + t +(1 row) + +drop type textrange_c; +drop type textrange_en_us; diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index a25f90cbfd2..19b559ffa17 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -147,7 +147,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.prorettype < p2.prorettype) + (p1.prorettype < p2.prorettype) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; prorettype | prorettype ------------+------------ @@ -161,7 +163,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.proargtypes[0] < p2.proargtypes[0]) + (p1.proargtypes[0] < p2.proargtypes[0]) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; proargtypes | proargtypes -------------+------------- @@ -178,7 +182,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.proargtypes[1] < p2.proargtypes[1]) + (p1.proargtypes[1] < p2.proargtypes[1]) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; proargtypes | proargtypes -------------+------------- @@ -1015,19 +1021,30 @@ ORDER BY 1, 2, 3; 403 | 5 | ~>~ 405 | 1 | = 783 | 1 | << + 783 | 1 | = 783 | 1 | @@ 783 | 2 | &< + 783 | 2 | <> 783 | 3 | && 783 | 4 | &> + 783 | 4 | @> + 783 | 5 | <@ 783 | 5 | >> + 783 | 6 | @> 783 | 6 | ~= + 783 | 7 | <@ 783 | 7 | @> + 783 | 8 | << 783 | 8 | <@ 783 | 9 | &<| + 783 | 9 | >> + 783 | 10 | &< 783 | 10 | <<| 783 | 10 | <^ + 783 | 11 | &> 783 | 11 | >^ 783 | 11 | |>> + 783 | 12 | -|- 783 | 12 | |&> 783 | 13 | ~ 783 | 14 | @ @@ -1044,7 +1061,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = -(40 rows) +(51 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing @@ -1053,9 +1070,15 @@ SELECT p1.amopfamily, p1.amopopr, p2.oid, p2.oprname FROM pg_amop AS p1, pg_operator AS p2 WHERE p1.amopopr = p2.oid AND p1.amoppurpose = 's' AND (p2.oprrest = 0 OR p2.oprjoin = 0); - amopfamily | amopopr | oid | oprname -------------+---------+-----+--------- -(0 rows) + amopfamily | amopopr | oid | oprname +------------+---------+------+--------- + 3919 | 3888 | 3888 | && + 3919 | 3889 | 3889 | @> + 3919 | 3891 | 3891 | <@ + 3919 | 3890 | 3890 | @> + 3919 | 3892 | 3892 | <@ + 3919 | 3897 | 3897 | -|- +(6 rows) -- Check that each opclass in an opfamily has associated operators, that is -- ones whose oprleft matches opcintype (possibly by coercion). diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 238bf5f0aec..fc9d4019444 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -4571,3 +4571,17 @@ ERROR: value for domain orderedarray violates check constraint "sorted" CONTEXT: PL/pgSQL function "testoa" line 5 at assignment drop function arrayassign1(); drop function testoa(x1 int, x2 int, x3 int); +-- Test resolve_polymorphic_argtypes() codepath. It is only taken when +-- a function is invoked from a different backend from where it's defined, +-- so we create the a function with polymorphic argument, reconnect, and +-- and then call it. +create function rangetypes_plpgsql(out a anyelement, b anyrange, c anyarray) + language plpgsql as + $$ begin a := upper(b) + c[1]; return; end; $$; +\c - +select rangetypes_plpgsql(int4range(1,10),ARRAY[2,20]); + rangetypes_plpgsql +-------------------- + 12 +(1 row) + diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out new file mode 100644 index 00000000000..495508c42ec --- /dev/null +++ b/src/test/regress/expected/rangetypes.out @@ -0,0 +1,951 @@ +-- +-- test parser +-- +create type textrange as range (subtype=text, collation="C"); +-- negative tests; should fail +select ''::textrange; +ERROR: malformed range literal: "" +LINE 1: select ''::textrange; + ^ +DETAIL: Missing left parenthesis or bracket. +select '-[a,z)'::textrange; +ERROR: malformed range literal: "-[a,z)" +LINE 1: select '-[a,z)'::textrange; + ^ +DETAIL: Missing left parenthesis or bracket. +select '[a,z) - '::textrange; +ERROR: malformed range literal: "[a,z) - " +LINE 1: select '[a,z) - '::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +select '(",a)'::textrange; +ERROR: malformed range literal: "(",a)" +LINE 1: select '(",a)'::textrange; + ^ +DETAIL: Unexpected end of input. +select '(,,a)'::textrange; +ERROR: malformed range literal: "(,,a)" +LINE 1: select '(,,a)'::textrange; + ^ +DETAIL: Too many boundaries. +select '(),a)'::textrange; +ERROR: malformed range literal: "(),a)" +LINE 1: select '(),a)'::textrange; + ^ +DETAIL: Missing upper bound. +select '(a,))'::textrange; +ERROR: malformed range literal: "(a,))" +LINE 1: select '(a,))'::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +select '(],a)'::textrange; +ERROR: malformed range literal: "(],a)" +LINE 1: select '(],a)'::textrange; + ^ +DETAIL: Missing upper bound. +select '(a,])'::textrange; +ERROR: malformed range literal: "(a,])" +LINE 1: select '(a,])'::textrange; + ^ +DETAIL: Junk after right parenthesis or bracket. +-- should succeed +select ' empty '::textrange; + textrange +----------- + empty +(1 row) + +select ' ( empty, empty ) '::textrange; + textrange +---------------------- + (" empty"," empty ") +(1 row) + +select ' ( " a " " a ", " z " " z " ) '::textrange; + textrange +-------------------------- + (" a a "," z z ") +(1 row) + +select '(,z)'::textrange; + textrange +----------- + (,z) +(1 row) + +select '(a,)'::textrange; + textrange +----------- + (a,) +(1 row) + +select '[,z]'::textrange; + textrange +----------- + (,z] +(1 row) + +select '[a,]'::textrange; + textrange +----------- + [a,) +(1 row) + +select '( , )'::textrange; + textrange +----------- + (" "," ") +(1 row) + +select '("","")'::textrange; + textrange +----------- + ("","") +(1 row) + +select '["",""]'::textrange; + textrange +----------- + ["",""] +(1 row) + +select '(",",",")'::textrange; + textrange +----------- + (",",",") +(1 row) + +select '("\\","\\")'::textrange +select '(\\,a)'::textrange; +ERROR: syntax error at or near "select" +LINE 2: select '(\\,a)'::textrange; + ^ +select '((,z)'::textrange; + textrange +----------- + ("(",z) +(1 row) + +select '([,z)'::textrange; + textrange +----------- + ("[",z) +(1 row) + +select '(!,()'::textrange; + textrange +----------- + (!,"(") +(1 row) + +select '(!,[)'::textrange; + textrange +----------- + (!,"[") +(1 row) + +drop type textrange; +-- +-- create some test data and test the operators +-- +CREATE TABLE numrange_test (nr NUMRANGE); +create index numrange_test_btree on numrange_test(nr); +SET enable_seqscan = f; +INSERT INTO numrange_test VALUES('[,)'); +INSERT INTO numrange_test VALUES('[3,]'); +INSERT INTO numrange_test VALUES('[, 5)'); +INSERT INTO numrange_test VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test VALUES('empty'); +INSERT INTO numrange_test VALUES(numrange(1.7)); +SELECT isempty(nr) FROM numrange_test; + isempty +--------- + f + f + f + f + t + f +(6 rows) + +SELECT lower_inc(nr), lower(nr), upper(nr), upper_inc(nr) FROM numrange_test + WHERE NOT isempty(nr) AND NOT lower_inf(nr) AND NOT upper_inf(nr); + lower_inc | lower | upper | upper_inc +-----------+-------+-------+----------- + t | 1.1 | 2.2 | f + t | 1.7 | 1.7 | t +(2 rows) + +SELECT * FROM numrange_test WHERE contains(nr, numrange(1.9,1.91)); + nr +----------- + (,) + (,5) + [1.1,2.2) +(3 rows) + +SELECT * FROM numrange_test WHERE nr @> numrange(1.0,10000.1); + nr +----- + (,) +(1 row) + +SELECT * FROM numrange_test WHERE contained_by(numrange(-1e7,-10000.1), nr); + nr +------ + (,) + (,5) +(2 rows) + +SELECT * FROM numrange_test WHERE 1.9 <@ nr; + nr +----------- + (,) + (,5) + [1.1,2.2) +(3 rows) + +SELECT * FROM numrange_test WHERE nr = 'empty'; + nr +------- + empty +(1 row) + +SELECT * FROM numrange_test WHERE range_eq(nr, '(1.1, 2.2)'); + nr +---- +(0 rows) + +SELECT * FROM numrange_test WHERE nr = '[1.1, 2.2)'; + nr +----------- + [1.1,2.2) +(1 row) + +select numrange(2.0, 1.0); +ERROR: range lower bound must be less than or equal to range upper bound +select numrange(2.0, 3.0) -|- numrange(3.0, 4.0); + ?column? +---------- + t +(1 row) + +select adjacent(numrange(2.0, 3.0), numrange(3.1, 4.0)); + adjacent +---------- + f +(1 row) + +select numrange(2.0, 3.0, '[]') -|- numrange(3.0, 4.0, '()'); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) -|- numrange(2.0, 3.0,'[]'); + ?column? +---------- + t +(1 row) + +select adjacent(numrange(2.0, 3.0, '(]'), numrange(1.0, 2.0, '(]')); + adjacent +---------- + t +(1 row) + +select numrange(1.1, 3.3) <@ numrange(0.1,10.1); + ?column? +---------- + t +(1 row) + +select numrange(0.1, 10.1) <@ numrange(1.1,3.3); + ?column? +---------- + f +(1 row) + +select numrange(1.1, 2.2) - numrange(2.0, 3.0); + ?column? +----------- + [1.1,2.0) +(1 row) + +select numrange(1.1, 2.2) - numrange(2.2, 3.0); + ?column? +----------- + [1.1,2.2) +(1 row) + +select numrange(1.1, 2.2,'[]') - numrange(2.0, 3.0); + ?column? +----------- + [1.1,2.0) +(1 row) + +select minus(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]')); + minus +------------- + [10.1,12.2] +(1 row) + +select minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]')); + minus +------- + empty +(1 row) + +select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) << numrange(3.0, 4.0); + ?column? +---------- + t +(1 row) + +select numrange(1.0, 2.0) >> numrange(3.0, 4.0); + ?column? +---------- + f +(1 row) + +select numrange(3.0, 70.0) &< numrange(6.6, 100.0); + ?column? +---------- + t +(1 row) + +select numrange(1.1, 2.2) < numrange(1.0, 200.2); + ?column? +---------- + f +(1 row) + +select numrange(1.1, 2.2) < numrange(1.1, 1.2); + ?column? +---------- + f +(1 row) + +select numrange(1.0, 2.0) + numrange(2.0, 3.0); + ?column? +----------- + [1.0,3.0) +(1 row) + +select numrange(1.0, 2.0) + numrange(1.5, 3.0); + ?column? +----------- + [1.0,3.0) +(1 row) + +select numrange(1.0, 2.0) + numrange(2.5, 3.0); +ERROR: result range is not contiguous +select numrange(1.0, 2.0) * numrange(2.0, 3.0); + ?column? +---------- + empty +(1 row) + +select numrange(1.0, 2.0) * numrange(1.5, 3.0); + ?column? +----------- + [1.5,2.0) +(1 row) + +select numrange(1.0, 2.0) * numrange(2.5, 3.0); + ?column? +---------- + empty +(1 row) + +select * from numrange_test where nr < numrange(-1000.0, -1000.0,'[]'); + nr +------- + (,) + (,5) + empty +(3 rows) + +select * from numrange_test where nr < numrange(0.0, 1.0,'[]'); + nr +------- + (,) + (,5) + empty +(3 rows) + +select * from numrange_test where nr < numrange(1000.0, 1001.0,'[]'); + nr +----------- + (,) + [3,) + (,5) + [1.1,2.2) + empty + [1.7,1.7] +(6 rows) + +select * from numrange_test where nr > numrange(-1001.0, -1000.0,'[]'); + nr +----------- + [3,) + [1.1,2.2) + [1.7,1.7] +(3 rows) + +select * from numrange_test where nr > numrange(0.0, 1.0,'[]'); + nr +----------- + [3,) + [1.1,2.2) + [1.7,1.7] +(3 rows) + +select * from numrange_test where nr > numrange(1000.0, 1000.0,'[]'); + nr +---- +(0 rows) + +create table numrange_test2(nr numrange); +create index numrange_test2_hash_idx on numrange_test2 (nr); +INSERT INTO numrange_test2 VALUES('[, 5)'); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2,'()')); +INSERT INTO numrange_test2 VALUES('empty'); +select * from numrange_test2 where nr = 'empty'::numrange; + nr +------- + empty +(1 row) + +select * from numrange_test2 where nr = numrange(1.1, 2.2); + nr +----------- + [1.1,2.2) + [1.1,2.2) +(2 rows) + +select * from numrange_test2 where nr = numrange(1.1, 2.3); + nr +---- +(0 rows) + +set enable_nestloop=t; +set enable_hashjoin=f; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop=f; +set enable_hashjoin=t; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop=f; +set enable_hashjoin=f; +set enable_mergejoin=t; +select * from numrange_test natural join numrange_test2 order by nr; + nr +----------- + empty + (,5) + [1.1,2.2) + [1.1,2.2) +(4 rows) + +set enable_nestloop to default; +set enable_hashjoin to default; +set enable_mergejoin to default; +SET enable_seqscan TO DEFAULT; +DROP TABLE numrange_test; +DROP TABLE numrange_test2; +-- test canonical form for int4range +select int4range(1,10,'[]'); + int4range +----------- + [1,11) +(1 row) + +select int4range(1,10,'[)'); + int4range +----------- + [1,10) +(1 row) + +select int4range(1,10,'(]'); + int4range +----------- + [2,11) +(1 row) + +select int4range(1,10,'[]'); + int4range +----------- + [1,11) +(1 row) + +-- test canonical form for daterange +select daterange('2000-01-10'::date, '2000-01-20'::date,'[]'); + daterange +------------------------- + [01-10-2000,01-21-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date,'[)'); + daterange +------------------------- + [01-10-2000,01-20-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date,'(]'); + daterange +------------------------- + [01-11-2000,01-21-2000) +(1 row) + +select daterange('2000-01-10'::date, '2000-01-20'::date,'[]'); + daterange +------------------------- + [01-10-2000,01-21-2000) +(1 row) + +create table test_range_gist(ir int4range); +create index test_range_gist_idx on test_range_gist using gist (ir); +insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g; +insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g; +insert into test_range_gist select int4range(g, g+10000) from generate_series(1,1000) g; +insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g; +insert into test_range_gist select int4range(NULL,g*10,'(]') from generate_series(1,100) g; +insert into test_range_gist select int4range(g*10,NULL,'(]') from generate_series(1,100) g; +insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g; +BEGIN; +SET LOCAL enable_seqscan = t; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = f; +select count(*) from test_range_gist where ir @> 'empty'::int4range; + count +------- + 6200 +(1 row) + +select count(*) from test_range_gist where ir = int4range(10,20); + count +------- + 2 +(1 row) + +select count(*) from test_range_gist where ir @> 10; + count +------- + 130 +(1 row) + +select count(*) from test_range_gist where ir @> int4range(10,20); + count +------- + 111 +(1 row) + +select count(*) from test_range_gist where ir && int4range(10,20); + count +------- + 158 +(1 row) + +select count(*) from test_range_gist where ir <@ int4range(10,50); + count +------- + 1062 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir << int4range(100,500); + count +------- + 189 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir >> int4range(100,500); + count +------- + 3554 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir &< int4range(100,500); + count +------- + 1029 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir &> int4range(100,500); + count +------- + 4794 +(1 row) + +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir -|- int4range(100,500); + count +------- + 5 +(1 row) + +COMMIT; +BEGIN; +SET LOCAL enable_seqscan = f; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = t; +select count(*) from test_range_gist where ir @> 'empty'::int4range; + count +------- + 6200 +(1 row) + +select count(*) from test_range_gist where ir = int4range(10,20); + count +------- + 2 +(1 row) + +select count(*) from test_range_gist where ir @> 10; + count +------- + 130 +(1 row) + +select count(*) from test_range_gist where ir @> int4range(10,20); + count +------- + 111 +(1 row) + +select count(*) from test_range_gist where ir && int4range(10,20); + count +------- + 158 +(1 row) + +select count(*) from test_range_gist where ir <@ int4range(10,50); + count +------- + 1062 +(1 row) + +select count(*) from test_range_gist where ir << int4range(100,500); + count +------- + 189 +(1 row) + +select count(*) from test_range_gist where ir >> int4range(100,500); + count +------- + 3554 +(1 row) + +select count(*) from test_range_gist where ir &< int4range(100,500); + count +------- + 1029 +(1 row) + +select count(*) from test_range_gist where ir &> int4range(100,500); + count +------- + 4794 +(1 row) + +select count(*) from test_range_gist where ir -|- int4range(100,500); + count +------- + 5 +(1 row) + +COMMIT; +drop index test_range_gist_idx; +create index test_range_gist_idx on test_range_gist using gist (ir); +BEGIN; +SET LOCAL enable_seqscan = f; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = t; +select count(*) from test_range_gist where ir @> 'empty'::int4range; + count +------- + 6200 +(1 row) + +select count(*) from test_range_gist where ir = int4range(10,20); + count +------- + 2 +(1 row) + +select count(*) from test_range_gist where ir @> 10; + count +------- + 130 +(1 row) + +select count(*) from test_range_gist where ir @> int4range(10,20); + count +------- + 111 +(1 row) + +select count(*) from test_range_gist where ir && int4range(10,20); + count +------- + 158 +(1 row) + +select count(*) from test_range_gist where ir <@ int4range(10,50); + count +------- + 1062 +(1 row) + +select count(*) from test_range_gist where ir << int4range(100,500); + count +------- + 189 +(1 row) + +select count(*) from test_range_gist where ir >> int4range(100,500); + count +------- + 3554 +(1 row) + +select count(*) from test_range_gist where ir &< int4range(100,500); + count +------- + 1029 +(1 row) + +select count(*) from test_range_gist where ir &> int4range(100,500); + count +------- + 4794 +(1 row) + +select count(*) from test_range_gist where ir -|- int4range(100,500); + count +------- + 5 +(1 row) + +COMMIT; +drop table test_range_gist; +-- +-- Btree_gist is not included by default, so to test exclusion +-- constraints with range types, use singleton int ranges for the "=" +-- portion of the constraint. +-- +create table test_range_excl( + room int4range, + speaker int4range, + during tsrange, + exclude using gist (room with =, during with &&), + exclude using gist (speaker with =, during with &&) +); +NOTICE: CREATE TABLE / EXCLUDE will create implicit index "test_range_excl_room_during_excl" for table "test_range_excl" +NOTICE: CREATE TABLE / EXCLUDE will create implicit index "test_range_excl_speaker_during_excl" for table "test_range_excl" +insert into test_range_excl + values(int4range(123), int4range(1), '[2010-01-02 10:00, 2010-01-02 11:00)'); +insert into test_range_excl + values(int4range(123), int4range(2), '[2010-01-02 11:00, 2010-01-02 12:00)'); +insert into test_range_excl + values(int4range(123), int4range(3), '[2010-01-02 10:10, 2010-01-02 11:10)'); +ERROR: conflicting key value violates exclusion constraint "test_range_excl_room_during_excl" +DETAIL: Key (room, during)=([123,124), ["Sat Jan 02 10:10:00 2010","Sat Jan 02 11:10:00 2010")) conflicts with existing key (room, during)=([123,124), ["Sat Jan 02 10:00:00 2010","Sat Jan 02 11:00:00 2010")). +insert into test_range_excl + values(int4range(124), int4range(3), '[2010-01-02 10:10, 2010-01-02 11:10)'); +insert into test_range_excl + values(int4range(125), int4range(1), '[2010-01-02 10:10, 2010-01-02 11:10)'); +ERROR: conflicting key value violates exclusion constraint "test_range_excl_speaker_during_excl" +DETAIL: Key (speaker, during)=([1,2), ["Sat Jan 02 10:10:00 2010","Sat Jan 02 11:10:00 2010")) conflicts with existing key (speaker, during)=([1,2), ["Sat Jan 02 10:00:00 2010","Sat Jan 02 11:00:00 2010")). +drop table test_range_excl; +-- test bigint ranges +select int8range(10000000000::int8, 20000000000::int8,'(]'); + int8range +--------------------------- + [10000000001,20000000001) +(1 row) + +-- test tstz ranges +set timezone to '-08'; +select '[2010-01-01 01:00:00 -05, 2010-01-01 02:00:00 -08)'::tstzrange; + tstzrange +----------------------------------------------------------------- + ["Thu Dec 31 22:00:00 2009 -08","Fri Jan 01 02:00:00 2010 -08") +(1 row) + +-- should fail +select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)'::tstzrange; +ERROR: range lower bound must be less than or equal to range upper bound +LINE 1: select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)':... + ^ +set timezone to default; +-- +-- Test user-defined range of floats +-- +--should fail +create type float8range as range (subtype=float8, subtype_diff=float4mi); +ERROR: function float4mi(double precision, double precision) does not exist +--should succeed +create type float8range as range (subtype=float8, subtype_diff=float8mi); +select '[123.001, 5.e9)'::float8range @> 888.882::float8; + ?column? +---------- + t +(1 row) + +create table float8range_test(f8r float8range, i int); +insert into float8range_test values(float8range(-100.00007, '1.111113e9')); +select * from float8range_test; + f8r | i +-------------------------+--- + [-100.00007,1111113000) | +(1 row) + +drop table float8range_test; +drop type float8range; +-- +-- Test range types over domains +-- +create domain mydomain as int4; +create type mydomainrange as range(subtype=mydomain); +select '[4,50)'::mydomainrange @> 7::mydomain; + ?column? +---------- + t +(1 row) + +drop type mydomainrange; +drop domain mydomain; +-- +-- Test domains over range types +-- +create domain restrictedrange as int4range check (upper(value) < 10); +select '[4,5)'::restrictedrange @> 7; + ?column? +---------- + f +(1 row) + +select '[4,50)'::restrictedrange @> 7; -- should fail +ERROR: value for domain restrictedrange violates check constraint "restrictedrange_check" +drop domain restrictedrange; +-- +-- Test multiple range types over the same subtype +-- +create type textrange1 as range(subtype=text, collation="C"); +create type textrange2 as range(subtype=text, collation="C"); +select textrange1('a','Z') @> 'b'::text; +ERROR: range lower bound must be less than or equal to range upper bound +select textrange2('a','z') @> 'b'::text; + ?column? +---------- + t +(1 row) + +drop type textrange1; +drop type textrange2; +-- +-- Test out polymorphic type system +-- +create function anyarray_anyrange_func(a anyarray, r anyrange) + returns anyelement as 'select $1[1] + lower($2);' language sql; +select anyarray_anyrange_func(ARRAY[1,2], int4range(10,20)); + anyarray_anyrange_func +------------------------ + 11 +(1 row) + +-- should fail +select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); +ERROR: function anyarray_anyrange_func(integer[], numrange) does not exist +LINE 1: select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +drop function anyarray_anyrange_func(anyarray, anyrange); +-- should fail +create function bogus_func(anyelement) + returns anyrange as 'select int4range(1,10)' language sql; +ERROR: cannot determine result data type +DETAIL: A function returning ANYRANGE must have at least one ANYRANGE argument. +-- should fail +create function bogus_func(int) + returns anyrange as 'select int4range(1,10)' language sql; +ERROR: cannot determine result data type +DETAIL: A function returning a polymorphic type must have at least one polymorphic argument. +create function range_add_bounds(anyrange) + returns anyelement as 'select lower($1) + upper($1)' language sql; +select range_add_bounds(numrange(1.0001, 123.123)); + range_add_bounds +------------------ + 124.1231 +(1 row) + +-- +-- Arrays of ranges +-- +select ARRAY[numrange(1.1), numrange(12.3,155.5)]; + array +------------------------------ + {"[1.1,1.1]","[12.3,155.5)"} +(1 row) + +-- +-- Ranges of arrays +-- +create type arrayrange as range (subtype=int4[]); +select arrayrange(ARRAY[1,2], ARRAY[2,1]); + arrayrange +------------------- + ["{1,2}","{2,1}") +(1 row) + +drop type arrayrange; +-- +-- OUT/INOUT/TABLE functions +-- +create function outparam_succeed(i anyrange, out r anyrange, out t text) + as $$ select $1, 'foo' $$ language sql; +create function inoutparam_succeed(out i anyelement, inout r anyrange) + as $$ select $1, $2 $$ language sql; +create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange) + as $$ select $1, $2 $$ language sql; +-- should fail +create function outparam_fail(i anyelement, out r anyrange, out t text) + as $$ select '[1,10]', 'foo' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A function returning ANYRANGE must have at least one ANYRANGE argument. +--should fail +create function inoutparam_fail(inout i anyelement, out r anyrange) + as $$ select $1, '[1,10]' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A function returning ANYRANGE must have at least one ANYRANGE argument. +--should fail +create function table_succeed(i anyelement) returns table(i anyelement, r anyrange) + as $$ select $1, '[1,10]' $$ language sql; +ERROR: cannot determine result data type +DETAIL: A function returning ANYRANGE must have at least one ANYRANGE argument. diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index d42b0ea045b..38c88d977ad 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -116,6 +116,7 @@ SELECT relname, relhasindex pg_opfamily | t pg_pltemplate | t pg_proc | t + pg_range | t pg_rewrite | t pg_seclabel | t pg_shdepend | t @@ -158,7 +159,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(147 rows) +(148 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index e30ecbc6feb..7ca7a95ec66 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -17,7 +17,7 @@ SELECT p1.oid, p1.typname FROM pg_type as p1 WHERE p1.typnamespace = 0 OR (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR - (p1.typtype not in ('b', 'c', 'd', 'e', 'p')) OR + (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r')) OR NOT p1.typisdefined OR (p1.typalign not in ('c', 's', 'i', 'd')) OR (p1.typstorage not in ('p', 'x', 'e', 'm')); diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 376f28d99a1..70976d115cd 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -13,7 +13,7 @@ test: tablespace # ---------- # The first group of parallel tests # ---------- -test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric txid uuid enum money +test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric txid uuid enum money rangetypes # Depends on things setup during char, varchar and text test: strings diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index bb654f9c612..2e87d9eefd6 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -18,6 +18,7 @@ test: txid test: uuid test: enum test: money +test: rangetypes test: strings test: numerology test: point diff --git a/src/test/regress/sql/collate.linux.utf8.sql b/src/test/regress/sql/collate.linux.utf8.sql index 73f5587b700..3b7cc6cf2bc 100644 --- a/src/test/regress/sql/collate.linux.utf8.sql +++ b/src/test/regress/sql/collate.linux.utf8.sql @@ -385,3 +385,14 @@ DROP COLLATION test0 CASCADE; DROP TABLE collate_dep_test1, collate_dep_test4t; DROP TYPE collate_dep_test2; + +-- test range types and collations + +create type textrange_c as range(subtype=text, collation="C"); +create type textrange_en_us as range(subtype=text, collation="en_US"); + +select textrange_c('A','Z') @> 'b'::text; +select textrange_en_us('A','Z') @> 'b'::text; + +drop type textrange_c; +drop type textrange_en_us; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 65ae868d989..7f936c81547 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -133,7 +133,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.prorettype < p2.prorettype) + (p1.prorettype < p2.prorettype) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; SELECT DISTINCT p1.proargtypes[0], p2.proargtypes[0] @@ -142,7 +144,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.proargtypes[0] < p2.proargtypes[0]) + (p1.proargtypes[0] < p2.proargtypes[0]) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; SELECT DISTINCT p1.proargtypes[1], p2.proargtypes[1] @@ -151,7 +155,9 @@ WHERE p1.oid != p2.oid AND p1.prosrc = p2.prosrc AND p1.prolang = 12 AND p2.prolang = 12 AND NOT p1.proisagg AND NOT p2.proisagg AND - (p1.proargtypes[1] < p2.proargtypes[1]) + (p1.proargtypes[1] < p2.proargtypes[1]) AND + -- range constructor functions are shared by all range types. + NOT p1.prosrc LIKE 'range_constructor%' ORDER BY 1, 2; SELECT DISTINCT p1.proargtypes[2], p2.proargtypes[2] diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index b47c2de312a..2906943f06f 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -3600,3 +3600,13 @@ select testoa(1,2,1); -- fail at update drop function arrayassign1(); drop function testoa(x1 int, x2 int, x3 int); + +-- Test resolve_polymorphic_argtypes() codepath. It is only taken when +-- a function is invoked from a different backend from where it's defined, +-- so we create the a function with polymorphic argument, reconnect, and +-- and then call it. +create function rangetypes_plpgsql(out a anyelement, b anyrange, c anyarray) + language plpgsql as + $$ begin a := upper(b) + c[1]; return; end; $$; +\c - +select rangetypes_plpgsql(int4range(1,10),ARRAY[2,20]); diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql new file mode 100644 index 00000000000..44885c870cf --- /dev/null +++ b/src/test/regress/sql/rangetypes.sql @@ -0,0 +1,371 @@ + +-- +-- test parser +-- + +create type textrange as range (subtype=text, collation="C"); + +-- negative tests; should fail +select ''::textrange; +select '-[a,z)'::textrange; +select '[a,z) - '::textrange; +select '(",a)'::textrange; +select '(,,a)'::textrange; +select '(),a)'::textrange; +select '(a,))'::textrange; +select '(],a)'::textrange; +select '(a,])'::textrange; + +-- should succeed +select ' empty '::textrange; +select ' ( empty, empty ) '::textrange; +select ' ( " a " " a ", " z " " z " ) '::textrange; +select '(,z)'::textrange; +select '(a,)'::textrange; +select '[,z]'::textrange; +select '[a,]'::textrange; +select '( , )'::textrange; +select '("","")'::textrange; +select '["",""]'::textrange; +select '(",",",")'::textrange; +select '("\\","\\")'::textrange +select '(\\,a)'::textrange; +select '((,z)'::textrange; +select '([,z)'::textrange; +select '(!,()'::textrange; +select '(!,[)'::textrange; + +drop type textrange; + +-- +-- create some test data and test the operators +-- + +CREATE TABLE numrange_test (nr NUMRANGE); +create index numrange_test_btree on numrange_test(nr); +SET enable_seqscan = f; + +INSERT INTO numrange_test VALUES('[,)'); +INSERT INTO numrange_test VALUES('[3,]'); +INSERT INTO numrange_test VALUES('[, 5)'); +INSERT INTO numrange_test VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test VALUES('empty'); +INSERT INTO numrange_test VALUES(numrange(1.7)); + +SELECT isempty(nr) FROM numrange_test; +SELECT lower_inc(nr), lower(nr), upper(nr), upper_inc(nr) FROM numrange_test + WHERE NOT isempty(nr) AND NOT lower_inf(nr) AND NOT upper_inf(nr); + +SELECT * FROM numrange_test WHERE contains(nr, numrange(1.9,1.91)); +SELECT * FROM numrange_test WHERE nr @> numrange(1.0,10000.1); +SELECT * FROM numrange_test WHERE contained_by(numrange(-1e7,-10000.1), nr); +SELECT * FROM numrange_test WHERE 1.9 <@ nr; +SELECT * FROM numrange_test WHERE nr = 'empty'; +SELECT * FROM numrange_test WHERE range_eq(nr, '(1.1, 2.2)'); +SELECT * FROM numrange_test WHERE nr = '[1.1, 2.2)'; + +select numrange(2.0, 1.0); + +select numrange(2.0, 3.0) -|- numrange(3.0, 4.0); +select adjacent(numrange(2.0, 3.0), numrange(3.1, 4.0)); +select numrange(2.0, 3.0, '[]') -|- numrange(3.0, 4.0, '()'); +select numrange(1.0, 2.0) -|- numrange(2.0, 3.0,'[]'); +select adjacent(numrange(2.0, 3.0, '(]'), numrange(1.0, 2.0, '(]')); + +select numrange(1.1, 3.3) <@ numrange(0.1,10.1); +select numrange(0.1, 10.1) <@ numrange(1.1,3.3); + +select numrange(1.1, 2.2) - numrange(2.0, 3.0); +select numrange(1.1, 2.2) - numrange(2.2, 3.0); +select numrange(1.1, 2.2,'[]') - numrange(2.0, 3.0); +select minus(numrange(10.1,12.2,'[]'), numrange(110.0,120.2,'(]')); +select minus(numrange(10.1,12.2,'[]'), numrange(0.0,120.2,'(]')); + +select numrange(4.5, 5.5, '[]') && numrange(5.5, 6.5); +select numrange(1.0, 2.0) << numrange(3.0, 4.0); +select numrange(1.0, 2.0) >> numrange(3.0, 4.0); +select numrange(3.0, 70.0) &< numrange(6.6, 100.0); + +select numrange(1.1, 2.2) < numrange(1.0, 200.2); +select numrange(1.1, 2.2) < numrange(1.1, 1.2); + +select numrange(1.0, 2.0) + numrange(2.0, 3.0); +select numrange(1.0, 2.0) + numrange(1.5, 3.0); +select numrange(1.0, 2.0) + numrange(2.5, 3.0); + +select numrange(1.0, 2.0) * numrange(2.0, 3.0); +select numrange(1.0, 2.0) * numrange(1.5, 3.0); +select numrange(1.0, 2.0) * numrange(2.5, 3.0); + +select * from numrange_test where nr < numrange(-1000.0, -1000.0,'[]'); +select * from numrange_test where nr < numrange(0.0, 1.0,'[]'); +select * from numrange_test where nr < numrange(1000.0, 1001.0,'[]'); +select * from numrange_test where nr > numrange(-1001.0, -1000.0,'[]'); +select * from numrange_test where nr > numrange(0.0, 1.0,'[]'); +select * from numrange_test where nr > numrange(1000.0, 1000.0,'[]'); + +create table numrange_test2(nr numrange); +create index numrange_test2_hash_idx on numrange_test2 (nr); +INSERT INTO numrange_test2 VALUES('[, 5)'); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2)); +INSERT INTO numrange_test2 VALUES(numrange(1.1, 2.2,'()')); +INSERT INTO numrange_test2 VALUES('empty'); + +select * from numrange_test2 where nr = 'empty'::numrange; +select * from numrange_test2 where nr = numrange(1.1, 2.2); +select * from numrange_test2 where nr = numrange(1.1, 2.3); + +set enable_nestloop=t; +set enable_hashjoin=f; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; +set enable_nestloop=f; +set enable_hashjoin=t; +set enable_mergejoin=f; +select * from numrange_test natural join numrange_test2 order by nr; +set enable_nestloop=f; +set enable_hashjoin=f; +set enable_mergejoin=t; +select * from numrange_test natural join numrange_test2 order by nr; + +set enable_nestloop to default; +set enable_hashjoin to default; +set enable_mergejoin to default; +SET enable_seqscan TO DEFAULT; +DROP TABLE numrange_test; +DROP TABLE numrange_test2; + +-- test canonical form for int4range +select int4range(1,10,'[]'); +select int4range(1,10,'[)'); +select int4range(1,10,'(]'); +select int4range(1,10,'[]'); + +-- test canonical form for daterange +select daterange('2000-01-10'::date, '2000-01-20'::date,'[]'); +select daterange('2000-01-10'::date, '2000-01-20'::date,'[)'); +select daterange('2000-01-10'::date, '2000-01-20'::date,'(]'); +select daterange('2000-01-10'::date, '2000-01-20'::date,'[]'); + +create table test_range_gist(ir int4range); +create index test_range_gist_idx on test_range_gist using gist (ir); + +insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g; +insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g; +insert into test_range_gist select int4range(g, g+10000) from generate_series(1,1000) g; +insert into test_range_gist select 'empty'::int4range from generate_series(1,500) g; +insert into test_range_gist select int4range(NULL,g*10,'(]') from generate_series(1,100) g; +insert into test_range_gist select int4range(g*10,NULL,'(]') from generate_series(1,100) g; +insert into test_range_gist select int4range(g, g+10) from generate_series(1,2000) g; + +BEGIN; +SET LOCAL enable_seqscan = t; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = f; + +select count(*) from test_range_gist where ir @> 'empty'::int4range; +select count(*) from test_range_gist where ir = int4range(10,20); +select count(*) from test_range_gist where ir @> 10; +select count(*) from test_range_gist where ir @> int4range(10,20); +select count(*) from test_range_gist where ir && int4range(10,20); +select count(*) from test_range_gist where ir <@ int4range(10,50); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir << int4range(100,500); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir >> int4range(100,500); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir &< int4range(100,500); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir &> int4range(100,500); +select count(*) from (select * from test_range_gist where not isempty(ir)) s where ir -|- int4range(100,500); +COMMIT; + +BEGIN; +SET LOCAL enable_seqscan = f; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = t; + +select count(*) from test_range_gist where ir @> 'empty'::int4range; +select count(*) from test_range_gist where ir = int4range(10,20); +select count(*) from test_range_gist where ir @> 10; +select count(*) from test_range_gist where ir @> int4range(10,20); +select count(*) from test_range_gist where ir && int4range(10,20); +select count(*) from test_range_gist where ir <@ int4range(10,50); +select count(*) from test_range_gist where ir << int4range(100,500); +select count(*) from test_range_gist where ir >> int4range(100,500); +select count(*) from test_range_gist where ir &< int4range(100,500); +select count(*) from test_range_gist where ir &> int4range(100,500); +select count(*) from test_range_gist where ir -|- int4range(100,500); +COMMIT; + +drop index test_range_gist_idx; +create index test_range_gist_idx on test_range_gist using gist (ir); + +BEGIN; +SET LOCAL enable_seqscan = f; +SET LOCAL enable_bitmapscan = f; +SET LOCAL enable_indexscan = t; + +select count(*) from test_range_gist where ir @> 'empty'::int4range; +select count(*) from test_range_gist where ir = int4range(10,20); +select count(*) from test_range_gist where ir @> 10; +select count(*) from test_range_gist where ir @> int4range(10,20); +select count(*) from test_range_gist where ir && int4range(10,20); +select count(*) from test_range_gist where ir <@ int4range(10,50); +select count(*) from test_range_gist where ir << int4range(100,500); +select count(*) from test_range_gist where ir >> int4range(100,500); +select count(*) from test_range_gist where ir &< int4range(100,500); +select count(*) from test_range_gist where ir &> int4range(100,500); +select count(*) from test_range_gist where ir -|- int4range(100,500); +COMMIT; + +drop table test_range_gist; + +-- +-- Btree_gist is not included by default, so to test exclusion +-- constraints with range types, use singleton int ranges for the "=" +-- portion of the constraint. +-- + +create table test_range_excl( + room int4range, + speaker int4range, + during tsrange, + exclude using gist (room with =, during with &&), + exclude using gist (speaker with =, during with &&) +); + +insert into test_range_excl + values(int4range(123), int4range(1), '[2010-01-02 10:00, 2010-01-02 11:00)'); +insert into test_range_excl + values(int4range(123), int4range(2), '[2010-01-02 11:00, 2010-01-02 12:00)'); +insert into test_range_excl + values(int4range(123), int4range(3), '[2010-01-02 10:10, 2010-01-02 11:10)'); +insert into test_range_excl + values(int4range(124), int4range(3), '[2010-01-02 10:10, 2010-01-02 11:10)'); +insert into test_range_excl + values(int4range(125), int4range(1), '[2010-01-02 10:10, 2010-01-02 11:10)'); + +drop table test_range_excl; + +-- test bigint ranges +select int8range(10000000000::int8, 20000000000::int8,'(]'); +-- test tstz ranges +set timezone to '-08'; +select '[2010-01-01 01:00:00 -05, 2010-01-01 02:00:00 -08)'::tstzrange; +-- should fail +select '[2010-01-01 01:00:00 -08, 2010-01-01 02:00:00 -05)'::tstzrange; +set timezone to default; + +-- +-- Test user-defined range of floats +-- + +--should fail +create type float8range as range (subtype=float8, subtype_diff=float4mi); + +--should succeed +create type float8range as range (subtype=float8, subtype_diff=float8mi); +select '[123.001, 5.e9)'::float8range @> 888.882::float8; +create table float8range_test(f8r float8range, i int); +insert into float8range_test values(float8range(-100.00007, '1.111113e9')); +select * from float8range_test; +drop table float8range_test; +drop type float8range; + +-- +-- Test range types over domains +-- + +create domain mydomain as int4; +create type mydomainrange as range(subtype=mydomain); +select '[4,50)'::mydomainrange @> 7::mydomain; +drop type mydomainrange; +drop domain mydomain; + +-- +-- Test domains over range types +-- + +create domain restrictedrange as int4range check (upper(value) < 10); +select '[4,5)'::restrictedrange @> 7; +select '[4,50)'::restrictedrange @> 7; -- should fail +drop domain restrictedrange; + +-- +-- Test multiple range types over the same subtype +-- + +create type textrange1 as range(subtype=text, collation="C"); +create type textrange2 as range(subtype=text, collation="C"); + +select textrange1('a','Z') @> 'b'::text; +select textrange2('a','z') @> 'b'::text; + +drop type textrange1; +drop type textrange2; + +-- +-- Test out polymorphic type system +-- + +create function anyarray_anyrange_func(a anyarray, r anyrange) + returns anyelement as 'select $1[1] + lower($2);' language sql; + +select anyarray_anyrange_func(ARRAY[1,2], int4range(10,20)); + +-- should fail +select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20)); + +drop function anyarray_anyrange_func(anyarray, anyrange); + +-- should fail +create function bogus_func(anyelement) + returns anyrange as 'select int4range(1,10)' language sql; + +-- should fail +create function bogus_func(int) + returns anyrange as 'select int4range(1,10)' language sql; + +create function range_add_bounds(anyrange) + returns anyelement as 'select lower($1) + upper($1)' language sql; + +select range_add_bounds(numrange(1.0001, 123.123)); + +-- +-- Arrays of ranges +-- + +select ARRAY[numrange(1.1), numrange(12.3,155.5)]; + +-- +-- Ranges of arrays +-- + +create type arrayrange as range (subtype=int4[]); + +select arrayrange(ARRAY[1,2], ARRAY[2,1]); + +drop type arrayrange; + +-- +-- OUT/INOUT/TABLE functions +-- + +create function outparam_succeed(i anyrange, out r anyrange, out t text) + as $$ select $1, 'foo' $$ language sql; + +create function inoutparam_succeed(out i anyelement, inout r anyrange) + as $$ select $1, $2 $$ language sql; + +create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange) + as $$ select $1, $2 $$ language sql; + +-- should fail +create function outparam_fail(i anyelement, out r anyrange, out t text) + as $$ select '[1,10]', 'foo' $$ language sql; + +--should fail +create function inoutparam_fail(inout i anyelement, out r anyrange) + as $$ select $1, '[1,10]' $$ language sql; + +--should fail +create function table_succeed(i anyelement) returns table(i anyelement, r anyrange) + as $$ select $1, '[1,10]' $$ language sql; diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index fa6dd75f07f..1638861bc1d 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -20,7 +20,7 @@ SELECT p1.oid, p1.typname FROM pg_type as p1 WHERE p1.typnamespace = 0 OR (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR - (p1.typtype not in ('b', 'c', 'd', 'e', 'p')) OR + (p1.typtype not in ('b', 'c', 'd', 'e', 'p', 'r')) OR NOT p1.typisdefined OR (p1.typalign not in ('c', 's', 'i', 'd')) OR (p1.typstorage not in ('p', 'x', 'e', 'm')); |