diff options
| author | Heikki Linnakangas | 2011-11-03 11:16:28 +0000 |
|---|---|---|
| committer | Heikki Linnakangas | 2011-11-03 11:42:15 +0000 |
| commit | 4429f6a9e3e12bb4af6e3677fbc78cd80f160252 (patch) | |
| tree | a2e272129e5515f7ef2f4e09989bddf0fd8158ea /src/backend/utils | |
| parent | 43342891861cc2d08dea2b1c8b190e15e5a36551 (diff) | |
Support range data types.
Selectivity estimation functions are missing for some range type operators,
which is a TODO.
Jeff Davis
Diffstat (limited to 'src/backend/utils')
| -rw-r--r-- | src/backend/utils/adt/Makefile | 4 | ||||
| -rw-r--r-- | src/backend/utils/adt/date.c | 1 | ||||
| -rw-r--r-- | src/backend/utils/adt/pseudotypes.c | 24 | ||||
| -rw-r--r-- | src/backend/utils/adt/rangetypes.c | 2153 | ||||
| -rw-r--r-- | src/backend/utils/adt/rangetypes_gist.c | 587 | ||||
| -rw-r--r-- | src/backend/utils/cache/lsyscache.c | 30 | ||||
| -rw-r--r-- | src/backend/utils/cache/syscache.c | 12 | ||||
| -rw-r--r-- | src/backend/utils/fmgr/funcapi.c | 115 |
8 files changed, 2911 insertions, 15 deletions
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) |
