diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c index 6836129c90d8..916734f675cf 100644 --- a/contrib/bloom/blutils.c +++ b/contrib/bloom/blutils.c @@ -112,6 +112,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amoptsprocnum = BLOOM_OPTIONS_PROC; amroutine->amcanorder = false; amroutine->amcanorderbyop = false; + amroutine->amorderbyopfirstcol = false; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = true; diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile index 073dcc745c4d..d54c615e9675 100644 --- a/contrib/btree_gist/Makefile +++ b/contrib/btree_gist/Makefile @@ -33,7 +33,8 @@ EXTENSION = btree_gist DATA = btree_gist--1.0--1.1.sql \ btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \ btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \ - btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql + btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql \ + btree_gist--1.7--1.8.sql PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes" REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \ diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c index 546b948ea400..398282732b4b 100644 --- a/contrib/btree_gist/btree_cash.c +++ b/contrib/btree_gist/btree_cash.c @@ -6,6 +6,7 @@ #include "btree_gist.h" #include "btree_utils_num.h" #include "common/int.h" +#include "utils/builtins.h" #include "utils/cash.h" typedef struct @@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(cash_dist); Datum cash_dist(PG_FUNCTION_ARGS) { - Cash a = PG_GETARG_CASH(0); - Cash b = PG_GETARG_CASH(1); - Cash r; - Cash ra; - - if (pg_sub_s64_overflow(a, b, &r) || - r == PG_INT64_MIN) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("money out of range"))); - - ra = i64abs(r); - - PG_RETURN_CASH(ra); + return cash_distance(fcinfo); } /************************************************** diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c index 68a4107dbf08..0262478265bc 100644 --- a/contrib/btree_gist/btree_date.c +++ b/contrib/btree_gist/btree_date.c @@ -118,12 +118,7 @@ PG_FUNCTION_INFO_V1(date_dist); Datum date_dist(PG_FUNCTION_ARGS) { - /* we assume the difference can't overflow */ - Datum diff = DirectFunctionCall2(date_mi, - PG_GETARG_DATUM(0), - PG_GETARG_DATUM(1)); - - PG_RETURN_INT32(abs(DatumGetInt32(diff))); + return date_distance(fcinfo); } diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c index 84ca5eee5012..7c9934feb885 100644 --- a/contrib/btree_gist/btree_float4.c +++ b/contrib/btree_gist/btree_float4.c @@ -6,6 +6,7 @@ #include "btree_gist.h" #include "btree_utils_num.h" #include "utils/float.h" +#include "utils/builtins.h" typedef struct float4key { @@ -94,15 +95,7 @@ PG_FUNCTION_INFO_V1(float4_dist); Datum float4_dist(PG_FUNCTION_ARGS) { - float4 a = PG_GETARG_FLOAT4(0); - float4 b = PG_GETARG_FLOAT4(1); - float4 r; - - r = a - b; - if (unlikely(isinf(r)) && !isinf(a) && !isinf(b)) - float_overflow_error(); - - PG_RETURN_FLOAT4(fabsf(r)); + return float4dist(fcinfo); } diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c index 081a719b0061..612f300059e7 100644 --- a/contrib/btree_gist/btree_float8.c +++ b/contrib/btree_gist/btree_float8.c @@ -6,6 +6,7 @@ #include "btree_gist.h" #include "btree_utils_num.h" #include "utils/float.h" +#include "utils/builtins.h" typedef struct float8key { @@ -102,15 +103,7 @@ PG_FUNCTION_INFO_V1(float8_dist); Datum float8_dist(PG_FUNCTION_ARGS) { - float8 a = PG_GETARG_FLOAT8(0); - float8 b = PG_GETARG_FLOAT8(1); - float8 r; - - r = a - b; - if (unlikely(isinf(r)) && !isinf(a) && !isinf(b)) - float_overflow_error(); - - PG_RETURN_FLOAT8(fabs(r)); + return float8dist(fcinfo); } /************************************************** diff --git a/contrib/btree_gist/btree_gist--1.7--1.8.sql b/contrib/btree_gist/btree_gist--1.7--1.8.sql new file mode 100644 index 000000000000..4dbdd13f4b12 --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.7--1.8.sql @@ -0,0 +1,90 @@ +/* contrib/btree_gist/btree_gist--1.7--1.8.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.8'" to load this file. \quit + +-- drop btree_gist distance operators from opfamilies + +ALTER OPERATOR FAMILY gist_int2_ops USING gist DROP OPERATOR 15 (int2, int2); +ALTER OPERATOR FAMILY gist_int4_ops USING gist DROP OPERATOR 15 (int4, int4); +ALTER OPERATOR FAMILY gist_int8_ops USING gist DROP OPERATOR 15 (int8, int8); +ALTER OPERATOR FAMILY gist_float4_ops USING gist DROP OPERATOR 15 (float4, float4); +ALTER OPERATOR FAMILY gist_float8_ops USING gist DROP OPERATOR 15 (float8, float8); +ALTER OPERATOR FAMILY gist_oid_ops USING gist DROP OPERATOR 15 (oid, oid); +ALTER OPERATOR FAMILY gist_cash_ops USING gist DROP OPERATOR 15 (money, money); +ALTER OPERATOR FAMILY gist_date_ops USING gist DROP OPERATOR 15 (date, date); +ALTER OPERATOR FAMILY gist_time_ops USING gist DROP OPERATOR 15 (time, time); +ALTER OPERATOR FAMILY gist_timestamp_ops USING gist DROP OPERATOR 15 (timestamp, timestamp); +ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist DROP OPERATOR 15 (timestamptz, timestamptz); +ALTER OPERATOR FAMILY gist_interval_ops USING gist DROP OPERATOR 15 (interval, interval); + +-- add pg_catalog distance operators to opfamilies + +ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops; +ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops; +ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops; +ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops; +ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops; +ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops; +ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (money, money) FOR ORDER BY pg_catalog.money_ops; +ALTER OPERATOR FAMILY gist_date_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (date, date) FOR ORDER BY pg_catalog.integer_ops; +ALTER OPERATOR FAMILY gist_time_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (time, time) FOR ORDER BY pg_catalog.interval_ops; +ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops; +ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops; +ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops; + +-- drop distance operators + +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int2, int2); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int4, int4); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int8, int8); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (float4, float4); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (float8, float8); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (oid, oid); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (money, money); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (date, date); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (time, time); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (timestamp, timestamp); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (timestamptz, timestamptz); +ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (interval, interval); + +DROP OPERATOR @extschema@.<-> (int2, int2); +DROP OPERATOR @extschema@.<-> (int4, int4); +DROP OPERATOR @extschema@.<-> (int8, int8); +DROP OPERATOR @extschema@.<-> (float4, float4); +DROP OPERATOR @extschema@.<-> (float8, float8); +DROP OPERATOR @extschema@.<-> (oid, oid); +DROP OPERATOR @extschema@.<-> (money, money); +DROP OPERATOR @extschema@.<-> (date, date); +DROP OPERATOR @extschema@.<-> (time, time); +DROP OPERATOR @extschema@.<-> (timestamp, timestamp); +DROP OPERATOR @extschema@.<-> (timestamptz, timestamptz); +DROP OPERATOR @extschema@.<-> (interval, interval); + +-- drop distance functions + +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int2_dist(int2, int2); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int4_dist(int4, int4); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int8_dist(int8, int8); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.float4_dist(float4, float4); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.float8_dist(float8, float8); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.oid_dist(oid, oid); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.cash_dist(money, money); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.date_dist(date, date); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.time_dist(time, time); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.ts_dist(timestamp, timestamp); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.tstz_dist(timestamptz, timestamptz); +ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.interval_dist(interval, interval); + +DROP FUNCTION @extschema@.int2_dist(int2, int2); +DROP FUNCTION @extschema@.int4_dist(int4, int4); +DROP FUNCTION @extschema@.int8_dist(int8, int8); +DROP FUNCTION @extschema@.float4_dist(float4, float4); +DROP FUNCTION @extschema@.float8_dist(float8, float8); +DROP FUNCTION @extschema@.oid_dist(oid, oid); +DROP FUNCTION @extschema@.cash_dist(money, money); +DROP FUNCTION @extschema@.date_dist(date, date); +DROP FUNCTION @extschema@.time_dist(time, time); +DROP FUNCTION @extschema@.ts_dist(timestamp, timestamp); +DROP FUNCTION @extschema@.tstz_dist(timestamptz, timestamptz); +DROP FUNCTION @extschema@.interval_dist(interval, interval); diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control index fa9171a80a2e..4c93737560b1 100644 --- a/contrib/btree_gist/btree_gist.control +++ b/contrib/btree_gist/btree_gist.control @@ -1,6 +1,6 @@ # btree_gist extension comment = 'support for indexing common datatypes in GiST' -default_version = '1.7' +default_version = '1.8' module_pathname = '$libdir/btree_gist' -relocatable = true -trusted = true +relocatable = false +trusted = true \ No newline at end of file diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c index fdbf156586c9..88214454b566 100644 --- a/contrib/btree_gist/btree_int2.c +++ b/contrib/btree_gist/btree_int2.c @@ -6,6 +6,7 @@ #include "btree_gist.h" #include "btree_utils_num.h" #include "common/int.h" +#include "utils/builtins.h" typedef struct int16key { @@ -94,20 +95,7 @@ PG_FUNCTION_INFO_V1(int2_dist); Datum int2_dist(PG_FUNCTION_ARGS) { - int16 a = PG_GETARG_INT16(0); - int16 b = PG_GETARG_INT16(1); - int16 r; - int16 ra; - - if (pg_sub_s16_overflow(a, b, &r) || - r == PG_INT16_MIN) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("smallint out of range"))); - - ra = abs(r); - - PG_RETURN_INT16(ra); + return int2dist(fcinfo); } diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c index 8915fb5d0877..2a4e2165f5be 100644 --- a/contrib/btree_gist/btree_int4.c +++ b/contrib/btree_gist/btree_int4.c @@ -6,6 +6,7 @@ #include "btree_gist.h" #include "btree_utils_num.h" #include "common/int.h" +#include "utils/builtins.h" typedef struct int32key { @@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(int4_dist); Datum int4_dist(PG_FUNCTION_ARGS) { - int32 a = PG_GETARG_INT32(0); - int32 b = PG_GETARG_INT32(1); - int32 r; - int32 ra; - - if (pg_sub_s32_overflow(a, b, &r) || - r == PG_INT32_MIN) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); - - ra = abs(r); - - PG_RETURN_INT32(ra); + return int4dist(fcinfo); } diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c index 7c63a5b6dc1f..11f34d6506db 100644 --- a/contrib/btree_gist/btree_int8.c +++ b/contrib/btree_gist/btree_int8.c @@ -6,6 +6,7 @@ #include "btree_gist.h" #include "btree_utils_num.h" #include "common/int.h" +#include "utils/builtins.h" typedef struct int64key { @@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(int8_dist); Datum int8_dist(PG_FUNCTION_ARGS) { - int64 a = PG_GETARG_INT64(0); - int64 b = PG_GETARG_INT64(1); - int64 r; - int64 ra; - - if (pg_sub_s64_overflow(a, b, &r) || - r == PG_INT64_MIN) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("bigint out of range"))); - - ra = i64abs(r); - - PG_RETURN_INT64(ra); + return int8dist(fcinfo); } diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c index 156f2cebac5d..fe7dc4729070 100644 --- a/contrib/btree_gist/btree_interval.c +++ b/contrib/btree_gist/btree_interval.c @@ -128,11 +128,7 @@ PG_FUNCTION_INFO_V1(interval_dist); Datum interval_dist(PG_FUNCTION_ARGS) { - Datum diff = DirectFunctionCall2(interval_mi, - PG_GETARG_DATUM(0), - PG_GETARG_DATUM(1)); - - PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff))); + return interval_distance(fcinfo); } diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c index 3cc7d4245d42..0561d86c6971 100644 --- a/contrib/btree_gist/btree_oid.c +++ b/contrib/btree_gist/btree_oid.c @@ -5,6 +5,7 @@ #include "btree_gist.h" #include "btree_utils_num.h" +#include "utils/builtins.h" typedef struct { @@ -100,15 +101,7 @@ PG_FUNCTION_INFO_V1(oid_dist); Datum oid_dist(PG_FUNCTION_ARGS) { - Oid a = PG_GETARG_OID(0); - Oid b = PG_GETARG_OID(1); - Oid res; - - if (a < b) - res = b - a; - else - res = a - b; - PG_RETURN_OID(res); + return oiddist(fcinfo); } diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c index d89401c0f51e..777082792fc8 100644 --- a/contrib/btree_gist/btree_time.c +++ b/contrib/btree_gist/btree_time.c @@ -141,11 +141,7 @@ PG_FUNCTION_INFO_V1(time_dist); Datum time_dist(PG_FUNCTION_ARGS) { - Datum diff = DirectFunctionCall2(time_mi_time, - PG_GETARG_DATUM(0), - PG_GETARG_DATUM(1)); - - PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff))); + return time_distance(fcinfo); } diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c index 3f5ba91891d8..db0e5304fbbd 100644 --- a/contrib/btree_gist/btree_ts.c +++ b/contrib/btree_gist/btree_ts.c @@ -146,48 +146,14 @@ PG_FUNCTION_INFO_V1(ts_dist); Datum ts_dist(PG_FUNCTION_ARGS) { - Timestamp a = PG_GETARG_TIMESTAMP(0); - Timestamp b = PG_GETARG_TIMESTAMP(1); - Interval *r; - - if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b)) - { - Interval *p = palloc(sizeof(Interval)); - - p->day = INT_MAX; - p->month = INT_MAX; - p->time = PG_INT64_MAX; - PG_RETURN_INTERVAL_P(p); - } - else - r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, - PG_GETARG_DATUM(0), - PG_GETARG_DATUM(1))); - PG_RETURN_INTERVAL_P(abs_interval(r)); + return timestamp_distance(fcinfo); } PG_FUNCTION_INFO_V1(tstz_dist); Datum tstz_dist(PG_FUNCTION_ARGS) { - TimestampTz a = PG_GETARG_TIMESTAMPTZ(0); - TimestampTz b = PG_GETARG_TIMESTAMPTZ(1); - Interval *r; - - if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b)) - { - Interval *p = palloc(sizeof(Interval)); - - p->day = INT_MAX; - p->month = INT_MAX; - p->time = PG_INT64_MAX; - PG_RETURN_INTERVAL_P(p); - } - - r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, - PG_GETARG_DATUM(0), - PG_GETARG_DATUM(1))); - PG_RETURN_INTERVAL_P(abs_interval(r)); + return timestamptz_distance(fcinfo); } diff --git a/doc/src/sgml/btree-gist.sgml b/doc/src/sgml/btree-gist.sgml index 31e7c78aaef4..9d5c57c3e701 100644 --- a/doc/src/sgml/btree-gist.sgml +++ b/doc/src/sgml/btree-gist.sgml @@ -102,6 +102,19 @@ INSERT 0 1 + Upgrade notes for version 1.6 + + + In version 1.6 btree_gist switched to using in-core + distance operators, and its own implementations were removed. References to + these operators in btree_gist opclasses will be updated + automatically during the extension upgrade, but if the user has created + objects referencing these operators or functions, then these objects must be + dropped manually before updating the extension. + + + + Authors diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml index 2b3997988cff..642e26d764b3 100644 --- a/doc/src/sgml/btree.sgml +++ b/doc/src/sgml/btree.sgml @@ -200,6 +200,53 @@ planner relies on them for optimization purposes. + + In order to implement the distance ordered (nearest-neighbor) search, + one needs to define a distance operator (usually it's called + <->) with a correpsonding operator family for + distance comparison in the operator class. These operators must + satisfy the following assumptions for all non-null values + A, B, + C of the data type: + + + + + A <-> + B = + B <-> + A + (symmetric law) + + + + + if A = + B, then A + <-> C + = B + <-> C + (distance equivalence) + + + + + if (A <= + B and B + <= C) or + (A >= + B and B + >= C), + then A <-> + B <= + A <-> + C + (monotonicity) + + + + + diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index e3c1539a1e3b..a69135cb7809 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -103,6 +103,11 @@ typedef struct IndexAmRoutine bool amcanorder; /* does AM support ORDER BY result of an operator on indexed column? */ bool amcanorderbyop; + /* + * Does AM support only the one ORDER BY operator on first indexed column? + * amcanorderbyop is implied. + */ + bool amorderbyopfirstcol; /* does AM support backward scanning? */ bool amcanbackward; /* does AM support UNIQUE indexes? */ @@ -932,7 +937,9 @@ amparallelrescan (IndexScanDesc scan); an order satisfying ORDER BY index_key operator constant. Scan modifiers of that form can be passed to amrescan as described - previously. + previously. If the access method supports the only one ORDER BY operator + on the first indexed column, then it should set + amorderbyopfirstcol to true. diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 6d731e0701fd..b841e1f0a54c 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -1193,6 +1193,17 @@ SELECT x FROM tab WHERE x = 'key' AND z < 42; make this type of scan very useful in practice. + + B-tree indexes are also capable of optimizing nearest-neighbor + searches, such as + date '2017-05-05' LIMIT 10; +]]> + + which finds the ten events closest to a given target date. The ability + to do this is again dependent on the particular operator class being used. + + INCLUDE diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml index 22d8ad1aac43..4636dce2a9a2 100644 --- a/doc/src/sgml/xindex.sgml +++ b/doc/src/sgml/xindex.sgml @@ -131,6 +131,10 @@ greater than 5 + + distance + 6 + @@ -1320,7 +1324,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING) Ordering Operators - Some index access methods (currently, only GiST and SP-GiST) support the concept of + Some index access methods (currently, B-tree, GiST and SP-GiST) + support the concept of ordering operators. What we have been discussing so far are search operators. A search operator is one for which the index can be searched to find all rows satisfying diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 6467bed604a0..c371f7269793 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -253,6 +253,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amoptsprocnum = BRIN_PROCNUM_OPTIONS; amroutine->amcanorder = false; amroutine->amcanorderbyop = false; + amroutine->amorderbyopfirstcol = false; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = true; diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index caf6991eb1b0..2617545b8c82 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -23,7 +23,7 @@ typedef struct MinmaxOpaque { Oid cached_subtype; - FmgrInfo strategy_procinfos[BTMaxStrategyNumber]; + FmgrInfo strategy_procinfos[BTMaxSearchStrategyNumber]; } MinmaxOpaque; static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, @@ -264,7 +264,7 @@ minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, MinmaxOpaque *opaque; Assert(strategynum >= 1 && - strategynum <= BTMaxStrategyNumber); + strategynum <= BTMaxSearchStrategyNumber); opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; @@ -277,7 +277,7 @@ minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, { uint16 i; - for (i = 1; i <= BTMaxStrategyNumber; i++) + for (i = 1; i <= BTMaxSearchStrategyNumber; i++) opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid; opaque->cached_subtype = subtype; } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 5747ae6a4cab..e9536fd92380 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -43,6 +43,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amoptsprocnum = GIN_OPTIONS_PROC; amroutine->amcanorder = false; amroutine->amcanorderbyop = false; + amroutine->amorderbyopfirstcol = false; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = true; diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index ed4ffa63a772..f6ed5023ef03 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -65,6 +65,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amoptsprocnum = GIST_OPTIONS_PROC; amroutine->amcanorder = false; amroutine->amcanorderbyop = true; + amroutine->amorderbyopfirstcol = false; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = true; diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 01d06b7c3289..5a090907bb31 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -63,6 +63,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amoptsprocnum = HASHOPTIONS_PROC; amroutine->amcanorder = false; amroutine->amcanorderbyop = false; + amroutine->amorderbyopfirstcol = false; amroutine->amcanbackward = true; amroutine->amcanunique = false; amroutine->amcanmulticol = false; diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README index 52e646c7f759..0db79b64d613 100644 --- a/src/backend/access/nbtree/README +++ b/src/backend/access/nbtree/README @@ -1081,3 +1081,25 @@ item is irrelevant, and need not be stored at all. This arrangement corresponds to the fact that an L&Y non-leaf page has one more pointer than key. Suffix truncation's negative infinity attributes behave in the same way. + +Nearest-neighbor search +----------------------- + +B-tree supports a special scan strategy for nearest-neighbor (kNN) search, +which is used for queries with "ORDER BY indexed_column operator constant" +clause. See the following example. + + SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k + +Unlike GiST and SP-GiST, B-tree supports kNN by the only one ordering operator +applied to the first indexed column. + +At the beginning of kNN scan, we determine the scan strategy to use: normal +unidirectional or special bidirectional. If the second distance operand falls +into the scan range, then we use bidirectional scan, otherwise we use normal +unidirectional scan. + +The bidirectional scan algorithm is quite simple. We start both forward and +backward scans starting from the tree location corresponding to the second +distance operand. Each time we need the next tuple, we return the nearest +tuple from two directions and advance scan in corresponding direction. diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 686a3206f726..7bf6b151631d 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -32,6 +32,8 @@ #include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/smgr.h" +#include "utils/builtins.h" +#include "utils/datum.h" #include "utils/fmgrprotos.h" #include "utils/index_selfuncs.h" #include "utils/memutils.h" @@ -66,7 +68,8 @@ typedef enum */ typedef struct BTParallelScanDescData { - BlockNumber btps_scanPage; /* latest or next page to be scanned */ + BlockNumber btps_forwardScanPage; /* latest or next page to be scanned */ + BlockNumber btps_backwardScanPage; /* secondary kNN page to be scanned */ BTPS_State btps_pageStatus; /* indicates whether next page is * available for scan. see above for * possible states of parallel scan. */ @@ -106,7 +109,8 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amsupport = BTNProcs; amroutine->amoptsprocnum = BTOPTIONS_PROC; amroutine->amcanorder = true; - amroutine->amcanorderbyop = false; + amroutine->amcanorderbyop = true; + amroutine->amorderbyopfirstcol = true; amroutine->amcanbackward = true; amroutine->amcanunique = true; amroutine->amcanmulticol = true; @@ -206,10 +210,19 @@ bool btgettuple(IndexScanDesc scan, ScanDirection dir) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanState state = &so->state; + ScanDirection arraydir = dir; bool res; + if (scan->numberOfOrderBys > 0 && !ScanDirectionIsForward(dir)) + elog(ERROR, "btree does not support backward order-by-distance scanning"); + /* btree indexes are never lossy */ scan->xs_recheck = false; + scan->xs_recheckorderby = false; + + if (so->scanDirection != NoMovementScanDirection) + dir = so->scanDirection; /* Each loop iteration performs another primitive index scan */ do @@ -219,7 +232,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir) * the appropriate direction. If we haven't done so yet, we call * _bt_first() to get the first item in the scan. */ - if (!BTScanPosIsValid(so->currPos)) + if (!BTScanPosIsValid(state->currPos) && + (!so->backwardState || !BTScanPosIsValid(so->backwardState->currPos))) res = _bt_first(scan, dir); else { @@ -237,11 +251,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir) * trying to optimize that, so we don't detect it, but instead * just forget any excess entries. */ - if (so->killedItems == NULL) - so->killedItems = (int *) + if (state->killedItems == NULL) + state->killedItems = (int *) palloc(MaxTIDsPerBTreePage * sizeof(int)); - if (so->numKilled < MaxTIDsPerBTreePage) - so->killedItems[so->numKilled++] = so->currPos.itemIndex; + if (state->numKilled < MaxTIDsPerBTreePage) + state->killedItems[state->numKilled++] = state->currPos.itemIndex; } /* @@ -254,7 +268,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir) if (res) break; /* ... otherwise see if we need another primitive index scan */ - } while (so->numArrayKeys && _bt_start_prim_scan(scan, dir)); + } while (so->numArrayKeys && _bt_start_prim_scan(scan, arraydir)); return res; } @@ -266,6 +280,7 @@ int64 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPos currPos = &so->state.currPos; int64 ntids = 0; ItemPointer heapTid; @@ -286,7 +301,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm) * Advance to next tuple within page. This is the same as the * easy case in _bt_next(). */ - if (++so->currPos.itemIndex > so->currPos.lastItem) + if (++currPos->itemIndex > currPos->lastItem) { /* let _bt_next do the heavy lifting */ if (!_bt_next(scan, ForwardScanDirection)) @@ -294,7 +309,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm) } /* Save tuple ID, and continue scanning */ - heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid; + heapTid = &currPos->items[currPos->itemIndex].heapTid; tbm_add_tuples(tbm, heapTid, 1, false); ntids++; } @@ -314,16 +329,13 @@ btbeginscan(Relation rel, int nkeys, int norderbys) IndexScanDesc scan; BTScanOpaque so; - /* no order by operators allowed */ - Assert(norderbys == 0); - /* get the scan */ scan = RelationGetIndexScan(rel, nkeys, norderbys); /* allocate private workspace */ so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData)); - BTScanPosInvalidate(so->currPos); - BTScanPosInvalidate(so->markPos); + BTScanPosInvalidate(so->state.currPos); + BTScanPosInvalidate(so->state.markPos); if (scan->numberOfKeys > 0) so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData)); else @@ -335,15 +347,18 @@ btbeginscan(Relation rel, int nkeys, int norderbys) so->orderProcs = NULL; so->arrayContext = NULL; - so->killedItems = NULL; /* until needed */ - so->numKilled = 0; + so->state.killedItems = NULL; /* until needed */ + so->state.numKilled = 0; /* * We don't know yet whether the scan will be index-only, so we do not * allocate the tuple workspace arrays until btrescan. However, we set up * scan->xs_itupdesc whether we'll need it or not, since that's so cheap. */ - so->currTuples = so->markTuples = NULL; + so->state.currTuples = so->state.markTuples = NULL; + so->backwardState = NULL; + so->distanceTypeByVal = true; + so->scanDirection = NoMovementScanDirection; scan->xs_itupdesc = RelationGetDescr(rel); @@ -352,6 +367,59 @@ btbeginscan(Relation rel, int nkeys, int norderbys) return scan; } +static void +_bt_release_current_position(BTScanState state, Relation indexRelation, + bool invalidate) +{ + /* we aren't holding any read locks, but gotta drop the pins */ + if (BTScanPosIsValid(state->currPos)) + { + /* Before leaving current page, deal with any killed items */ + if (state->numKilled > 0) + _bt_killitems(state, indexRelation); + + BTScanPosUnpinIfPinned(state->currPos); + + if (invalidate) + BTScanPosInvalidate(state->currPos); + } +} + +static void +_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + + /* No need to invalidate positions, if the RAM is about to be freed. */ + _bt_release_current_position(state, scan->indexRelation, !free); + + state->markItemIndex = -1; + BTScanPosUnpinIfPinned(state->markPos); + + if (free) + { + if (state->killedItems != NULL) + pfree(state->killedItems); + if (state->currTuples != NULL) + pfree(state->currTuples); + /* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */ + } + else + BTScanPosInvalidate(state->markPos); + + if (!so->distanceTypeByVal) + { + if (DatumGetPointer(state->currDistance)) + pfree(DatumGetPointer(state->currDistance)); + + if (DatumGetPointer(state->markDistance)) + pfree(DatumGetPointer(state->markDistance)); + } + + state->currDistance = (Datum) 0; + state->markDistance = (Datum) 0; +} + /* * btrescan() -- rescan an index relation */ @@ -360,22 +428,19 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, ScanKey orderbys, int norderbys) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanState state = &so->state; - /* we aren't holding any read locks, but gotta drop the pins */ - if (BTScanPosIsValid(so->currPos)) + _bt_release_scan_state(scan, state, false); + + if (so->backwardState) { - /* Before leaving current page, deal with any killed items */ - if (so->numKilled > 0) - _bt_killitems(scan); - BTScanPosUnpinIfPinned(so->currPos); - BTScanPosInvalidate(so->currPos); + _bt_release_scan_state(scan, so->backwardState, true); + pfree(so->backwardState); + so->backwardState = NULL; } - so->markItemIndex = -1; so->needPrimScan = false; so->scanBehind = false; - BTScanPosUnpinIfPinned(so->markPos); - BTScanPosInvalidate(so->markPos); /* * Allocate tuple workspace arrays, if needed for an index-only scan and @@ -393,11 +458,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, * a SIGSEGV is not possible. Yeah, this is ugly as sin, but it beats * adding special-case treatment for name_ops elsewhere. */ - if (scan->xs_want_itup && so->currTuples == NULL) - { - so->currTuples = (char *) palloc(BLCKSZ * 2); - so->markTuples = so->currTuples + BLCKSZ; - } + if (scan->xs_want_itup && state->currTuples == NULL) + _bt_allocate_tuple_workspaces(state); /* * Reset the scan keys @@ -408,6 +470,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, scan->numberOfKeys * sizeof(ScanKeyData)); so->numberOfKeys = 0; /* until _bt_preprocess_keys sets it */ so->numArrayKeys = 0; /* ditto */ + + if (orderbys && scan->numberOfOrderBys > 0) + memmove(scan->orderByData, + orderbys, + scan->numberOfOrderBys * sizeof(ScanKeyData)); + + so->scanDirection = NoMovementScanDirection; + so->distanceTypeByVal = true; } /* @@ -418,44 +488,29 @@ btendscan(IndexScanDesc scan) { BTScanOpaque so = (BTScanOpaque) scan->opaque; - /* we aren't holding any read locks, but gotta drop the pins */ - if (BTScanPosIsValid(so->currPos)) + _bt_release_scan_state(scan, &so->state, true); + + if (so->backwardState) { - /* Before leaving current page, deal with any killed items */ - if (so->numKilled > 0) - _bt_killitems(scan); - BTScanPosUnpinIfPinned(so->currPos); + _bt_release_scan_state(scan, so->backwardState, true); + pfree(so->backwardState); } - so->markItemIndex = -1; - BTScanPosUnpinIfPinned(so->markPos); - - /* No need to invalidate positions, the RAM is about to be freed. */ - /* Release storage */ if (so->keyData != NULL) pfree(so->keyData); /* so->arrayKeys and so->orderProcs are in arrayContext */ if (so->arrayContext != NULL) MemoryContextDelete(so->arrayContext); - if (so->killedItems != NULL) - pfree(so->killedItems); - if (so->currTuples != NULL) - pfree(so->currTuples); - /* so->markTuples should not be pfree'd, see btrescan */ + pfree(so); } -/* - * btmarkpos() -- save current scan position - */ -void -btmarkpos(IndexScanDesc scan) +static void +_bt_mark_current_position(BTScanOpaque so, BTScanState state) { - BTScanOpaque so = (BTScanOpaque) scan->opaque; - /* There may be an old mark with a pin (but no lock). */ - BTScanPosUnpinIfPinned(so->markPos); + BTScanPosUnpinIfPinned(state->markPos); /* * Just record the current itemIndex. If we later step to next page @@ -463,24 +518,57 @@ btmarkpos(IndexScanDesc scan) * the currPos struct in markPos. If (as often happens) the mark is moved * before we leave the page, we don't have to do that work. */ - if (BTScanPosIsValid(so->currPos)) - so->markItemIndex = so->currPos.itemIndex; + if (BTScanPosIsValid(state->currPos)) + state->markItemIndex = state->currPos.itemIndex; else { - BTScanPosInvalidate(so->markPos); - so->markItemIndex = -1; + BTScanPosInvalidate(state->markPos); + state->markItemIndex = -1; + } + + if (so->backwardState) + { + if (!so->distanceTypeByVal && DatumGetPointer(state->markDistance)) + pfree(DatumGetPointer(state->markDistance)); + + if (!BTScanPosIsValid(state->currPos) || state->currIsNull) + { + state->markIsNull = true; + state->markDistance = (Datum) 0; + } + else + { + state->markIsNull = false; + state->markDistance = datumCopy(state->currDistance, + so->distanceTypeByVal, + so->distanceTypeLen); + } } } /* - * btrestrpos() -- restore scan to last saved position + * btmarkpos() -- save current scan position */ void -btrestrpos(IndexScanDesc scan) +btmarkpos(IndexScanDesc scan) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + + _bt_mark_current_position(so, &so->state); + + if (so->backwardState) + { + _bt_mark_current_position(so, so->backwardState); + so->markRightIsNearest = so->currRightIsNearest; + } +} + +static void +_bt_restore_marked_position(IndexScanDesc scan, BTScanState state) { BTScanOpaque so = (BTScanOpaque) scan->opaque; - if (so->markItemIndex >= 0) + if (state->markItemIndex >= 0) { /* * The scan has never moved to a new page since the last mark. Just @@ -489,7 +577,7 @@ btrestrpos(IndexScanDesc scan) * NB: In this case we can't count on anything in so->markPos to be * accurate. */ - so->currPos.itemIndex = so->markItemIndex; + state->currPos.itemIndex = state->markItemIndex; } else { @@ -499,34 +587,43 @@ btrestrpos(IndexScanDesc scan) * locks, but if we're still holding the pin for the current position, * we must drop it. */ - if (BTScanPosIsValid(so->currPos)) - { - /* Before leaving current page, deal with any killed items */ - if (so->numKilled > 0) - _bt_killitems(scan); - BTScanPosUnpinIfPinned(so->currPos); - } + _bt_release_current_position(state, scan->indexRelation, + !BTScanPosIsValid(state->markPos)); - if (BTScanPosIsValid(so->markPos)) + if (BTScanPosIsValid(state->markPos)) { /* bump pin on mark buffer for assignment to current buffer */ - if (BTScanPosIsPinned(so->markPos)) - IncrBufferRefCount(so->markPos.buf); - memcpy(&so->currPos, &so->markPos, + if (BTScanPosIsPinned(state->markPos)) + IncrBufferRefCount(state->markPos.buf); + memcpy(&state->currPos, &state->markPos, offsetof(BTScanPosData, items[1]) + - so->markPos.lastItem * sizeof(BTScanPosItem)); - if (so->currTuples) - memcpy(so->currTuples, so->markTuples, - so->markPos.nextTupleOffset); + state->markPos.lastItem * sizeof(BTScanPosItem)); + if (state->currTuples) + memcpy(state->currTuples, state->markTuples, + state->markPos.nextTupleOffset); /* Reset the scan's array keys (see _bt_steppage for why) */ if (so->numArrayKeys) { - _bt_start_array_keys(scan, so->currPos.dir); + _bt_start_array_keys(scan, state->currPos.dir); so->needPrimScan = false; } } - else - BTScanPosInvalidate(so->currPos); + } + + /* + * For bidirectional nearest neighbor scan we also need to restore the + * distance to the current item. + */ + if (so->useBidirectionalKnnScan) + { + if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance)) + pfree(DatumGetPointer(state->currDistance)); + + state->currIsNull = state->markIsNull; + state->currDistance = state->markIsNull ? (Datum) 0 : + datumCopy(state->markDistance, + so->distanceTypeByVal, + so->distanceTypeLen); } } @@ -549,7 +646,8 @@ btinitparallelscan(void *target) BTParallelScanDesc bt_target = (BTParallelScanDesc) target; SpinLockInit(&bt_target->btps_mutex); - bt_target->btps_scanPage = InvalidBlockNumber; + bt_target->btps_forwardScanPage = InvalidBlockNumber; + bt_target->btps_backwardScanPage = InvalidBlockNumber; bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED; ConditionVariableInit(&bt_target->btps_cv); } @@ -574,7 +672,8 @@ btparallelrescan(IndexScanDesc scan) * consistency. */ SpinLockAcquire(&btscan->btps_mutex); - btscan->btps_scanPage = InvalidBlockNumber; + btscan->btps_forwardScanPage = InvalidBlockNumber; + btscan->btps_backwardScanPage = InvalidBlockNumber; btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED; SpinLockRelease(&btscan->btps_mutex); } @@ -602,13 +701,14 @@ btparallelrescan(IndexScanDesc scan) * for first=false callers that require another primitive index scan. */ bool -_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first) +_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno, bool first) { BTScanOpaque so = (BTScanOpaque) scan->opaque; bool exit_loop = false; bool status = true; ParallelIndexScanDesc parallel_scan = scan->parallel_scan; BTParallelScanDesc btscan; + BlockNumber *scanPage; *pageno = P_NONE; @@ -640,6 +740,10 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first) btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan, parallel_scan->ps_offset); + scanPage = state == so->backwardState ? + &btscan->btps_backwardScanPage : + &btscan->btps_forwardScanPage; + while (1) { SpinLockAcquire(&btscan->btps_mutex); @@ -681,7 +785,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first) * of advancing it to a new page! */ btscan->btps_pageStatus = BTPARALLEL_ADVANCING; - *pageno = btscan->btps_scanPage; + *pageno = *scanPage; exit_loop = true; } SpinLockRelease(&btscan->btps_mutex); @@ -706,19 +810,44 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first) * scan lands on scan_page). */ void -_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page) +_bt_parallel_release(IndexScanDesc scan, BTScanState state, + BlockNumber scan_page) { + BTScanOpaque so = (BTScanOpaque) scan->opaque; ParallelIndexScanDesc parallel_scan = scan->parallel_scan; BTParallelScanDesc btscan; + BlockNumber *scanPage; + BlockNumber *otherScanPage; + bool status_changed = false; + bool knnScan = so->useBidirectionalKnnScan; btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan, parallel_scan->ps_offset); + Assert(state); + if (state != so->backwardState) + { + scanPage = &btscan->btps_forwardScanPage; + otherScanPage = &btscan->btps_backwardScanPage; + } + else + { + scanPage = &btscan->btps_backwardScanPage; + otherScanPage = &btscan->btps_forwardScanPage; + } + SpinLockAcquire(&btscan->btps_mutex); - btscan->btps_scanPage = scan_page; - btscan->btps_pageStatus = BTPARALLEL_IDLE; + *scanPage = scan_page; + /* switch to idle state only if both KNN pages are initialized */ + if (!knnScan || *otherScanPage != InvalidBlockNumber) + { + btscan->btps_pageStatus = BTPARALLEL_IDLE; + status_changed = true; + } SpinLockRelease(&btscan->btps_mutex); - ConditionVariableSignal(&btscan->btps_cv); + + if (status_changed) + ConditionVariableSignal(&btscan->btps_cv); } /* @@ -729,11 +858,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page) * advance to the next page. */ void -_bt_parallel_done(IndexScanDesc scan) +_bt_parallel_done(IndexScanDesc scan, BTScanState state) { + BTScanOpaque so = (BTScanOpaque) scan->opaque; ParallelIndexScanDesc parallel_scan = scan->parallel_scan; BTParallelScanDesc btscan; + BlockNumber *scanPage; + BlockNumber *otherScanPage; bool status_changed = false; + bool knnScan = so->useBidirectionalKnnScan; /* Do nothing, for non-parallel scans */ if (parallel_scan == NULL) @@ -742,16 +875,43 @@ _bt_parallel_done(IndexScanDesc scan) btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan, parallel_scan->ps_offset); + Assert(state); + if (state != so->backwardState) + { + scanPage = &btscan->btps_forwardScanPage; + otherScanPage = &btscan->btps_backwardScanPage; + } + else + { + scanPage = &btscan->btps_backwardScanPage; + otherScanPage = &btscan->btps_forwardScanPage; + } + /* * Mark the parallel scan as done, unless some other process did so * already */ SpinLockAcquire(&btscan->btps_mutex); - if (btscan->btps_pageStatus != BTPARALLEL_DONE) + + Assert(!knnScan || btscan->btps_pageStatus == BTPARALLEL_ADVANCING); + + *scanPage = P_NONE; + status_changed = true; + + /* switch to "done" state only if both KNN scans are done */ + if (!knnScan || *otherScanPage == P_NONE) { + if (btscan->btps_pageStatus == BTPARALLEL_DONE) + status_changed = false; + btscan->btps_pageStatus = BTPARALLEL_DONE; - status_changed = true; } + /* else switch to "idle" state only if both KNN scans are initialized */ + else if (*otherScanPage != InvalidBlockNumber) + btscan->btps_pageStatus = BTPARALLEL_IDLE; + else + status_changed = false; + SpinLockRelease(&btscan->btps_mutex); /* wake up all the workers associated with this parallel scan */ @@ -771,19 +931,30 @@ void _bt_parallel_primscan_schedule(IndexScanDesc scan, BlockNumber prev_scan_page) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanState state = &so->state; ParallelIndexScanDesc parallel_scan = scan->parallel_scan; BTParallelScanDesc btscan; + BlockNumber *scan_page; Assert(so->numArrayKeys); btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan, parallel_scan->ps_offset); + if (state != so->backwardState) + { + scan_page = &btscan->btps_forwardScanPage; + } + else + { + scan_page = &btscan->btps_backwardScanPage; + } + SpinLockAcquire(&btscan->btps_mutex); - if (btscan->btps_scanPage == prev_scan_page && + if (*scan_page == prev_scan_page && btscan->btps_pageStatus == BTPARALLEL_IDLE) { - btscan->btps_scanPage = InvalidBlockNumber; + *scan_page = InvalidBlockNumber; btscan->btps_pageStatus = BTPARALLEL_NEED_PRIMSCAN; /* Serialize scan's current array keys */ @@ -797,6 +968,23 @@ _bt_parallel_primscan_schedule(IndexScanDesc scan, BlockNumber prev_scan_page) SpinLockRelease(&btscan->btps_mutex); } +/* + * btrestrpos() -- restore scan to last saved position + */ +void +btrestrpos(IndexScanDesc scan) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + + _bt_restore_marked_position(scan, &so->state); + + if (so->backwardState) + { + _bt_restore_marked_position(scan, so->backwardState); + so->currRightIsNearest = so->markRightIsNearest; + } +} + /* * Bulk deletion of all index entries pointing to a set of heap tuples. * The set of target tuples is specified via a callback routine that tells diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c index 57bcfc7e4c64..b19802789bc9 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -29,23 +29,28 @@ static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp); static OffsetNumber _bt_binsrch(Relation rel, BTScanInsert key, Buffer buf); static int _bt_binsrch_posting(BTScanInsert key, Page page, OffsetNumber offnum); -static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir, - OffsetNumber offnum, bool firstPage); -static void _bt_saveitem(BTScanOpaque so, int itemIndex, +static bool _bt_readpage(IndexScanDesc scan, BTScanState state, + ScanDirection dir, OffsetNumber offnum, + bool firstPage); +static void _bt_saveitem(BTScanState state, int itemIndex, OffsetNumber offnum, IndexTuple itup); -static int _bt_setuppostingitems(BTScanOpaque so, int itemIndex, +static int _bt_setuppostingitems(BTScanState state, int itemIndex, OffsetNumber offnum, ItemPointer heapTid, IndexTuple itup); -static inline void _bt_savepostingitem(BTScanOpaque so, int itemIndex, +static inline void _bt_savepostingitem(BTScanState state, int itemIndex, OffsetNumber offnum, ItemPointer heapTid, int tupleOffset); -static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir); -static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir); -static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, - ScanDirection dir); +static bool _bt_steppage(IndexScanDesc scan, BTScanState state, + ScanDirection dir); +static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state, + BlockNumber blkno, ScanDirection dir); +static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state, + BlockNumber blkno, ScanDirection dir); static Buffer _bt_walk_left(Relation rel, Buffer buf); static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir); -static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir); +static inline void _bt_initialize_more_data(IndexScanDesc scan, BTScanState state, ScanDirection dir); +static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan); +static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right); /* @@ -852,6 +857,227 @@ _bt_compare(Relation rel, return 0; } +/* + * _bt_return_current_item() -- Prepare current scan state item for return. + * + * This function is used only in "return _bt_return_current_item();" statements + * and always returns true. + */ +static inline bool +_bt_return_current_item(IndexScanDesc scan, BTScanState state) +{ + BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex]; + + scan->xs_heaptid = currItem->heapTid; + + if (scan->xs_want_itup) + scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset); + + return true; +} + +/* + * _bt_load_first_page() -- Load data from the first page of the scan. + * + * Caller must have pinned and read-locked state->currPos.buf. + * + * On success exit, state->currPos is updated to contain data from the next + * interesting page. For success on a scan using a non-MVCC snapshot we hold + * a pin, but not a read lock, on that page. If we do not hold the pin, we + * set state->currPos.buf to InvalidBuffer. We return true to indicate success. + * + * If there are no more matching records in the given direction at all, + * we drop all locks and pins, set state->currPos.buf to InvalidBuffer, + * and return false. + */ +static bool +_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir, + OffsetNumber offnum, bool *readPageStatus) +{ + if (!(readPageStatus ? + *readPageStatus : + _bt_readpage(scan, state, dir, offnum, true))) + { + /* + * There's no actually-matching data on this page. Try to advance to + * the next page. Return false if there's no matching data at all. + */ + LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK); + return _bt_steppage(scan, state, dir); + } + + /* Drop the lock, and maybe the pin, on the current page */ + _bt_drop_lock_and_maybe_pin(scan, &state->currPos); + return true; +} + +/* + * _bt_calc_current_dist() -- Calculate distance from the current item + * of the scan state to the target order-by ScanKey argument. + */ +static void +_bt_calc_current_dist(IndexScanDesc scan, BTScanState state) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex]; + IndexTuple itup = (IndexTuple) (state->currTuples + currItem->tupleOffset); + ScanKey scankey = &scan->orderByData[0]; + Datum value; + + value = index_getattr(itup, 1, scan->xs_itupdesc, &state->currIsNull); + + if (state->currIsNull) + return; /* NULL distance */ + + value = FunctionCall2Coll(&scankey->sk_func, + scankey->sk_collation, + value, + scankey->sk_argument); + + /* free previous distance value for by-ref types */ + if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance)) + pfree(DatumGetPointer(state->currDistance)); + + state->currDistance = value; +} + +/* + * _bt_compare_current_dist() -- Compare current distances of the left and + *right scan states. + * + * NULL distances are considered to be greater than any non-NULL distances. + * + * Returns true if right distance is lesser than left, otherwise false. + */ +static bool +_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate) +{ + if (lstate->currIsNull) + return true; /* non-NULL < NULL */ + + if (rstate->currIsNull) + return false; /* NULL > non-NULL */ + + return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc, + InvalidOid, /* XXX collation for + * distance comparison */ + rstate->currDistance, + lstate->currDistance)); +} + +/* + * _bt_alloc_knn_backward_scan() -- Allocate additional backward scan state for KNN. + */ +static BTScanState +_bt_alloc_knn_scan(IndexScanDesc scan) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanState lstate = (BTScanState) palloc(sizeof(BTScanStateData)); + + _bt_allocate_tuple_workspaces(lstate); + + if (!scan->xs_want_itup) + { + /* We need to request index tuples for distance comparison. */ + scan->xs_want_itup = true; + _bt_allocate_tuple_workspaces(&so->state); + } + + BTScanPosInvalidate(lstate->currPos); + lstate->currPos.moreLeft = false; + lstate->currPos.moreRight = false; + BTScanPosInvalidate(lstate->markPos); + lstate->markItemIndex = -1; + lstate->killedItems = NULL; + lstate->numKilled = 0; + lstate->currDistance = (Datum) 0; + lstate->markDistance = (Datum) 0; + + return so->backwardState = lstate; +} + +static bool +_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanState rstate; /* right (forward) main scan state */ + BTScanState lstate; /* additional left (backward) KNN scan state */ + + if (!left && !right) + return false; /* empty result */ + + rstate = &so->state; + lstate = so->backwardState; + + if (left && right) + { + /* + * We have found items in both scan directions, determine nearest item + * to return. + */ + _bt_calc_current_dist(scan, rstate); + _bt_calc_current_dist(scan, lstate); + so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate); + + /* + * 'right' flag determines the selected scan direction; right + * direction is selected if the right item is nearest. + */ + right = so->currRightIsNearest; + } + + /* Return current item of the selected scan direction. */ + return _bt_return_current_item(scan, right ? rstate : lstate); +} + +/* + * _bt_init_knn_scan() -- Init additional scan state for KNN search. + * + * Caller must pin and read-lock scan->state.currPos.buf buffer. + * + * If empty result was found returned false. + * Otherwise prepared current item, and returned true. + */ +static bool +_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanState rstate = &so->state; /* right (forward) main scan state */ + BTScanState lstate; /* additional left (backward) KNN scan state */ + Buffer buf = rstate->currPos.buf; + bool left, + right; + ScanDirection rdir = ForwardScanDirection; + ScanDirection ldir = BackwardScanDirection; + OffsetNumber roffnum = offnum; + OffsetNumber loffnum = OffsetNumberPrev(offnum); + + lstate = _bt_alloc_knn_scan(scan); + + /* Bump pin and lock count before BTScanPosData copying. */ + IncrBufferRefCount(buf); + LockBuffer(buf, BT_READ); + + memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData)); + lstate->currPos.moreLeft = true; + lstate->currPos.moreRight = false; + + /* + * Load first pages from the both scans. + * + * _bt_load_first_page(right) can step to next page, and then + * _bt_parallel_seize() will deadlock if the left page number is not yet + * initialized in BTParallelScanDesc. So we must first read the left page + * using _bt_readpage(), and _bt_parallel_release() which is called inside + * will save the next page number in BTParallelScanDesc. + */ + left = _bt_readpage(scan, lstate, ldir, loffnum, true); + right = _bt_load_first_page(scan, rstate, rdir, roffnum, NULL); + left = _bt_load_first_page(scan, lstate, ldir, loffnum, &left); + + return _bt_start_knn_scan(scan, left, right); +} + /* * _bt_first() -- Find the first item in a scan. * @@ -877,6 +1103,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) { Relation rel = scan->indexRelation; BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPos currPos = &so->state.currPos; Buffer buf; BTStack stack; OffsetNumber offnum; @@ -888,10 +1115,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) int i; bool status; StrategyNumber strat_total; - BTScanPosItem *currItem; BlockNumber blkno; - Assert(!BTScanPosIsValid(so->currPos)); + Assert(!BTScanPosIsValid(*currPos)); pgstat_count_index_scan(rel); @@ -907,10 +1133,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) */ if (!so->qual_ok) { - _bt_parallel_done(scan); + _bt_parallel_done(scan, &so->state); return false; } + if (scan->numberOfOrderBys > 0) + { + if (so->useBidirectionalKnnScan) + _bt_init_distance_comparison(scan); + else if (so->scanDirection != NoMovementScanDirection) + /* use selected KNN scan direction */ + dir = so->scanDirection; + } + /* * For parallel scans, get the starting page from shared state. If the * scan has not started, proceed to find out first leaf page in the usual @@ -923,7 +1158,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) */ if (scan->parallel_scan != NULL) { - status = _bt_parallel_seize(scan, &blkno, true); + status = _bt_parallel_seize(scan, &so->state, &blkno, true); /* * Initialize arrays (when _bt_parallel_seize didn't already set up @@ -934,16 +1169,47 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) if (!status) return false; - else if (blkno == P_NONE) - { - _bt_parallel_done(scan); - return false; - } else if (blkno != InvalidBlockNumber) { - if (!_bt_parallel_readpage(scan, blkno, dir)) - return false; - goto readcomplete; + bool knn = so->useBidirectionalKnnScan; + bool right; + bool left; + + if (knn) + _bt_alloc_knn_scan(scan); + + if (blkno == P_NONE) + { + _bt_parallel_done(scan, &so->state); + right = false; + } + else + right = _bt_parallel_readpage(scan, &so->state, blkno, + knn ? ForwardScanDirection : dir); + + if (!knn) + return right && _bt_return_current_item(scan, &so->state); + + /* seize additional backward KNN scan */ + left = _bt_parallel_seize(scan, so->backwardState, &blkno, true); + + if (left) + { + if (blkno == P_NONE) + { + _bt_parallel_done(scan, so->backwardState); + left = false; + } + else + { + /* backward scan should be already initialized */ + Assert(blkno != InvalidBlockNumber); + left = _bt_parallel_readpage(scan, so->backwardState, blkno, + BackwardScanDirection); + } + } + + return _bt_start_knn_scan(scan, left, right); } } else if (so->numArrayKeys && !so->needPrimScan) @@ -1015,14 +1281,20 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) * need to be kept in sync. *---------- */ - strat_total = BTEqualStrategyNumber; - if (so->numberOfKeys > 0) + if (so->useBidirectionalKnnScan) + { + keysz = _bt_init_knn_start_keys(scan, startKeys, notnullkeys); + strat_total = BTNearestStrategyNumber; + } + else if (so->numberOfKeys > 0) { AttrNumber curattr; ScanKey chosen; ScanKey impliesNN; ScanKey cur; + strat_total = BTEqualStrategyNumber; + /* * chosen is the so-far-chosen key for the current attribute, if any. * We don't cast the decision in stone until we reach keys for the @@ -1156,7 +1428,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) if (!match) { /* No match, so mark (parallel) scan finished */ - _bt_parallel_done(scan); + _bt_parallel_done(scan, &so->state); } return match; @@ -1192,7 +1464,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) Assert(subkey->sk_flags & SK_ROW_MEMBER); if (subkey->sk_flags & SK_ISNULL) { - _bt_parallel_done(scan); + _bt_parallel_done(scan, &so->state); return false; } memcpy(inskey.scankeys + i, subkey, sizeof(ScanKeyData)); @@ -1357,6 +1629,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) break; case BTGreaterEqualStrategyNumber: + case BTMaxStrategyNumber: /* * Find first item >= scankey @@ -1414,20 +1687,20 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) * Mark parallel scan as done, so that all the workers can finish * their scan. */ - _bt_parallel_done(scan); - BTScanPosInvalidate(so->currPos); + _bt_parallel_done(scan, &so->state); + BTScanPosInvalidate(*currPos); return false; } } PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->xs_snapshot); - _bt_initialize_more_data(so, dir); + _bt_initialize_more_data(scan, &so->state, dir); /* position to the precise item on the page */ offnum = _bt_binsrch(rel, &inskey, buf); - Assert(!BTScanPosIsValid(so->currPos)); - so->currPos.buf = buf; + Assert(!BTScanPosIsValid(*currPos)); + currPos->buf = buf; /* * Now load data from the first page of the scan. @@ -1448,30 +1721,83 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) * for the page. For example, when inskey is both < the leaf page's high * key and > all of its non-pivot tuples, offnum will be "maxoff + 1". */ - if (!_bt_readpage(scan, dir, offnum, true)) + if (strat_total == BTNearestStrategyNumber) + return _bt_init_knn_scan(scan, offnum); + + if (!_bt_load_first_page(scan, &so->state, dir, offnum, NULL)) + return false; /* empty result */ + + /* OK, currPos->itemIndex says what to return */ + return _bt_return_current_item(scan, &so->state); +} + +/* + * _bt_next_item() -- Advance to next tuple on current page; + * or if there's no more, try to step to the next page with data. + * + * If there are any matching records in the given direction true is + * returned, otherwise false. + */ +static bool +_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir) +{ + if (ScanDirectionIsForward(dir)) { - /* - * There's no actually-matching data on this page. Try to advance to - * the next page. Return false if there's no matching data at all. - */ - _bt_unlockbuf(scan->indexRelation, so->currPos.buf); - if (!_bt_steppage(scan, dir)) - return false; + if (++state->currPos.itemIndex <= state->currPos.lastItem) + return true; } else { - /* We have at least one item to return as scan's first item */ - _bt_drop_lock_and_maybe_pin(scan, &so->currPos); + if (--state->currPos.itemIndex >= state->currPos.firstItem) + return true; } -readcomplete: - /* OK, itemIndex says what to return */ - currItem = &so->currPos.items[so->currPos.itemIndex]; - scan->xs_heaptid = currItem->heapTid; - if (scan->xs_want_itup) - scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset); + return _bt_steppage(scan, state, dir); +} - return true; +/* + * _bt_next_nearest() -- Return next nearest item from bidirectional KNN scan. + */ +static bool +_bt_next_nearest(IndexScanDesc scan) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanState rstate = &so->state; + BTScanState lstate = so->backwardState; + bool right = BTScanPosIsValid(rstate->currPos); + bool left = BTScanPosIsValid(lstate->currPos); + bool advanceRight; + + if (right && left) + advanceRight = so->currRightIsNearest; + else if (right) + advanceRight = true; + else if (left) + advanceRight = false; + else + return false; /* end of the scan */ + + if (advanceRight) + right = _bt_next_item(scan, rstate, ForwardScanDirection); + else + left = _bt_next_item(scan, lstate, BackwardScanDirection); + + if (!left && !right) + return false; /* end of the scan */ + + if (left && right) + { + /* + * If there are items in both scans we must recalculate distance in + * the advanced scan. + */ + _bt_calc_current_dist(scan, advanceRight ? rstate : lstate); + so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate); + right = so->currRightIsNearest; + } + + /* return nearest item */ + return _bt_return_current_item(scan, right ? rstate : lstate); } /* @@ -1492,44 +1818,24 @@ bool _bt_next(IndexScanDesc scan, ScanDirection dir) { BTScanOpaque so = (BTScanOpaque) scan->opaque; - BTScanPosItem *currItem; - /* - * Advance to next tuple on current page; or if there's no more, try to - * step to the next page with data. - */ - if (ScanDirectionIsForward(dir)) - { - if (++so->currPos.itemIndex > so->currPos.lastItem) - { - if (!_bt_steppage(scan, dir)) - return false; - } - } - else - { - if (--so->currPos.itemIndex < so->currPos.firstItem) - { - if (!_bt_steppage(scan, dir)) - return false; - } - } + if (so->backwardState) + /* return next neareset item from KNN scan */ + return _bt_next_nearest(scan); - /* OK, itemIndex says what to return */ - currItem = &so->currPos.items[so->currPos.itemIndex]; - scan->xs_heaptid = currItem->heapTid; - if (scan->xs_want_itup) - scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset); + if (!_bt_next_item(scan, &so->state, dir)) + return false; - return true; + /* OK, itemIndex says what to return */ + return _bt_return_current_item(scan, &so->state); } /* * _bt_readpage() -- Load data from current index page into so->currPos * - * Caller must have pinned and read-locked so->currPos.buf; the buffer's state - * is not changed here. Also, currPos.moreLeft and moreRight must be valid; - * they are updated as appropriate. All other fields of so->currPos are + * Caller must have pinned and read-locked pos->buf; the buffer's state + * is not changed here. Also, pos->moreLeft and moreRight must be valid; + * they are updated as appropriate. All other fields of pos are * initialized from scratch here. * * We scan the current page starting at offnum and moving in the indicated @@ -1553,10 +1859,11 @@ _bt_next(IndexScanDesc scan, ScanDirection dir) * Returns true if any matching items found on the page, false if none. */ static bool -_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, +_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir, OffsetNumber offnum, bool firstPage) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPos pos = &state->currPos; Page page; BTPageOpaque opaque; OffsetNumber minoff; @@ -1570,9 +1877,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, * We must have the buffer pinned and locked, but the usual macro can't be * used here; this function is what makes it good for currPos. */ - Assert(BufferIsValid(so->currPos.buf)); + Assert(BufferIsValid(pos->buf)); - page = BufferGetPage(so->currPos.buf); + page = BufferGetPage(pos->buf); opaque = BTPageGetOpaque(page); /* allow next page be processed by parallel worker */ @@ -1581,9 +1888,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, if (ScanDirectionIsForward(dir)) pstate.prev_scan_page = opaque->btpo_next; else - pstate.prev_scan_page = BufferGetBlockNumber(so->currPos.buf); + pstate.prev_scan_page = BufferGetBlockNumber(pos->buf); - _bt_parallel_release(scan, pstate.prev_scan_page); + _bt_parallel_release(scan, state, pstate.prev_scan_page); } indnatts = IndexRelationGetNumberOfAttributes(scan->indexRelation); @@ -1609,30 +1916,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, * We note the buffer's block number so that we can release the pin later. * This allows us to re-read the buffer if it is needed again for hinting. */ - so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf); + pos->currPage = BufferGetBlockNumber(pos->buf); /* * We save the LSN of the page as we read it, so that we know whether it * safe to apply LP_DEAD hints to the page later. This allows us to drop * the pin for MVCC scans, which allows vacuum to avoid blocking. */ - so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf); + pos->lsn = BufferGetLSNAtomic(pos->buf); /* * we must save the page's right-link while scanning it; this tells us * where to step right to after we're done with these items. There is no * corresponding need for the left-link, since splits always go right. */ - so->currPos.nextPage = opaque->btpo_next; + pos->nextPage = opaque->btpo_next; /* initialize tuple workspace to empty */ - so->currPos.nextTupleOffset = 0; + pos->nextTupleOffset = 0; /* * Now that the current page has been made consistent, the macro should be * good. */ - Assert(BTScanPosIsPinned(so->currPos)); + Assert(BTScanPosIsPinned(*pos)); /* * Prechecking the value of the continuescan flag for the last item on the @@ -1674,7 +1981,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, * required < or <= strategy scan keys) during the precheck, we can safely * assume that this must also be true of all earlier tuples from the page. */ - if (!firstPage && !so->scanBehind && minoff < maxoff) + if (!so->useBidirectionalKnnScan && !firstPage && !so->scanBehind && minoff < maxoff) { ItemId iid; IndexTuple itup; @@ -1747,7 +2054,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, if (!BTreeTupleIsPosting(itup)) { /* Remember it */ - _bt_saveitem(so, itemIndex, offnum, itup); + _bt_saveitem(state, itemIndex, offnum, itup); itemIndex++; } else @@ -1759,14 +2066,14 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, * TID */ tupleOffset = - _bt_setuppostingitems(so, itemIndex, offnum, + _bt_setuppostingitems(state, itemIndex, offnum, BTreeTupleGetPostingN(itup, 0), itup); itemIndex++; /* Remember additional TIDs */ for (int i = 1; i < BTreeTupleGetNPosting(itup); i++) { - _bt_savepostingitem(so, itemIndex, offnum, + _bt_savepostingitem(state, itemIndex, offnum, BTreeTupleGetPostingN(itup, i), tupleOffset); itemIndex++; @@ -1803,12 +2110,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, } if (!pstate.continuescan) - so->currPos.moreRight = false; + pos->moreRight = false; Assert(itemIndex <= MaxTIDsPerBTreePage); - so->currPos.firstItem = 0; - so->currPos.lastItem = itemIndex - 1; - so->currPos.itemIndex = 0; + pos->firstItem = 0; + pos->lastItem = itemIndex - 1; + pos->itemIndex = 0; } else { @@ -1885,7 +2192,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, { /* Remember it */ itemIndex--; - _bt_saveitem(so, itemIndex, offnum, itup); + _bt_saveitem(state, itemIndex, offnum, itup); } else { @@ -1903,14 +2210,14 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, */ itemIndex--; tupleOffset = - _bt_setuppostingitems(so, itemIndex, offnum, + _bt_setuppostingitems(state, itemIndex, offnum, BTreeTupleGetPostingN(itup, 0), itup); /* Remember additional TIDs */ for (int i = 1; i < BTreeTupleGetNPosting(itup); i++) { itemIndex--; - _bt_savepostingitem(so, itemIndex, offnum, + _bt_savepostingitem(state, itemIndex, offnum, BTreeTupleGetPostingN(itup, i), tupleOffset); } @@ -1919,7 +2226,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, if (!pstate.continuescan) { /* there can't be any more matches, so stop */ - so->currPos.moreLeft = false; + pos->moreLeft = false; break; } @@ -1927,39 +2234,40 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum, } Assert(itemIndex >= 0); - so->currPos.firstItem = itemIndex; - so->currPos.lastItem = MaxTIDsPerBTreePage - 1; - so->currPos.itemIndex = MaxTIDsPerBTreePage - 1; + pos->firstItem = itemIndex; + pos->lastItem = MaxTIDsPerBTreePage - 1; + pos->itemIndex = MaxTIDsPerBTreePage - 1; } - return (so->currPos.firstItem <= so->currPos.lastItem); + return (pos->firstItem <= pos->lastItem); } /* Save an index item into so->currPos.items[itemIndex] */ static void -_bt_saveitem(BTScanOpaque so, int itemIndex, +_bt_saveitem(BTScanState state, int itemIndex, OffsetNumber offnum, IndexTuple itup) { - BTScanPosItem *currItem = &so->currPos.items[itemIndex]; + BTScanPosItem *currItem = &state->currPos.items[itemIndex]; Assert(!BTreeTupleIsPivot(itup) && !BTreeTupleIsPosting(itup)); currItem->heapTid = itup->t_tid; currItem->indexOffset = offnum; - if (so->currTuples) + if (state->currTuples) { Size itupsz = IndexTupleSize(itup); - currItem->tupleOffset = so->currPos.nextTupleOffset; - memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz); - so->currPos.nextTupleOffset += MAXALIGN(itupsz); + currItem->tupleOffset = state->currPos.nextTupleOffset; + memcpy(state->currTuples + state->currPos.nextTupleOffset, + itup, itupsz); + state->currPos.nextTupleOffset += MAXALIGN(itupsz); } } /* * Setup state to save TIDs/items from a single posting list tuple. * - * Saves an index item into so->currPos.items[itemIndex] for TID that is + * Saves an index item into state->currPos.items[itemIndex] for TID that is * returned to scan first. Second or subsequent TIDs for posting list should * be saved by calling _bt_savepostingitem(). * @@ -1967,29 +2275,29 @@ _bt_saveitem(BTScanOpaque so, int itemIndex, * needed. */ static int -_bt_setuppostingitems(BTScanOpaque so, int itemIndex, OffsetNumber offnum, +_bt_setuppostingitems(BTScanState state, int itemIndex, OffsetNumber offnum, ItemPointer heapTid, IndexTuple itup) { - BTScanPosItem *currItem = &so->currPos.items[itemIndex]; + BTScanPosItem *currItem = &state->currPos.items[itemIndex]; Assert(BTreeTupleIsPosting(itup)); currItem->heapTid = *heapTid; currItem->indexOffset = offnum; - if (so->currTuples) + if (state->currTuples) { /* Save base IndexTuple (truncate posting list) */ IndexTuple base; Size itupsz = BTreeTupleGetPostingOffset(itup); itupsz = MAXALIGN(itupsz); - currItem->tupleOffset = so->currPos.nextTupleOffset; - base = (IndexTuple) (so->currTuples + so->currPos.nextTupleOffset); + currItem->tupleOffset = state->currPos.nextTupleOffset; + base = (IndexTuple) (state->currTuples + state->currPos.nextTupleOffset); memcpy(base, itup, itupsz); /* Defensively reduce work area index tuple header size */ base->t_info &= ~INDEX_SIZE_MASK; base->t_info |= itupsz; - so->currPos.nextTupleOffset += itupsz; + state->currPos.nextTupleOffset += itupsz; return currItem->tupleOffset; } @@ -1998,17 +2306,17 @@ _bt_setuppostingitems(BTScanOpaque so, int itemIndex, OffsetNumber offnum, } /* - * Save an index item into so->currPos.items[itemIndex] for current posting + * Save an index item into state->currPos.items[itemIndex] for current posting * tuple. * * Assumes that _bt_setuppostingitems() has already been called for current * posting list tuple. Caller passes its return value as tupleOffset. */ static inline void -_bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum, +_bt_savepostingitem(BTScanState state, int itemIndex, OffsetNumber offnum, ItemPointer heapTid, int tupleOffset) { - BTScanPosItem *currItem = &so->currPos.items[itemIndex]; + BTScanPosItem *currItem = &state->currPos.items[itemIndex]; currItem->heapTid = *heapTid; currItem->indexOffset = offnum; @@ -2017,7 +2325,7 @@ _bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum, * Have index-only scans return the same base IndexTuple for every TID * that originates from the same posting list */ - if (so->currTuples) + if (state->currTuples) currItem->tupleOffset = tupleOffset; } @@ -2033,35 +2341,37 @@ _bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum, * to InvalidBuffer. We return true to indicate success. */ static bool -_bt_steppage(IndexScanDesc scan, ScanDirection dir) +_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPos currPos = &state->currPos; + Relation rel = scan->indexRelation; BlockNumber blkno = InvalidBlockNumber; bool status; - Assert(BTScanPosIsValid(so->currPos)); + Assert(BTScanPosIsValid(*currPos)); /* Before leaving current page, deal with any killed items */ - if (so->numKilled > 0) - _bt_killitems(scan); + if (state->numKilled > 0) + _bt_killitems(state, rel); /* * Before we modify currPos, make a copy of the page data if there was a * mark position that needs it. */ - if (so->markItemIndex >= 0) + if (state->markItemIndex >= 0) { /* bump pin on current buffer for assignment to mark buffer */ - if (BTScanPosIsPinned(so->currPos)) - IncrBufferRefCount(so->currPos.buf); - memcpy(&so->markPos, &so->currPos, + if (BTScanPosIsPinned(*currPos)) + IncrBufferRefCount(currPos->buf); + memcpy(&state->markPos, currPos, offsetof(BTScanPosData, items[1]) + - so->currPos.lastItem * sizeof(BTScanPosItem)); - if (so->markTuples) - memcpy(so->markTuples, so->currTuples, - so->currPos.nextTupleOffset); - so->markPos.itemIndex = so->markItemIndex; - so->markItemIndex = -1; + currPos->lastItem * sizeof(BTScanPosItem)); + if (state->markTuples) + memcpy(state->markTuples, state->currTuples, + currPos->nextTupleOffset); + state->markPos.itemIndex = state->markItemIndex; + state->markItemIndex = -1; /* * If we're just about to start the next primitive index scan @@ -2079,13 +2389,13 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) * In effect, btrestpos leaves advancing the arrays up to the first * _bt_readpage call (that takes place after it has restored markPos). */ - Assert(so->markPos.dir == dir); + Assert(state->markPos.dir == dir); if (so->needPrimScan) { if (ScanDirectionIsForward(dir)) - so->markPos.moreRight = true; + state->markPos.moreRight = true; else - so->markPos.moreLeft = true; + state->markPos.moreLeft = true; } } @@ -2098,31 +2408,31 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) * Seize the scan to get the next block number; if the scan has * ended already, bail out. */ - status = _bt_parallel_seize(scan, &blkno, false); + status = _bt_parallel_seize(scan, state, &blkno, false); if (!status) { /* release the previous buffer, if pinned */ - BTScanPosUnpinIfPinned(so->currPos); - BTScanPosInvalidate(so->currPos); + BTScanPosUnpinIfPinned(*currPos); + BTScanPosInvalidate(*currPos); return false; } } else { /* Not parallel, so use the previously-saved nextPage link. */ - blkno = so->currPos.nextPage; + blkno = currPos->nextPage; } /* Remember we left a page with data */ - so->currPos.moreLeft = true; + currPos->moreLeft = true; /* release the previous buffer, if pinned */ - BTScanPosUnpinIfPinned(so->currPos); + BTScanPosUnpinIfPinned(*currPos); } else { /* Remember we left a page with data */ - so->currPos.moreRight = true; + currPos->moreRight = true; if (scan->parallel_scan != NULL) { @@ -2130,26 +2440,32 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) * Seize the scan to get the current block number; if the scan has * ended already, bail out. */ - status = _bt_parallel_seize(scan, &blkno, false); - BTScanPosUnpinIfPinned(so->currPos); + status = _bt_parallel_seize(scan, state, &blkno, false); + BTScanPosUnpinIfPinned(*currPos); if (!status) { - BTScanPosInvalidate(so->currPos); + BTScanPosInvalidate(*currPos); + return false; + } + if (blkno == P_NONE) + { + _bt_parallel_done(scan, state); + BTScanPosInvalidate(*currPos); return false; } } else { /* Not parallel, so just use our own notion of the current page */ - blkno = so->currPos.currPage; + blkno = currPos->currPage; } } - if (!_bt_readnextpage(scan, blkno, dir)) + if (!_bt_readnextpage(scan, state, blkno, dir)) return false; /* We have at least one item to return as scan's next item */ - _bt_drop_lock_and_maybe_pin(scan, &so->currPos); + _bt_drop_lock_and_maybe_pin(scan, currPos); return true; } @@ -2165,9 +2481,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) * locks and pins, set so->currPos.buf to InvalidBuffer, and return false. */ static bool -_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) +_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, + ScanDirection dir) { - BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPos currPos = &state->currPos; Relation rel; Page page; BTPageOpaque opaque; @@ -2183,17 +2500,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) * if we're at end of scan, give up and mark parallel scan as * done, so that all the workers can finish their scan */ - if (blkno == P_NONE || !so->currPos.moreRight) + if (blkno == P_NONE || !currPos->moreRight) { - _bt_parallel_done(scan); - BTScanPosInvalidate(so->currPos); + _bt_parallel_done(scan, state); + BTScanPosInvalidate(*currPos); return false; } /* check for interrupts while we're not holding any buffer lock */ CHECK_FOR_INTERRUPTS(); /* step right one page */ - so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ); - page = BufferGetPage(so->currPos.buf); + currPos->buf = _bt_getbuf(rel, blkno, BT_READ); + page = BufferGetPage(currPos->buf); opaque = BTPageGetOpaque(page); /* check for deleted page */ if (!P_IGNORE(opaque)) @@ -2201,30 +2518,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) PredicateLockPage(rel, blkno, scan->xs_snapshot); /* see if there are any matches on this page */ /* note that this will clear moreRight if we can stop */ - if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque), false)) + if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque), false)) break; } else if (scan->parallel_scan != NULL) { /* allow next page be processed by parallel worker */ - _bt_parallel_release(scan, opaque->btpo_next); + _bt_parallel_release(scan, state, opaque->btpo_next); } /* nope, keep going */ if (scan->parallel_scan != NULL) { - _bt_relbuf(rel, so->currPos.buf); - status = _bt_parallel_seize(scan, &blkno, false); + _bt_relbuf(rel, currPos->buf); + status = _bt_parallel_seize(scan, state, &blkno, false); if (!status) { - BTScanPosInvalidate(so->currPos); + BTScanPosInvalidate(*currPos); return false; } } else { blkno = opaque->btpo_next; - _bt_relbuf(rel, so->currPos.buf); + _bt_relbuf(rel, currPos->buf); } } } @@ -2234,10 +2551,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) * Should only happen in parallel cases, when some other backend * advanced the scan. */ - if (so->currPos.currPage != blkno) + if (currPos->currPage != blkno) { - BTScanPosUnpinIfPinned(so->currPos); - so->currPos.currPage = blkno; + BTScanPosUnpinIfPinned(*currPos); + currPos->currPage = blkno; } /* @@ -2253,30 +2570,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) * optimistically starting there (rather than pinning the page twice). * It is not clear that this would be worth the complexity. */ - if (BTScanPosIsPinned(so->currPos)) - _bt_lockbuf(rel, so->currPos.buf, BT_READ); + if (BTScanPosIsPinned(*currPos)) + _bt_lockbuf(rel, currPos->buf, BT_READ); else - so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ); + currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ); for (;;) { /* Done if we know there are no matching keys to the left */ - if (!so->currPos.moreLeft) + if (!currPos->moreLeft) { - _bt_relbuf(rel, so->currPos.buf); - _bt_parallel_done(scan); - BTScanPosInvalidate(so->currPos); + _bt_relbuf(rel, currPos->buf); + _bt_parallel_done(scan, state); + BTScanPosInvalidate(*currPos); return false; } /* Step to next physical page */ - so->currPos.buf = _bt_walk_left(rel, so->currPos.buf); + currPos->buf = _bt_walk_left(rel, currPos->buf); /* if we're physically at end of index, return failure */ - if (so->currPos.buf == InvalidBuffer) + if (currPos->buf == InvalidBuffer) { - _bt_parallel_done(scan); - BTScanPosInvalidate(so->currPos); + _bt_parallel_done(scan, state); + BTScanPosInvalidate(*currPos); return false; } @@ -2285,20 +2602,20 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) * it's not half-dead and contains matching tuples. Else loop back * and do it all again. */ - page = BufferGetPage(so->currPos.buf); + page = BufferGetPage(currPos->buf); opaque = BTPageGetOpaque(page); if (!P_IGNORE(opaque)) { - PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot); + PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot); /* see if there are any matches on this page */ /* note that this will clear moreLeft if we can stop */ - if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page), false)) + if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page), false)) break; } else if (scan->parallel_scan != NULL) { /* allow next page be processed by parallel worker */ - _bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf)); + _bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf)); } /* @@ -2309,14 +2626,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) */ if (scan->parallel_scan != NULL) { - _bt_relbuf(rel, so->currPos.buf); - status = _bt_parallel_seize(scan, &blkno, false); + _bt_relbuf(rel, currPos->buf); + status = _bt_parallel_seize(scan, state, &blkno, false); if (!status) { - BTScanPosInvalidate(so->currPos); + BTScanPosInvalidate(*currPos); return false; } - so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ); + currPos->buf = _bt_getbuf(rel, blkno, BT_READ); } } } @@ -2331,19 +2648,20 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) * indicate success. */ static bool -_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) +_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, + ScanDirection dir) { BTScanOpaque so = (BTScanOpaque) scan->opaque; Assert(!so->needPrimScan); - _bt_initialize_more_data(so, dir); + _bt_initialize_more_data(scan, state, dir); - if (!_bt_readnextpage(scan, blkno, dir)) + if (!_bt_readnextpage(scan, state, blkno, dir)) return false; - /* We have at least one item to return as scan's next item */ - _bt_drop_lock_and_maybe_pin(scan, &so->currPos); + /* Drop the lock, and maybe the pin, on the current page */ + _bt_drop_lock_and_maybe_pin(scan, &state->currPos); return true; } @@ -2561,11 +2879,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir) { Relation rel = scan->indexRelation; BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPos currPos = &so->state.currPos; Buffer buf; Page page; BTPageOpaque opaque; OffsetNumber start; - BTScanPosItem *currItem; /* * Scan down to the leftmost or rightmost leaf page. This is a simplified @@ -2581,7 +2899,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir) * exists. */ PredicateLockRelation(rel, scan->xs_snapshot); - BTScanPosInvalidate(so->currPos); + BTScanPosInvalidate(*currPos); return false; } @@ -2610,36 +2928,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir) } /* remember which buffer we have pinned */ - so->currPos.buf = buf; + currPos->buf = buf; - _bt_initialize_more_data(so, dir); + _bt_initialize_more_data(scan, &so->state, dir); - /* - * Now load data from the first page of the scan. - */ - if (!_bt_readpage(scan, dir, start, true)) - { - /* - * There's no actually-matching data on this page. Try to advance to - * the next page. Return false if there's no matching data at all. - */ - _bt_unlockbuf(scan->indexRelation, so->currPos.buf); - if (!_bt_steppage(scan, dir)) - return false; - } - else - { - /* We have at least one item to return as scan's first item */ - _bt_drop_lock_and_maybe_pin(scan, &so->currPos); - } - - /* OK, itemIndex says what to return */ - currItem = &so->currPos.items[so->currPos.itemIndex]; - scan->xs_heaptid = currItem->heapTid; - if (scan->xs_want_itup) - scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset); + if (!_bt_load_first_page(scan, &so->state, dir, start, NULL)) + return false; - return true; + /* OK, currPos->itemIndex says what to return */ + return _bt_return_current_item(scan, &so->state); } /* @@ -2647,27 +2944,29 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir) * from currPos */ static inline void -_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir) +_bt_initialize_more_data(IndexScanDesc scan, BTScanState state, ScanDirection dir) { - so->currPos.dir = dir; + BTScanOpaque so = (BTScanOpaque) scan->opaque; + + state->currPos.dir = dir; if (so->needPrimScan) { Assert(so->numArrayKeys); - so->currPos.moreLeft = true; - so->currPos.moreRight = true; + state->currPos.moreLeft = true; + state->currPos.moreRight = true; so->needPrimScan = false; } else if (ScanDirectionIsForward(dir)) { - so->currPos.moreLeft = false; - so->currPos.moreRight = true; + state->currPos.moreLeft = false; + state->currPos.moreRight = true; } else { - so->currPos.moreLeft = true; - so->currPos.moreRight = false; + state->currPos.moreLeft = true; + state->currPos.moreRight = false; } - so->numKilled = 0; /* just paranoia */ - so->markItemIndex = -1; /* ditto */ + state->numKilled = 0; /* just paranoia */ + state->markItemIndex = -1; /* ditto */ } diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index d6de2072d405..4f8253251746 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -20,6 +20,7 @@ #include "access/nbtree.h" #include "access/reloptions.h" #include "access/relscan.h" +#include "catalog/pg_amop.h" #include "commands/progress.h" #include "lib/qunique.h" #include "miscadmin.h" @@ -28,6 +29,7 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "utils/syscache.h" #define LOOK_AHEAD_REQUIRED_RECHECKS 3 #define LOOK_AHEAD_DEFAULT_DISTANCE 5 @@ -35,6 +37,9 @@ typedef struct BTSortArrayContext { FmgrInfo *sortproc; + FmgrInfo distflinfo; + FmgrInfo distcmpflinfo; + ScanKey distkey; Oid collation; bool reverse; } BTSortArrayContext; @@ -51,7 +56,7 @@ static void _bt_setup_array_cmp(IndexScanDesc scan, ScanKey skey, Oid elemtype, static Datum _bt_find_extreme_element(IndexScanDesc scan, ScanKey skey, Oid elemtype, StrategyNumber strat, Datum *elems, int nelems); -static int _bt_sort_array_elements(ScanKey skey, FmgrInfo *sortproc, +static int _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey, FmgrInfo *sortproc, bool reverse, Datum *elems, int nelems); static bool _bt_merge_arrays(IndexScanDesc scan, ScanKey skey, FmgrInfo *sortproc, bool reverse, @@ -102,6 +107,11 @@ static void _bt_checkkeys_look_ahead(IndexScanDesc scan, BTReadPageState *pstate int tupnatts, TupleDesc tupdesc); static int _bt_keep_natts(Relation rel, IndexTuple lastleft, IndexTuple firstright, BTScanInsert itup_key); +static inline StrategyNumber _bt_select_knn_strategy_for_key(IndexScanDesc scan, + ScanKey cond); +static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, + Oid leftargtype, FmgrInfo *finfo, + int16 *typlen, bool *typbyval); /* @@ -441,7 +451,7 @@ _bt_preprocess_array_keys(IndexScanDesc scan) * the index's key space. */ reverse = (indoption[cur->sk_attno - 1] & INDOPTION_DESC) != 0; - num_elems = _bt_sort_array_elements(cur, sortprocp, reverse, + num_elems = _bt_sort_array_elements(scan, cur, sortprocp, reverse, elem_values, num_nonnulls); if (origarrayatt == cur->sk_attno) @@ -846,18 +856,77 @@ _bt_find_extreme_element(IndexScanDesc scan, ScanKey skey, Oid elemtype, * we sort in descending order. */ static int -_bt_sort_array_elements(ScanKey skey, FmgrInfo *sortproc, bool reverse, +_bt_sort_array_elements(IndexScanDesc scan, ScanKey skey, FmgrInfo *sortproc, bool reverse, Datum *elems, int nelems) { + Relation rel = scan->indexRelation; + Oid elemtype; + Oid opfamily; BTSortArrayContext cxt; if (nelems <= 1) return nelems; /* no work to do */ + /* + * Determine the nominal datatype of the array elements. We have to + * support the convention that sk_subtype == InvalidOid means the opclass + * input type; this is a hack to simplify life for ScanKeyInit(). + */ + elemtype = skey->sk_subtype; + if (elemtype == InvalidOid) + elemtype = rel->rd_opcintype[skey->sk_attno - 1]; + + opfamily = rel->rd_opfamily[skey->sk_attno - 1]; + + if (scan->numberOfOrderBys <= 0 || + scan->orderByData[0].sk_attno != skey->sk_attno) + { + cxt.distkey = NULL; + cxt.reverse = reverse; + } + else + { + /* Init procedures for distance calculation and comparison. */ + ScanKey distkey = &scan->orderByData[0]; + ScanKeyData distkey2; + Oid disttype = distkey->sk_subtype; + Oid distopr; + RegProcedure distproc; + + if (!OidIsValid(disttype)) + disttype = rel->rd_opcintype[skey->sk_attno - 1]; + + /* Lookup distance operator in index column's operator family. */ + distopr = get_opfamily_member(opfamily, + elemtype, + disttype, + distkey->sk_strategy); + + if (!OidIsValid(distopr)) + elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u", + elemtype, disttype, BTMaxStrategyNumber, opfamily); + + distproc = get_opcode(distopr); + + if (!RegProcedureIsValid(distproc)) + elog(ERROR, "missing code for operator %u", distopr); + + fmgr_info(distproc, &cxt.distflinfo); + + distkey2 = *distkey; + fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext); + distkey2.sk_subtype = disttype; + + _bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype, + &cxt.distcmpflinfo, NULL, NULL); + + cxt.distkey = distkey; + cxt.reverse = false; /* supported only ascending ordering */ + } + /* Sort the array elements */ cxt.sortproc = sortproc; cxt.collation = skey->sk_collation; - cxt.reverse = reverse; qsort_arg(elems, nelems, sizeof(Datum), _bt_compare_array_elements, &cxt); @@ -930,6 +999,7 @@ _bt_merge_arrays(IndexScanDesc scan, ScanKey skey, FmgrInfo *sortproc, cxt.sortproc = mergeproc; cxt.collation = skey->sk_collation; cxt.reverse = reverse; + cxt.distkey = NULL; for (int i = 0, j = 0; i < nelems_orig_start && j < nelems_next;) { @@ -1103,6 +1173,24 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg) BTSortArrayContext *cxt = (BTSortArrayContext *) arg; int32 compare; + if (cxt->distkey) + { + Datum dista = FunctionCall2Coll(&cxt->distflinfo, + cxt->collation, + da, + cxt->distkey->sk_argument); + Datum distb = FunctionCall2Coll(&cxt->distflinfo, + cxt->collation, + db, + cxt->distkey->sk_argument); + bool cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo, + cxt->collation, + dista, + distb)); + + return cmp ? -1 : 1; + } + compare = DatumGetInt32(FunctionCall2Coll(cxt->sortproc, cxt->collation, da, db)); @@ -1721,7 +1809,7 @@ _bt_start_prim_scan(IndexScanDesc scan, ScanDirection dir) /* The top-level index scan ran out of tuples in this scan direction */ if (scan->parallel_scan != NULL) - _bt_parallel_done(scan); + _bt_parallel_done(scan, &so->state); return false; } @@ -2456,6 +2544,69 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate, /* Caller's tuple doesn't match any qual */ return false; } +/* + * _bt_emit_scan_key() -- Emit one prepared scan key + * + * Push the scan key into the so->keyData[] array, and then mark it if it is + * required. Also update selected kNN strategy. + */ +static void +_bt_emit_scan_key(IndexScanDesc scan, ScanKey skey, int numberOfEqualCols) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + ScanKey outkey = &so->keyData[so->numberOfKeys++]; + + memcpy(outkey, skey, sizeof(ScanKeyData)); + + /* + * We can mark the qual as required (possibly only in one direction) if + * all attrs before this one had "=". + */ + if (outkey->sk_attno - 1 == numberOfEqualCols) + _bt_mark_scankey_required(outkey); + + /* Update kNN strategy if it is not already selected. */ + if (so->useBidirectionalKnnScan) + { + switch (_bt_select_knn_strategy_for_key(scan, outkey)) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + + /* + * Ordering key argument is greater than all values in scan + * range, select backward scan direction. + */ + so->scanDirection = BackwardScanDirection; + so->useBidirectionalKnnScan = false; + break; + + case BTEqualStrategyNumber: + /* Use default unidirectional scan direction. */ + so->useBidirectionalKnnScan = false; + break; + + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + + /* + * Ordering key argument is lesser than all values in scan + * range, select forward scan direction. + */ + so->scanDirection = ForwardScanDirection; + so->useBidirectionalKnnScan = false; + break; + + case BTMaxStrategyNumber: + + /* + * Ordering key argument falls into scan range, keep using + * bidirectional scan. + */ + break; + } + } +} /* * _bt_preprocess_keys() -- Preprocess scan keys @@ -2548,12 +2699,10 @@ _bt_preprocess_keys(IndexScanDesc scan) BTScanOpaque so = (BTScanOpaque) scan->opaque; int numberOfKeys = scan->numberOfKeys; int16 *indoption = scan->indexRelation->rd_indoption; - int new_numberOfKeys; int numberOfEqualCols; ScanKey inkeys; - ScanKey outkeys; ScanKey cur; - BTScanKeyPreproc xform[BTMaxStrategyNumber]; + BTScanKeyPreproc xform[BTMaxSearchStrategyNumber]; bool test_result; int i, j; @@ -2576,6 +2725,25 @@ _bt_preprocess_keys(IndexScanDesc scan) return; } + if (scan->numberOfOrderBys > 0) + { + ScanKey ord = scan->orderByData; + + if (scan->numberOfOrderBys > 1 || ord->sk_attno != 1) + /* it should not happen, see btmatchorderby() */ + elog(ERROR, "only one btree ordering operator " + "for the first index column is supported"); + + Assert(ord->sk_strategy == BTMaxStrategyNumber); + + /* use bidirectional kNN scan by default */ + so->useBidirectionalKnnScan = true; + } + else + { + so->useBidirectionalKnnScan = false; + } + /* initialize result variables */ so->qual_ok = true; so->numberOfKeys = 0; @@ -2607,7 +2775,6 @@ _bt_preprocess_keys(IndexScanDesc scan) else inkeys = scan->keyData; - outkeys = so->keyData; cur = &inkeys[0]; /* we check that input keys are correctly ordered */ if (cur->sk_attno < 1) @@ -2619,11 +2786,9 @@ _bt_preprocess_keys(IndexScanDesc scan) /* Apply indoption to scankey (might change sk_strategy!) */ if (!_bt_fix_scankey_strategy(cur, indoption)) so->qual_ok = false; - memcpy(outkeys, cur, sizeof(ScanKeyData)); - so->numberOfKeys = 1; - /* We can mark the qual as required if it's for first index col */ - if (cur->sk_attno == 1) - _bt_mark_scankey_required(outkeys); + + _bt_emit_scan_key(scan, cur, 0); + if (arrayKeyData) { /* @@ -2636,14 +2801,12 @@ _bt_preprocess_keys(IndexScanDesc scan) (so->arrayKeys[0].scan_key == 0 && OidIsValid(so->orderProcs[0].fn_oid))); } - return; } /* * Otherwise, do the full set of pushups. */ - new_numberOfKeys = 0; numberOfEqualCols = 0; /* @@ -2716,7 +2879,7 @@ _bt_preprocess_keys(IndexScanDesc scan) Assert(OidIsValid(orderproc->fn_oid)); } - for (j = BTMaxStrategyNumber; --j >= 0;) + for (j = BTMaxSearchStrategyNumber; --j >= 0;) { ScanKey chk = xform[j].skey; @@ -2786,21 +2949,17 @@ _bt_preprocess_keys(IndexScanDesc scan) } /* - * Emit the cleaned-up keys into the outkeys[] array, and then + * Emit the cleaned-up keys into the so->keyData[] array, and then * mark them if they are required. They are required (possibly * only in one direction) if all attrs before this one had "=". */ - for (j = BTMaxStrategyNumber; --j >= 0;) + for (j = BTMaxSearchStrategyNumber; --j >= 0;) { if (xform[j].skey) { - ScanKey outkey = &outkeys[new_numberOfKeys++]; - - memcpy(outkey, xform[j].skey, sizeof(ScanKeyData)); + _bt_emit_scan_key(scan, xform[j].skey, priorNumberOfEqualCols); if (arrayKeyData) - keyDataMap[new_numberOfKeys - 1] = xform[j].ikey; - if (priorNumberOfEqualCols == attno - 1) - _bt_mark_scankey_required(outkey); + keyDataMap[so->numberOfKeys - 1] = xform[j].ikey; } } @@ -2821,19 +2980,16 @@ _bt_preprocess_keys(IndexScanDesc scan) /* if row comparison, push it directly to the output array */ if (cur->sk_flags & SK_ROW_HEADER) { - ScanKey outkey = &outkeys[new_numberOfKeys++]; - - memcpy(outkey, cur, sizeof(ScanKeyData)); + _bt_emit_scan_key(scan, cur, numberOfEqualCols); if (arrayKeyData) - keyDataMap[new_numberOfKeys - 1] = i; - if (numberOfEqualCols == attno - 1) - _bt_mark_scankey_required(outkey); + keyDataMap[so->numberOfKeys - 1] = i; /* * We don't support RowCompare using equality; such a qual would * mess up the numberOfEqualCols tracking. */ Assert(j != (BTEqualStrategyNumber - 1)); + continue; } @@ -2959,22 +3115,15 @@ _bt_preprocess_keys(IndexScanDesc scan) * even with incomplete opfamilies. _bt_advance_array_keys * depends on this. */ - ScanKey outkey = &outkeys[new_numberOfKeys++]; - - memcpy(outkey, xform[j].skey, sizeof(ScanKeyData)); + _bt_emit_scan_key(scan, xform[j].skey, numberOfEqualCols); if (arrayKeyData) - keyDataMap[new_numberOfKeys - 1] = xform[j].ikey; - if (numberOfEqualCols == attno - 1) - _bt_mark_scankey_required(outkey); + keyDataMap[so->numberOfKeys - 1] = xform[j].ikey; xform[j].skey = cur; xform[j].ikey = i; xform[j].arrayidx = arrayidx; } } } - - so->numberOfKeys = new_numberOfKeys; - /* * Now that we've built a temporary mapping from so->keyData[] (output * scan keys) to scan->keyData[] (input scan keys), fix array->scan_key @@ -4162,27 +4311,27 @@ _bt_checkkeys_look_ahead(IndexScanDesc scan, BTReadPageState *pstate, * away and the TID was re-used by a completely different heap tuple. */ void -_bt_killitems(IndexScanDesc scan) +_bt_killitems(BTScanState state, Relation indexRelation) { - BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPos pos = &state->currPos; Page page; BTPageOpaque opaque; OffsetNumber minoff; OffsetNumber maxoff; int i; - int numKilled = so->numKilled; + int numKilled = state->numKilled; bool killedsomething = false; bool droppedpin PG_USED_FOR_ASSERTS_ONLY; - Assert(BTScanPosIsValid(so->currPos)); + Assert(BTScanPosIsValid(state->currPos)); /* * Always reset the scan state, so we don't look for same items on other * pages. */ - so->numKilled = 0; + state->numKilled = 0; - if (BTScanPosIsPinned(so->currPos)) + if (BTScanPosIsPinned(*pos)) { /* * We have held the pin on this page since we read the index tuples, @@ -4191,9 +4340,7 @@ _bt_killitems(IndexScanDesc scan) * LSN. */ droppedpin = false; - _bt_lockbuf(scan->indexRelation, so->currPos.buf, BT_READ); - - page = BufferGetPage(so->currPos.buf); + _bt_lockbuf(indexRelation, pos->buf, BT_READ); } else { @@ -4201,31 +4348,31 @@ _bt_killitems(IndexScanDesc scan) droppedpin = true; /* Attempt to re-read the buffer, getting pin and lock. */ - buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ); + buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ); - page = BufferGetPage(buf); - if (BufferGetLSNAtomic(buf) == so->currPos.lsn) - so->currPos.buf = buf; + if (BufferGetLSNAtomic(buf) == pos->lsn) + pos->buf = buf; else { /* Modified while not pinned means hinting is not safe. */ - _bt_relbuf(scan->indexRelation, buf); + _bt_relbuf(indexRelation, buf); return; } } + page = BufferGetPage(pos->buf); opaque = BTPageGetOpaque(page); minoff = P_FIRSTDATAKEY(opaque); maxoff = PageGetMaxOffsetNumber(page); for (i = 0; i < numKilled; i++) { - int itemIndex = so->killedItems[i]; - BTScanPosItem *kitem = &so->currPos.items[itemIndex]; + int itemIndex = state->killedItems[i]; + BTScanPosItem *kitem = &pos->items[itemIndex]; OffsetNumber offnum = kitem->indexOffset; - Assert(itemIndex >= so->currPos.firstItem && - itemIndex <= so->currPos.lastItem); + Assert(itemIndex >= pos->firstItem && + itemIndex <= pos->lastItem); if (offnum < minoff) continue; /* pure paranoia */ while (offnum <= maxoff) @@ -4283,7 +4430,7 @@ _bt_killitems(IndexScanDesc scan) * correctly -- posting tuple still gets killed). */ if (pi < numKilled) - kitem = &so->currPos.items[so->killedItems[pi++]]; + kitem = &state->currPos.items[state->killedItems[pi++]]; } /* @@ -4330,10 +4477,10 @@ _bt_killitems(IndexScanDesc scan) if (killedsomething) { opaque->btpo_flags |= BTP_HAS_GARBAGE; - MarkBufferDirtyHint(so->currPos.buf, true); + MarkBufferDirtyHint(pos->buf, true); } - _bt_unlockbuf(scan->indexRelation, so->currPos.buf); + _bt_unlockbuf(indexRelation, pos->buf); } @@ -4585,6 +4732,39 @@ btproperty(Oid index_oid, int attno, *res = true; return true; + case AMPROP_DISTANCE_ORDERABLE: + { + Oid opclass, + opfamily, + opcindtype; + + /* answer only for columns, not AM or whole index */ + if (attno == 0) + return false; + + opclass = get_index_column_opclass(index_oid, attno); + + if (!OidIsValid(opclass)) + { + *res = false; /* non-key attribute */ + return true; + } + + if (!get_opclass_opfamily_and_input_type(opclass, + &opfamily, &opcindtype)) + { + *isnull = true; + return true; + } + + *res = SearchSysCacheExists(AMOPSTRATEGY, + ObjectIdGetDatum(opfamily), + ObjectIdGetDatum(opcindtype), + ObjectIdGetDatum(opcindtype), + Int16GetDatum(BTMaxStrategyNumber)); + return true; + } + default: return false; /* punt to generic code */ } @@ -5170,3 +5350,227 @@ _bt_allequalimage(Relation rel, bool debugmessage) return allequalimage; } + +/* + * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples + * in index-only scans. + */ +void +_bt_allocate_tuple_workspaces(BTScanState state) +{ + state->currTuples = (char *) palloc(BLCKSZ * 2); + state->markTuples = state->currTuples + BLCKSZ; +} + +static bool +_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result) +{ + ScanKey subkey = (ScanKey) DatumGetPointer(row->sk_argument); + int32 cmpresult; + + Assert(subkey->sk_attno == 1); + Assert(subkey->sk_flags & SK_ROW_MEMBER); + + if (subkey->sk_flags & SK_ISNULL) + return false; + + /* Perform the test --- three-way comparison not bool operator */ + cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func, + subkey->sk_collation, + ord->sk_argument, + subkey->sk_argument)); + + if (subkey->sk_flags & SK_BT_DESC) + cmpresult = -cmpresult; + + /* + * At this point cmpresult indicates the overall result of the row + * comparison, and subkey points to the deciding column (or the last + * column if the result is "="). + */ + switch (subkey->sk_strategy) + { + /* EQ and NE cases aren't allowed here */ + case BTLessStrategyNumber: + *result = cmpresult < 0; + break; + case BTLessEqualStrategyNumber: + *result = cmpresult <= 0; + break; + case BTGreaterEqualStrategyNumber: + *result = cmpresult >= 0; + break; + case BTGreaterStrategyNumber: + *result = cmpresult > 0; + break; + default: + elog(ERROR, "unrecognized RowCompareType: %d", + (int) subkey->sk_strategy); + *result = false; /* keep compiler quiet */ + } + + return true; +} + +/* + * _bt_select_knn_strategy_for_key() -- Determine which kNN scan strategy to use: + * bidirectional or unidirectional. We are checking here if the + * ordering scankey argument falls into the scan range: if it falls + * we must use bidirectional scan, otherwise we use unidirectional. + * + * Returns BTMaxStrategyNumber for bidirectional scan or + * strategy number of non-matched scankey for unidirectional. + */ +static inline StrategyNumber +_bt_select_knn_strategy_for_key(IndexScanDesc scan, ScanKey cond) +{ + ScanKey ord = scan->orderByData; + bool result; + + /* only interesting in the first index attribute */ + if (cond->sk_attno != 1) + return BTMaxStrategyNumber; + + if (cond->sk_strategy == BTEqualStrategyNumber) + /* always use simple unidirectional scan for equals operators */ + return BTEqualStrategyNumber; + + if (cond->sk_flags & SK_ROW_HEADER) + { + if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result)) + return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS + * NULL */ + } + else + { + if (!_bt_compare_scankey_args(scan, cond, ord, cond, NULL, NULL, &result)) + elog(ERROR, "could not compare ordering key"); + } + + if (!result) + + /* + * Ordering scankey argument is out of scan range, use unidirectional + * scan. + */ + return cond->sk_strategy; + + return BTMaxStrategyNumber; +} + +int +_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys, ScanKey bufKeys) +{ + ScanKey ord = scan->orderByData; + int indopt = scan->indexRelation->rd_indoption[ord->sk_attno - 1]; + int flags = (indopt << SK_BT_INDOPTION_SHIFT) | + SK_ORDER_BY | + SK_SEARCHNULL; /* only for invalid procedure oid, see assert + * in ScanKeyEntryInitialize() */ + int keysCount = 0; + + /* Init btree search key with ordering key argument. */ + ScanKeyEntryInitialize(&bufKeys[0], + flags, + ord->sk_attno, + BTMaxStrategyNumber, + ord->sk_subtype, + ord->sk_collation, + InvalidOid, + ord->sk_argument); + + startKeys[keysCount++] = &bufKeys[0]; + + return keysCount; +} + +static Oid +_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype, + StrategyNumber strategy) +{ + HeapTuple tp; + Form_pg_amop amop_tup; + Oid sortfamily; + + tp = SearchSysCache4(AMOPSTRATEGY, + ObjectIdGetDatum(opfamily), + ObjectIdGetDatum(lefttype), + ObjectIdGetDatum(righttype), + Int16GetDatum(strategy)); + if (!HeapTupleIsValid(tp)) + return InvalidOid; + amop_tup = (Form_pg_amop) GETSTRUCT(tp); + sortfamily = amop_tup->amopsortfamily; + ReleaseSysCache(tp); + + return sortfamily; +} + +/* + * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances + * between "leftargtype" and "distkey". + */ +static void +_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype, + FmgrInfo *finfo, int16 *typlen, bool *typbyval) +{ + RegProcedure opcode; + Oid sortfamily; + Oid opno; + Oid distanceType; + + distanceType = get_func_rettype(distkey->sk_func.fn_oid); + + sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype, + distkey->sk_subtype, + distkey->sk_strategy); + + if (!OidIsValid(sortfamily)) + elog(ERROR, "could not find sort family for btree ordering operator"); + + opno = get_opfamily_member(sortfamily, + distanceType, + distanceType, + BTLessEqualStrategyNumber); + + if (!OidIsValid(opno)) + elog(ERROR, "could not find operator for btree distance comparison"); + + opcode = get_opcode(opno); + + if (!RegProcedureIsValid(opcode)) + elog(ERROR, + "could not find procedure for btree distance comparison operator"); + + fmgr_info(opcode, finfo); + + if (typlen) + get_typlenbyval(distanceType, typlen, typbyval); +} + +/* + * _bt_init_distance_comparison() -- Init distance typlen/typbyval and its + * comparison procedure. + */ +void +_bt_init_distance_comparison(IndexScanDesc scan) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + Relation rel = scan->indexRelation; + ScanKey ord = scan->orderByData; + + _bt_get_distance_cmp_proc(ord, + rel->rd_opfamily[ord->sk_attno - 1], + rel->rd_opcintype[ord->sk_attno - 1], + &so->distanceCmpProc, + &so->distanceTypeLen, + &so->distanceTypeByVal); + + /* + * In fact, distance values need to be initialized only for by-ref types, + * because previous distance values are pfreed before writing new ones + * (see _bt_calc_current_dist()). + */ + so->state.currDistance = (Datum) 0; + so->state.markDistance = (Datum) 0; +} diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c index e9d4cd60de3c..3c91d74512f7 100644 --- a/src/backend/access/nbtree/nbtvalidate.c +++ b/src/backend/access/nbtree/nbtvalidate.c @@ -28,6 +28,13 @@ #include "utils/regproc.h" #include "utils/syscache.h" +#define BTRequiredOperatorSet \ + ((1 << BTLessStrategyNumber) | \ + (1 << BTLessEqualStrategyNumber) | \ + (1 << BTEqualStrategyNumber) | \ + (1 << BTGreaterEqualStrategyNumber) | \ + (1 << BTGreaterStrategyNumber)) + /* * Validator for a btree opclass. @@ -142,6 +149,7 @@ btvalidate(Oid opclassoid) { HeapTuple oprtup = &oprlist->members[i]->tuple; Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); + Oid op_rettype; /* Check that only allowed strategy numbers exist */ if (oprform->amopstrategy < 1 || @@ -156,20 +164,29 @@ btvalidate(Oid opclassoid) result = false; } - /* btree doesn't support ORDER BY operators */ - if (oprform->amoppurpose != AMOP_SEARCH || - OidIsValid(oprform->amopsortfamily)) + /* btree supports ORDER BY operators */ + if (oprform->amoppurpose != AMOP_SEARCH) { - ereport(INFO, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s", - opfamilyname, "btree", - format_operator(oprform->amopopr)))); - result = false; + /* ... and operator result must match the claimed btree opfamily */ + op_rettype = get_op_rettype(oprform->amopopr); + if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype)) + { + ereport(INFO, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s", + opfamilyname, "btree", + format_operator(oprform->amopopr)))); + result = false; + } + } + else + { + /* Search operators must always return bool */ + op_rettype = BOOLOID; } /* Check operator signature --- same for all btree strategies */ - if (!check_amop_signature(oprform->amopopr, BOOLOID, + if (!check_amop_signature(oprform->amopopr, op_rettype, oprform->amoplefttype, oprform->amoprighttype)) { @@ -224,12 +241,8 @@ btvalidate(Oid opclassoid) * or support functions for this datatype pair. The sortsupport, * in_range, and equalimage functions are considered optional. */ - if (thisgroup->operatorset != - ((1 << BTLessStrategyNumber) | - (1 << BTLessEqualStrategyNumber) | - (1 << BTEqualStrategyNumber) | - (1 << BTGreaterEqualStrategyNumber) | - (1 << BTGreaterStrategyNumber))) + if ((thisgroup->operatorset & BTRequiredOperatorSet) != + BTRequiredOperatorSet) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index 76b80146ff01..2ba8cbc966be 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -50,6 +50,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amoptsprocnum = SPGIST_OPTIONS_PROC; amroutine->amcanorder = false; amroutine->amcanorderbyop = true; + amroutine->amorderbyopfirstcol = false; amroutine->amcanbackward = false; amroutine->amcanunique = false; amroutine->amcanmulticol = false; diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 3289e3e02199..a6545d900462 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -553,9 +553,15 @@ ExecSupportsBackwardScan(Plan *node) return false; case T_IndexScan: + /* Backward ORDER BY operator scans are not supported. */ + if (((IndexScan *) node)->indexorderby) + return false; return IndexSupportsBackwardScan(((IndexScan *) node)->indexid); case T_IndexOnlyScan: + /* Backward ORDER BY operator scans are not supported. */ + if (((IndexOnlyScan *) node)->indexorderby) + return false; return IndexSupportsBackwardScan(((IndexOnlyScan *) node)->indexid); case T_SubqueryScan: diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index 8000feff4c9f..b2332c03dd9b 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -183,10 +183,8 @@ IndexNextWithReorder(IndexScanState *node) * Only forward scan is supported with reordering. Note: we can get away * with just Asserting here because the system will not try to run the * plan backwards if ExecSupportsBackwardScan() says it won't work. - * Currently, that is guaranteed because no index AMs support both - * amcanorderbyop and amcanbackward; if any ever do, - * ExecSupportsBackwardScan() will need to consider indexorderbys - * explicitly. + * Currently, ExecSupportsBackwardScan() simply returns false for index + * plans with indexorderbys. */ Assert(!ScanDirectionIsBackward(((IndexScan *) node->ss.ps.plan)->indexorderdir)); Assert(ScanDirectionIsForward(estate->es_direction)); diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index c0fcc7d78dfc..36bfaab77fce 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -907,6 +907,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, * many of them are actually useful for this query. This is not relevant * if we are only trying to build bitmap indexscans. */ + useful_pathkeys = NIL; + orderbyclauses = NIL; + orderbyclausecols = NIL; + pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN && has_useful_pathkeys(root, rel)); index_is_ordered = (index->sortopfamily != NULL); @@ -916,16 +920,19 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, ForwardScanDirection); useful_pathkeys = truncate_useless_pathkeys(root, rel, index_pathkeys); - orderbyclauses = NIL; - orderbyclausecols = NIL; } - else if (index->amcanorderbyop && pathkeys_possibly_useful) + + if (useful_pathkeys == NIL && + index->amcanorderbyop && pathkeys_possibly_useful) { /* * See if we can generate ordering operators for query_pathkeys or at * least some prefix thereof. Matching to just a prefix of the * query_pathkeys will allow an incremental sort to be considered on * the index's partially sorted results. + * Index access method can be both ordered and supporting ordering by + * operator. We're looking for ordering by operator only when native + * ordering doesn't match. */ match_pathkeys_to_index(index, root->query_pathkeys, &orderbyclauses, @@ -936,12 +943,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, useful_pathkeys = list_copy_head(root->query_pathkeys, list_length(orderbyclauses)); } - else - { - useful_pathkeys = NIL; - orderbyclauses = NIL; - orderbyclausecols = NIL; - } /* * 3. Check if an index-only scan is possible. If we're not building @@ -3030,6 +3031,10 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, if (!index->amcanorderbyop) return; + /* Only the one pathkey is supported when amorderbyopfirstcol is true */ + if (index->amorderbyopfirstcol && list_length(pathkeys) != 1) + return; + foreach(lc1, pathkeys) { PathKey *pathkey = (PathKey *) lfirst(lc1); @@ -3058,20 +3063,24 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, { EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2); int indexcol; + int ncolumns; /* No possibility of match if it references other relations */ if (!bms_equal(member->em_relids, index->rel->relids)) continue; /* - * We allow any column of the index to match each pathkey; they - * don't have to match left-to-right as you might expect. This is - * correct for GiST, and it doesn't matter for SP-GiST because - * that doesn't handle multiple columns anyway, and no other - * existing AMs support amcanorderbyop. We might need different - * logic in future for other implementations. + * We allow any column or only the first of the index to match + * each pathkey; they don't have to match left-to-right as you + * might expect. This is correct for GiST, and it doesn't matter + * for SP-GiST and B-Tree because they do not handle multiple + * columns anyway, and no other existing AMs support + * amcanorderbyop. We might need different logic in future for + * other implementations. */ - for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++) + ncolumns = index->amorderbyopfirstcol ? 1 : index->nkeycolumns; + + for (indexcol = 0; indexcol < ncolumns; indexcol++) { Expr *expr; diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9a1a7faac7ad..1b6b2ff299dc 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -1385,7 +1385,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context, { PartitionScheme part_scheme = context->rel->part_scheme; List *opsteps = NIL; - List *btree_clauses[BTMaxStrategyNumber + 1], + List *btree_clauses[BTMaxSearchStrategyNumber + 1], *hash_clauses[HTMaxStrategyNumber + 1]; int i; ListCell *lc; @@ -1497,7 +1497,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context, * combinations of expressions of different keys, which * get_steps_using_prefix takes care of for us. */ - for (strat = 1; strat <= BTMaxStrategyNumber; strat++) + for (strat = 1; strat <= BTMaxSearchStrategyNumber; strat++) { foreach(lc, btree_clauses[strat]) { diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index ec3c08acfc2d..94bf236880fa 100644 --- a/src/backend/utils/adt/cash.c +++ b/src/backend/utils/adt/cash.c @@ -30,6 +30,7 @@ #include "utils/numeric.h" #include "utils/pg_locale.h" +#define SAMESIGN(a,b) (((a) < 0) == ((b) < 0)) /************************************************************************* * Private routines @@ -1191,3 +1192,22 @@ int8_cash(PG_FUNCTION_ARGS) PG_RETURN_CASH(result); } + +Datum +cash_distance(PG_FUNCTION_ARGS) +{ + Cash a = PG_GETARG_CASH(0); + Cash b = PG_GETARG_CASH(1); + Cash r; + Cash ra; + + if (pg_sub_s64_overflow(a, b, &r) || + r == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); + + ra = i64abs(r); + + PG_RETURN_CASH(ra); +} diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 9c854e0e5c35..73e1a4f5b6a0 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -546,6 +546,16 @@ date_mii(PG_FUNCTION_ARGS) PG_RETURN_DATEADT(result); } +Datum +date_distance(PG_FUNCTION_ARGS) +{ + /* we assume the difference can't overflow */ + Datum diff = DirectFunctionCall2(date_mi, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1)); + + PG_RETURN_INT32(abs(DatumGetInt32(diff))); +} /* * Promote date to timestamp. @@ -840,6 +850,29 @@ date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2) return timestamptz_cmp_internal(dt1, dt2); } +Datum +date_dist_timestamp(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + Timestamp dt2 = PG_GETARG_TIMESTAMP(1); + Timestamp dt1; + + if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2)) + { + Interval *r = palloc(sizeof(Interval)); + + r->day = INT_MAX; + r->month = INT_MAX; + r->time = PG_INT64_MAX; + + PG_RETURN_INTERVAL_P(r); + } + + dt1 = date2timestamp(dateVal); + + PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2)); +} + Datum date_eq_timestamptz(PG_FUNCTION_ARGS) { @@ -903,6 +936,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS) PG_RETURN_INT32(date_cmp_timestamptz_internal(dateVal, dt2)); } +Datum +date_dist_timestamptz(PG_FUNCTION_ARGS) +{ + DateADT dateVal = PG_GETARG_DATEADT(0); + TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1); + TimestampTz dt1; + + if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2)) + { + Interval *r = palloc(sizeof(Interval)); + + r->day = INT_MAX; + r->month = INT_MAX; + r->time = PG_INT64_MAX; + + PG_RETURN_INTERVAL_P(r); + } + + dt1 = date2timestamptz(dateVal); + + PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2)); +} + + Datum timestamp_eq_date(PG_FUNCTION_ARGS) { @@ -966,6 +1023,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS) PG_RETURN_INT32(-date_cmp_timestamp_internal(dateVal, dt1)); } +Datum +timestamp_dist_date(PG_FUNCTION_ARGS) +{ + Timestamp dt1 = PG_GETARG_TIMESTAMP(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + Timestamp dt2; + + if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1)) + { + Interval *r = palloc(sizeof(Interval)); + + r->day = INT_MAX; + r->month = INT_MAX; + r->time = PG_INT64_MAX; + + PG_RETURN_INTERVAL_P(r); + } + + dt2 = date2timestamp(dateVal); + + PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2)); +} + Datum timestamptz_eq_date(PG_FUNCTION_ARGS) { @@ -1058,6 +1138,28 @@ in_range_date_interval(PG_FUNCTION_ARGS) BoolGetDatum(less)); } +Datum +timestamptz_dist_date(PG_FUNCTION_ARGS) +{ + TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0); + DateADT dateVal = PG_GETARG_DATEADT(1); + TimestampTz dt2; + + if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1)) + { + Interval *r = palloc(sizeof(Interval)); + + r->day = INT_MAX; + r->month = INT_MAX; + r->time = PG_INT64_MAX; + + PG_RETURN_INTERVAL_P(r); + } + + dt2 = date2timestamptz(dateVal); + + PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2)); +} /* extract_date() * Extract specified field from date type. @@ -2251,6 +2353,16 @@ extract_time(PG_FUNCTION_ARGS) return time_part_common(fcinfo, true); } +Datum +time_distance(PG_FUNCTION_ARGS) +{ + Datum diff = DirectFunctionCall2(time_mi_time, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1)); + + PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff))); +} + /***************************************************************************** * Time With Time Zone ADT diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index f709c21e1fe3..b66359b6f21b 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -4088,3 +4088,52 @@ width_bucket_float8(PG_FUNCTION_ARGS) PG_RETURN_INT32(result); } + +Datum +float4dist(PG_FUNCTION_ARGS) +{ + float4 a = PG_GETARG_FLOAT4(0); + float4 b = PG_GETARG_FLOAT4(1); + float4 r; + + r = a - b; + if (unlikely(isinf(r)) && !isinf(a) && !isinf(b)) + float_overflow_error(); + + PG_RETURN_FLOAT4(fabsf(r)); +} + +Datum +float8dist(PG_FUNCTION_ARGS) +{ + float8 a = PG_GETARG_FLOAT8(0); + float8 b = PG_GETARG_FLOAT8(1); + float8 r; + + r = a - b; + if (unlikely(isinf(r)) && !isinf(a) && !isinf(b)) + float_overflow_error(); + + PG_RETURN_FLOAT8(fabs(r)); +} + + +Datum +float48dist(PG_FUNCTION_ARGS) +{ + float4 a = PG_GETARG_FLOAT4(0); + float8 b = PG_GETARG_FLOAT8(1); + float8 r = float8_mi(a, b); + + PG_RETURN_FLOAT8(fabs(r)); +} + +Datum +float84dist(PG_FUNCTION_ARGS) +{ + float8 a = PG_GETARG_FLOAT8(0); + float4 b = PG_GETARG_FLOAT4(1); + float8 r = float8_mi(a, b); + + PG_RETURN_FLOAT8(fabs(r)); +} \ No newline at end of file diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index 234f20796b76..87664cf2e6c4 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -1647,3 +1647,54 @@ generate_series_int4_support(PG_FUNCTION_ARGS) PG_RETURN_POINTER(ret); } + +Datum +int2dist(PG_FUNCTION_ARGS) +{ + int16 a = PG_GETARG_INT16(0); + int16 b = PG_GETARG_INT16(1); + int16 r; + int16 ra; + + if (pg_sub_s16_overflow(a, b, &r) || + r == PG_INT16_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + ra = abs(r); + + PG_RETURN_INT16(ra); +} + +static int32 +int44_dist(int32 a, int32 b) +{ + int32 r; + + if (pg_sub_s32_overflow(a, b, &r) || + r == PG_INT32_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + return abs(r); +} + +Datum +int4dist(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1))); +} + +Datum +int24dist(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1))); +} + +Datum +int42dist(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1))); +} diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 54fa3bc37999..e81c68e47e18 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1521,3 +1521,47 @@ generate_series_int8_support(PG_FUNCTION_ARGS) PG_RETURN_POINTER(ret); } + +static int64 +int88_dist(int64 a, int64 b) +{ + int64 r; + + if (pg_sub_s64_overflow(a, b, &r) || + r == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + return i64abs(r); +} + +Datum +int8dist(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1))); +} + +Datum +int82dist(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1))); +} + +Datum +int84dist(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1))); +} + +Datum +int28dist(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1))); +} + +Datum +int48dist(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1))); +} diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c index 56fb1fd77cee..65521a6ce9ed 100644 --- a/src/backend/utils/adt/oid.c +++ b/src/backend/utils/adt/oid.c @@ -387,3 +387,24 @@ oidvectorgt(PG_FUNCTION_ARGS) PG_RETURN_BOOL(cmp > 0); } + +Datum +oiddist(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + Oid res; + bool overflow; + + if (a < b) + overflow = pg_sub_u32_overflow(b, a, &res); + else + overflow = pg_sub_u32_overflow(a, b, &res); + + if (overflow) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("oid out of range"))); + + PG_RETURN_OID(res); +} diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 69fe7860ede0..280092bbf42e 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -2865,6 +2865,86 @@ timestamp_mi(PG_FUNCTION_ARGS) PG_RETURN_INTERVAL_P(result); } +Datum +timestamp_distance(PG_FUNCTION_ARGS) +{ + Timestamp a = PG_GETARG_TIMESTAMP(0); + Timestamp b = PG_GETARG_TIMESTAMP(1); + Interval *r; + + if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b)) + { + Interval *p = palloc(sizeof(Interval)); + + p->day = INT_MAX; + p->month = INT_MAX; + p->time = PG_INT64_MAX; + PG_RETURN_INTERVAL_P(p); + } + else + r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + PG_RETURN_INTERVAL_P(abs_interval(r)); +} + +Interval * +timestamp_dist_internal(Timestamp a, Timestamp b) +{ + Interval *r; + + if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b)) + { + r = palloc(sizeof(Interval)); + + r->day = INT_MAX; + r->month = INT_MAX; + r->time = PG_INT64_MAX; + + return r; + } + + r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, + TimestampGetDatum(a), + TimestampGetDatum(b))); + + return abs_interval(r); +} + +Datum +timestamptz_distance(PG_FUNCTION_ARGS) +{ + TimestampTz a = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz b = PG_GETARG_TIMESTAMPTZ(1); + + PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b)); +} + +Datum +timestamp_dist_timestamptz(PG_FUNCTION_ARGS) +{ + Timestamp ts1 = PG_GETARG_TIMESTAMP(0); + TimestampTz tstz2 = PG_GETARG_TIMESTAMPTZ(1); + TimestampTz tstz1; + + tstz1 = timestamp2timestamptz(ts1); + + PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2)); +} + +Datum +timestamptz_dist_timestamp(PG_FUNCTION_ARGS) +{ + TimestampTz tstz1 = PG_GETARG_TIMESTAMPTZ(0); + Timestamp ts2 = PG_GETARG_TIMESTAMP(1); + TimestampTz tstz2; + + tstz2 = timestamp2timestamptz(ts2); + + PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2)); +} + + /* * interval_justify_interval() * @@ -4237,6 +4317,29 @@ interval_sum(PG_FUNCTION_ARGS) PG_RETURN_INTERVAL_P(result); } +Interval * +abs_interval(Interval *a) +{ + static Interval zero = {0, 0, 0}; + + if (DatumGetBool(DirectFunctionCall2(interval_lt, + IntervalPGetDatum(a), + IntervalPGetDatum(&zero)))) + a = DatumGetIntervalP(DirectFunctionCall1(interval_um, + IntervalPGetDatum(a))); + + return a; +} + +Datum +interval_distance(PG_FUNCTION_ARGS) +{ + Datum diff = DirectFunctionCall2(interval_mi, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1)); + + PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff))); +} /* timestamp_age() * Calculate time difference while retaining year/month fields. diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index f25c9d58a7da..21b5d3149f89 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -224,6 +224,12 @@ typedef struct IndexAmRoutine bool amcanorder; /* does AM support ORDER BY result of an operator on indexed column? */ bool amcanorderbyop; + + /* + * Does AM support only the one ORDER BY operator on first indexed column? + * amcanorderbyop is implied. + */ + bool amorderbyopfirstcol; /* does AM support backward scanning? */ bool amcanbackward; /* does AM support UNIQUE indexes? */ diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 749304334809..d2da1b964f28 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -682,7 +682,7 @@ BTreeTupleGetMaxHeapTID(IndexTuple itup) * The strategy numbers are chosen so that we can commute them by * subtraction, thus: */ -#define BTCommuteStrategyNumber(strat) (BTMaxStrategyNumber + 1 - (strat)) +#define BTCommuteStrategyNumber(strat) (BTMaxSearchStrategyNumber + 1 - (strat)) /* * When a new operator class is declared, we require that the user @@ -916,7 +916,8 @@ typedef BTVacuumPostingData *BTVacuumPosting; /* * BTScanOpaqueData is the btree-private state needed for an indexscan. * This consists of preprocessed scan keys (see _bt_preprocess_keys() for - * details of the preprocessing), information about the current location + * details of the preprocessing), and tree scan state itself (BTScanStateData). + * In turn, BTScanStateData contains information about the current location * of the scan, and information about the marked location, if any. (We use * BTScanPosData to represent the data needed for each of current and marked * locations.) In addition we can remember some known-killed index entries @@ -1037,21 +1038,8 @@ typedef struct BTArrayKeyInfo Datum *elem_values; /* array of num_elems Datums */ } BTArrayKeyInfo; -typedef struct BTScanOpaqueData +typedef struct BTScanStateData { - /* these fields are set by _bt_preprocess_keys(): */ - bool qual_ok; /* false if qual can never be satisfied */ - int numberOfKeys; /* number of preprocessed scan keys */ - ScanKey keyData; /* array of preprocessed scan keys */ - - /* workspace for SK_SEARCHARRAY support */ - int numArrayKeys; /* number of equality-type array keys */ - bool needPrimScan; /* New prim scan to continue in current dir? */ - bool scanBehind; /* Last array advancement matched -inf attr? */ - BTArrayKeyInfo *arrayKeys; /* info about each equality-type array key */ - FmgrInfo *orderProcs; /* ORDER procs for required equality keys */ - MemoryContext arrayContext; /* scan-lifespan context for array data */ - /* info about killed items if any (killedItems is NULL if never used) */ int *killedItems; /* currPos.items indexes of killed items */ int numKilled; /* number of currently stored items */ @@ -1076,6 +1064,45 @@ typedef struct BTScanOpaqueData /* keep these last in struct for efficiency */ BTScanPosData currPos; /* current position data */ BTScanPosData markPos; /* marked position, if any */ + + /* KNN-search fields: */ + Datum currDistance; /* distance to the current item */ + Datum markDistance; /* distance to the marked item */ + bool currIsNull; /* current item is NULL */ + bool markIsNull; /* marked item is NULL */ +} BTScanStateData; + +typedef BTScanStateData *BTScanState; + +typedef struct BTScanOpaqueData +{ + /* these fields are set by _bt_preprocess_keys(): */ + bool qual_ok; /* false if qual can never be satisfied */ + int numberOfKeys; /* number of preprocessed scan keys */ + ScanKey keyData; /* array of preprocessed scan keys */ + + /* workspace for SK_SEARCHARRAY support */ + int numArrayKeys; /* number of equality-type array keys */ + bool needPrimScan; /* New prim scan to continue in current dir? */ + bool scanBehind; /* Last array advancement matched -inf attr? */ + BTArrayKeyInfo *arrayKeys; /* info about each equality-type array key */ + FmgrInfo *orderProcs; /* ORDER procs for required equality keys */ + MemoryContext arrayContext; /* scan-lifespan context for array data */ + + /* the state of main tree scan */ + BTScanStateData state; + + /* kNN-search fields: */ + bool useBidirectionalKnnScan; /* use bidirectional kNN scan? */ + BTScanState forwardState; + BTScanState backwardState; /* optional scan state for kNN search */ + ScanDirection scanDirection; /* selected scan direction for + * unidirectional kNN scan */ + FmgrInfo distanceCmpProc; /* distance comparison procedure */ + int16 distanceTypeLen; /* distance typlen */ + bool distanceTypeByVal; /* distance typebyval */ + bool currRightIsNearest; /* current right item is nearest */ + bool markRightIsNearest; /* marked right item is nearest */ } BTScanOpaqueData; typedef BTScanOpaqueData *BTScanOpaque; @@ -1190,13 +1217,14 @@ extern bool btcanreturn(Relation index, int attno); /* * prototypes for internal functions in nbtree.c */ -extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, +extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno, bool first); -extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page); -extern void _bt_parallel_done(IndexScanDesc scan); +extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page); +extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state); extern void _bt_parallel_primscan_schedule(IndexScanDesc scan, BlockNumber prev_scan_page); + /* * prototypes for functions in nbtdedup.c */ @@ -1291,7 +1319,7 @@ extern void _bt_start_array_keys(IndexScanDesc scan, ScanDirection dir); extern void _bt_preprocess_keys(IndexScanDesc scan); extern bool _bt_checkkeys(IndexScanDesc scan, BTReadPageState *pstate, bool arrayKeys, IndexTuple tuple, int tupnatts); -extern void _bt_killitems(IndexScanDesc scan); +extern void _bt_killitems(BTScanState state, Relation indexRelation); extern BTCycleId _bt_vacuum_cycleid(Relation rel); extern BTCycleId _bt_start_vacuum(Relation rel); extern void _bt_end_vacuum(Relation rel); @@ -1312,6 +1340,10 @@ extern bool _bt_check_natts(Relation rel, bool heapkeyspace, Page page, extern void _bt_check_third_page(Relation rel, Relation heap, bool needheaptidspace, Page page, IndexTuple newtup); extern bool _bt_allequalimage(Relation rel, bool debugmessage); +extern void _bt_allocate_tuple_workspaces(BTScanState state); +extern void _bt_init_distance_comparison(IndexScanDesc scan); +extern int _bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys, + ScanKey bufKeys); /* * prototypes for functions in nbtvalidate.c diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h index 8a47d3c9ec80..ccf2e0b92693 100644 --- a/src/include/access/stratnum.h +++ b/src/include/access/stratnum.h @@ -32,7 +32,12 @@ typedef uint16 StrategyNumber; #define BTGreaterEqualStrategyNumber 4 #define BTGreaterStrategyNumber 5 -#define BTMaxStrategyNumber 5 +#define BTMaxSearchStrategyNumber 5 /* number of B-tree search + * strategies */ + +#define BTNearestStrategyNumber 6 /* for ordering by <-> operator */ +#define BTMaxStrategyNumber 6 /* total numer of B-tree + * strategies */ /* * Strategy numbers for hash indexes. There's only one valid strategy for diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index d8a05214b118..805ae021e4c0 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -30,6 +30,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int2', amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2', + amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int2,int2)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # crosstype operators int24 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2', @@ -47,6 +51,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int2', amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2', + amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int2,int4)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # crosstype operators int28 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2', @@ -64,6 +72,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int2', amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2', + amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int2,int8)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # default operators int4 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4', @@ -81,6 +93,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int4', amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4', + amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int4,int4)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # crosstype operators int42 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4', @@ -98,6 +114,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int4', amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4', + amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int4,int2)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # crosstype operators int48 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4', @@ -115,6 +135,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int4', amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4', + amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int4,int8)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # default operators int8 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8', @@ -132,6 +156,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int8', amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8', + amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int8,int8)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # crosstype operators int82 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8', @@ -149,6 +177,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int8', amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8', + amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int8,int2)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # crosstype operators int84 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8', @@ -166,6 +198,10 @@ { amopfamily => 'btree/integer_ops', amoplefttype => 'int8', amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)', amopmethod => 'btree' }, +{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8', + amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(int8,int4)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # btree oid_ops @@ -179,6 +215,10 @@ amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' }, { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid', amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' }, +{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid', + amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(oid,oid)', amopmethod => 'btree', + amopsortfamily => 'btree/oid_ops' }, # btree xid8_ops @@ -247,6 +287,10 @@ { amopfamily => 'btree/float_ops', amoplefttype => 'float4', amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)', amopmethod => 'btree' }, +{ amopfamily => 'btree/float_ops', amoplefttype => 'float4', + amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(float4,float4)', amopmethod => 'btree', + amopsortfamily => 'btree/float_ops' }, # crosstype operators float48 { amopfamily => 'btree/float_ops', amoplefttype => 'float4', @@ -264,6 +308,10 @@ { amopfamily => 'btree/float_ops', amoplefttype => 'float4', amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)', amopmethod => 'btree' }, +{ amopfamily => 'btree/float_ops', amoplefttype => 'float4', + amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(float4,float8)', amopmethod => 'btree', + amopsortfamily => 'btree/float_ops' }, # default operators float8 { amopfamily => 'btree/float_ops', amoplefttype => 'float8', @@ -281,6 +329,10 @@ { amopfamily => 'btree/float_ops', amoplefttype => 'float8', amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)', amopmethod => 'btree' }, +{ amopfamily => 'btree/float_ops', amoplefttype => 'float8', + amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(float8,float8)', amopmethod => 'btree', + amopsortfamily => 'btree/float_ops' }, # crosstype operators float84 { amopfamily => 'btree/float_ops', amoplefttype => 'float8', @@ -298,6 +350,10 @@ { amopfamily => 'btree/float_ops', amoplefttype => 'float8', amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)', amopmethod => 'btree' }, +{ amopfamily => 'btree/float_ops', amoplefttype => 'float8', + amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(float8,float4)', amopmethod => 'btree', + amopsortfamily => 'btree/float_ops' }, # btree char_ops @@ -434,6 +490,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'date', amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date', + amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(date,date)', amopmethod => 'btree', + amopsortfamily => 'btree/integer_ops' }, # crosstype operators vs timestamp { amopfamily => 'btree/datetime_ops', amoplefttype => 'date', @@ -451,6 +511,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'date', amoprighttype => 'timestamp', amopstrategy => '5', amopopr => '>(date,timestamp)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date', + amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(date,timestamp)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # crosstype operators vs timestamptz { amopfamily => 'btree/datetime_ops', amoplefttype => 'date', @@ -468,6 +532,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'date', amoprighttype => 'timestamptz', amopstrategy => '5', amopopr => '>(date,timestamptz)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date', + amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(date,timestamptz)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # default operators timestamp { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', @@ -485,6 +553,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', amoprighttype => 'timestamp', amopstrategy => '5', amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', + amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # crosstype operators vs date { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', @@ -502,6 +574,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', + amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(timestamp,date)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # crosstype operators vs timestamptz { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', @@ -519,6 +595,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', amoprighttype => 'timestamptz', amopstrategy => '5', amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp', + amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # default operators timestamptz { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', @@ -536,6 +616,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', amoprighttype => 'timestamptz', amopstrategy => '5', amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', + amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # crosstype operators vs date { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', @@ -553,6 +637,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamptz,date)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', + amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(timestamptz,date)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # crosstype operators vs timestamp { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', @@ -570,6 +658,10 @@ { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', amoprighttype => 'timestamp', amopstrategy => '5', amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' }, +{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz', + amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # btree time_ops @@ -588,6 +680,10 @@ { amopfamily => 'btree/time_ops', amoplefttype => 'time', amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)', amopmethod => 'btree' }, +{ amopfamily => 'btree/time_ops', amoplefttype => 'time', + amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(time,time)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # btree timetz_ops @@ -624,6 +720,10 @@ { amopfamily => 'btree/interval_ops', amoplefttype => 'interval', amoprighttype => 'interval', amopstrategy => '5', amopopr => '>(interval,interval)', amopmethod => 'btree' }, +{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval', + amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(interval,interval)', amopmethod => 'btree', + amopsortfamily => 'btree/interval_ops' }, # btree macaddr @@ -799,6 +899,10 @@ { amopfamily => 'btree/money_ops', amoplefttype => 'money', amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)', amopmethod => 'btree' }, +{ amopfamily => 'btree/money_ops', amoplefttype => 'money', + amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o', + amopopr => '<->(money,money)', amopmethod => 'btree', + amopsortfamily => 'btree/money_ops' }, # btree array_ops diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0e7511dde1c6..0d45443cb431 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2845,6 +2845,114 @@ oprname => '-', oprleft => 'pg_lsn', oprright => 'numeric', oprresult => 'pg_lsn', oprcode => 'pg_lsn_mii' }, +# distance operators +{ oid => '9447', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2', + oprright => 'int2', oprresult => 'int2', + oprcode => 'int2dist'}, +{ oid => '9448', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4', + oprright => 'int4', oprresult => 'int4', + oprcode => 'int4dist'}, +{ oid => '9449', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8', + oprright => 'int8', oprresult => 'int8', + oprcode => 'int8dist'}, +{ oid => '9450', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid', + oprright => 'oid', oprresult => 'oid', + oprcode => 'oiddist'}, +{ oid => '9451', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4', + oprright => 'float4', oprresult => 'float4', + oprcode => 'float4dist'}, +{ oid => '9452', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8', + oprright => 'float8', oprresult => 'float8', + oprcode => 'float8dist'}, +{ oid => '9453', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money', + oprright => 'money', oprresult => 'money', + oprcode => 'cash_distance'}, +{ oid => '9454', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date', + oprright => 'date', oprresult => 'int4', + oprcode => 'date_distance'}, +{ oid => '9455', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time', + oprright => 'time', oprresult => 'interval', + oprcode => 'time_distance'}, +{ oid => '9456', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp', + oprright => 'timestamp', oprresult => 'interval', + oprcode => 'timestamp_distance'}, +{ oid => '9457', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz', + oprright => 'timestamptz', oprresult => 'interval', + oprcode => 'timestamptz_distance'}, +{ oid => '9458', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval', + oprright => 'interval', oprresult => 'interval', + oprcode => 'interval_distance'}, + +# cross-type distance operators +{ oid => '9432', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2', + oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)', + oprcode => 'int24dist'}, +{ oid => '9433', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4', + oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)', + oprcode => 'int42dist'}, +{ oid => '9434', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2', + oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)', + oprcode => 'int28dist'}, +{ oid => '9435', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8', + oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)', + oprcode => 'int82dist'}, +{ oid => '9436', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4', + oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)', + oprcode => 'int48dist'}, +{ oid => '9437', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8', + oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)', + oprcode => 'int84dist'}, +{ oid => '9438', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4', + oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)', + oprcode => 'float48dist'}, +{ oid => '9439', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8', + oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)', + oprcode => 'float84dist'}, +{ oid => '9440', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date', + oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)', + oprcode => 'date_dist_timestamp'}, +{ oid => '9441', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp', + oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)', + oprcode => 'timestamp_dist_date'}, +{ oid => '9442', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date', + oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)', + oprcode => 'date_dist_timestamptz'}, +{ oid => '9443', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz', + oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)', + oprcode => 'timestamptz_dist_date'}, +{ oid => '9444', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp', + oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)', + oprcode => 'timestamp_dist_timestamptz'}, +{ oid => '9445', descr => 'distance between', + oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz', + oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)', + oprcode => 'timestamptz_dist_timestamp'}, + # enum operators { oid => '3516', descr => 'equal', oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index d36f6001bb1a..f615f9ffea79 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12247,4 +12247,90 @@ proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}', prosrc => 'pg_get_wal_summarizer_state' }, +# distance functions +{ oid => '9406', + proname => 'int2dist', prorettype => 'int2', + proargtypes => 'int2 int2', prosrc => 'int2dist' }, +{ oid => '9407', + proname => 'int4dist', prorettype => 'int4', + proargtypes => 'int4 int4', prosrc => 'int4dist' }, +{ oid => '9408', + proname => 'int8dist', prorettype => 'int8', + proargtypes => 'int8 int8', prosrc => 'int8dist' }, +{ oid => '9409', + proname => 'oiddist', prorettype => 'oid', + proargtypes => 'oid oid', prosrc => 'oiddist' }, +{ oid => '9410', + proname => 'float4dist', prorettype => 'float4', + proargtypes => 'float4 float4', prosrc => 'float4dist' }, +{ oid => '9411', + proname => 'float8dist', prorettype => 'float8', + proargtypes => 'float8 float8', prosrc => 'float8dist' }, +{ oid => '9412', + proname => 'cash_distance', prorettype => 'money', + proargtypes => 'money money', prosrc => 'cash_distance' }, +{ oid => '9413', + proname => 'date_distance', prorettype => 'int4', + proargtypes => 'date date', prosrc => 'date_distance' }, +{ oid => '9414', + proname => 'time_distance', prorettype => 'interval', + proargtypes => 'time time', prosrc => 'time_distance' }, +{ oid => '9415', + proname => 'timestamp_distance', prorettype => 'interval', + proargtypes => 'timestamp timestamp', prosrc => 'timestamp_distance' }, +{ oid => '9416', + proname => 'timestamptz_distance', prorettype => 'interval', + proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_distance' }, +{ oid => '9417', + proname => 'interval_distance', prorettype => 'interval', + proargtypes => 'interval interval', prosrc => 'interval_distance' }, + +# cross-type distance functions +{ oid => '9418', + proname => 'int24dist', prorettype => 'int4', + proargtypes => 'int2 int4', prosrc => 'int24dist' }, +{ oid => '9419', + proname => 'int28dist', prorettype => 'int8', + proargtypes => 'int2 int8', prosrc => 'int28dist' }, +{ oid => '9420', + proname => 'int42dist', prorettype => 'int4', + proargtypes => 'int4 int2', prosrc => 'int42dist' }, +{ oid => '9221', + proname => 'int48dist', prorettype => 'int8', + proargtypes => 'int4 int8', prosrc => 'int48dist' }, +{ oid => '9422', + proname => 'int82dist', prorettype => 'int8', + proargtypes => 'int8 int2', prosrc => 'int82dist' }, +{ oid => '9423', + proname => 'int84dist', prorettype => 'int8', + proargtypes => 'int8 int4', prosrc => 'int84dist' }, +{ oid => '9424', + proname => 'float48dist', prorettype => 'float8', + proargtypes => 'float4 float8', prosrc => 'float48dist' }, +{ oid => '9425', + proname => 'float84dist', prorettype => 'float8', + proargtypes => 'float8 float4', prosrc => 'float84dist' }, +{ oid => '9426', + proname => 'date_dist_timestamp', prorettype => 'interval', + proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' }, +{ oid => '9427', + proname => 'date_dist_timestamptz', provolatile => 's', + prorettype => 'interval', proargtypes => 'date timestamptz', + prosrc => 'date_dist_timestamptz' }, +{ oid => '9428', + proname => 'timestamp_dist_date', prorettype => 'interval', + proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' }, +{ oid => '9429', + proname => 'timestamp_dist_timestamptz', provolatile => 's', + prorettype => 'interval', proargtypes => 'timestamp timestamptz', + prosrc => 'timestamp_dist_timestamptz' }, +{ oid => '9430', + proname => 'timestamptz_dist_date', provolatile => 's', + prorettype => 'interval', proargtypes => 'timestamptz date', + prosrc => 'timestamptz_dist_date' }, +{ oid => '9431', + proname => 'timestamptz_dist_timestamp', provolatile => 's', + prorettype => 'interval', proargtypes => 'timestamptz timestamp', + prosrc => 'timestamptz_dist_timestamp' }, + ] diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 14ccfc1ac1c7..b7267f3ecfdc 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1188,6 +1188,8 @@ struct IndexOptInfo * (IndexAmRoutine). These fields are not set for partitioned indexes. */ bool amcanorderbyop; + bool amorderbyopfirstcol; /* order by op is supported only on + * first column? */ bool amoptionalkey; bool amsearcharray; bool amsearchnulls; diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index e4ac2b8e7f6c..b334b61e6ffb 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -364,4 +364,6 @@ extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); extern bool AdjustTimestampForTypmod(Timestamp *time, int32 typmod, struct Node *escontext); +extern Interval *abs_interval(Interval *a); + #endif /* DATETIME_H */ diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index a6ce03ed4604..27015a36bbd2 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -126,9 +126,11 @@ extern Timestamp SetEpochTimestamp(void); extern void GetEpochTime(struct pg_tm *tm); extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2); +extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b); -/* timestamp comparison works for timestamptz also */ +/* timestamp comparison and distance works for timestamptz also */ #define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2) +#define timestamptz_dist_internal(dt1,dt2) timestamp_dist_internal(dt1, dt2) extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp, int *overflow); diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out index ae54cb254f90..0b08c29bebb1 100644 --- a/src/test/regress/expected/alter_generic.out +++ b/src/test/regress/expected/alter_generic.out @@ -355,10 +355,10 @@ ROLLBACK; CREATE OPERATOR FAMILY alt_opf4 USING btree; ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD OPERATOR 1 < (int4, int2); -- invalid indexing_method ERROR: access method "invalid_index_method" does not exist -ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5 -ERROR: invalid operator number 6, must be between 1 and 5 -ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5 -ERROR: invalid operator number 0, must be between 1 and 5 +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6 +ERROR: invalid operator number 7, must be between 1 and 6 +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6 +ERROR: invalid operator number 0, must be between 1 and 6 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types ERROR: operator argument types must be specified in ALTER OPERATOR FAMILY ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- invalid options parsing function @@ -405,11 +405,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree; CREATE OPERATOR FAMILY alt_opf9 USING gist; ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops; DROP OPERATOR FAMILY alt_opf9 USING gist; --- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY +-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY +BEGIN TRANSACTION; CREATE OPERATOR FAMILY alt_opf10 USING btree; ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops; -ERROR: access method "btree" does not support ordering operators DROP OPERATOR FAMILY alt_opf10 USING btree; +ROLLBACK; -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY CREATE OPERATOR FAMILY alt_opf11 USING gist; ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops; diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out index 7ab6113c6191..1b39abccbf85 100644 --- a/src/test/regress/expected/amutils.out +++ b/src/test/regress/expected/amutils.out @@ -24,7 +24,7 @@ select prop, nulls_first | | | f nulls_last | | | t orderable | | | t - distance_orderable | | | f + distance_orderable | | | t returnable | | | t search_array | | | t search_nulls | | | t @@ -100,7 +100,7 @@ select prop, nulls_first | f | f | f | f | f | f | f nulls_last | t | f | f | f | f | f | f orderable | t | f | f | f | f | f | f - distance_orderable | f | f | t | f | t | f | f + distance_orderable | t | f | t | f | t | f | f returnable | t | f | f | t | t | f | f search_array | t | f | f | f | f | f | f search_nulls | t | f | t | t | t | f | t @@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop) 1 | desc | f 1 | nulls_first | f 1 | nulls_last | t - 1 | distance_orderable | f + 1 | distance_orderable | t 1 | returnable | t 1 | bogus | 2 | orderable | f diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out index 510646cbce71..66f94af12111 100644 --- a/src/test/regress/expected/btree_index.out +++ b/src/test/regress/expected/btree_index.out @@ -486,3 +486,957 @@ ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100); ERROR: ALTER action ALTER COLUMN ... SET cannot be performed on relation "btree_part_idx" DETAIL: This operation is not supported for partitioned indexes. DROP TABLE btree_part; +--- +--- Test B-tree distance ordering +--- +SET enable_bitmapscan = OFF; +-- temporarily disable bt_i4_index index on bt_i4_heap(seqno) +UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass; +CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno); +-- test unsupported orderings (by non-first index attribute or by more than one order keys) +EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0; + QUERY PLAN +----------------------------------------------------------- + Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap + Order By: (seqno <-> 0) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0; + QUERY PLAN +----------------------------------------------------------- + Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap + Order By: ((random <-> 0) AND (seqno <-> 0)) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1; + QUERY PLAN +----------------------------------------------------------- + Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap + Order By: ((random <-> 0) AND (random <-> 1)) +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 4000000; + QUERY PLAN +------------------------------------------------------------------------------- + Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap + Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0))) + Order By: (random <-> 4000000) +(3 rows) + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 4000000; + seqno | random +-------+--------- + 6448 | 4157193 + 9004 | 3783884 + 4408 | 4488889 + 8391 | 4825069 + 8984 | 3148979 + 1829 | 3053937 + 6262 | 3013326 + 5380 | 3000193 + 9142 | 2847247 + 8411 | 2809541 + 2859 | 5224694 + 6320 | 5257716 + 2126 | 2648497 + 8729 | 5450460 + 6862 | 5556001 + 1836 | 5593978 + 2681 | 2321799 + 2893 | 1919087 + 210 | 1809552 +(19 rows) + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 10000000; + seqno | random +-------+--------- + 1836 | 5593978 + 6862 | 5556001 + 8729 | 5450460 + 6320 | 5257716 + 2859 | 5224694 + 8391 | 4825069 + 4408 | 4488889 + 6448 | 4157193 + 9004 | 3783884 + 8984 | 3148979 + 1829 | 3053937 + 6262 | 3013326 + 5380 | 3000193 + 9142 | 2847247 + 8411 | 2809541 + 2126 | 2648497 + 2681 | 2321799 + 2893 | 1919087 + 210 | 1809552 +(19 rows) + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 0; + seqno | random +-------+--------- + 210 | 1809552 + 2893 | 1919087 + 2681 | 2321799 + 2126 | 2648497 + 8411 | 2809541 + 9142 | 2847247 + 5380 | 3000193 + 6262 | 3013326 + 1829 | 3053937 + 8984 | 3148979 + 9004 | 3783884 + 6448 | 4157193 + 4408 | 4488889 + 8391 | 4825069 + 2859 | 5224694 + 6320 | 5257716 + 8729 | 5450460 + 6862 | 5556001 + 1836 | 5593978 +(19 rows) + +EXPLAIN (COSTS OFF) +SELECT * FROM bt_i4_heap +WHERE + random > 1000000 AND (random, seqno) < (6000000, 0) AND + random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL) +ORDER BY random <-> 3000000; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap + Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[]))) + Order By: (random <-> 3000000) +(3 rows) + +SELECT * FROM bt_i4_heap +WHERE + random > 1000000 AND (random, seqno) < (6000000, 0) AND + random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL) +ORDER BY random <-> 3000000; + seqno | random +-------+--------- + 5380 | 3000193 + 6262 | 3013326 + 2126 | 2648497 + 2681 | 2321799 + 2893 | 1919087 + 6448 | 4157193 + 210 | 1809552 + 4408 | 4488889 + 6320 | 5257716 + 1836 | 5593978 +(10 rows) + +DROP INDEX bt_i4_heap_random_idx; +CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno); +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 4000000; + seqno | random +-------+--------- + 6448 | 4157193 + 9004 | 3783884 + 4408 | 4488889 + 8391 | 4825069 + 8984 | 3148979 + 1829 | 3053937 + 6262 | 3013326 + 5380 | 3000193 + 9142 | 2847247 + 8411 | 2809541 + 2859 | 5224694 + 6320 | 5257716 + 2126 | 2648497 + 8729 | 5450460 + 6862 | 5556001 + 1836 | 5593978 + 2681 | 2321799 + 2893 | 1919087 + 210 | 1809552 +(19 rows) + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 10000000; + seqno | random +-------+--------- + 1836 | 5593978 + 6862 | 5556001 + 8729 | 5450460 + 6320 | 5257716 + 2859 | 5224694 + 8391 | 4825069 + 4408 | 4488889 + 6448 | 4157193 + 9004 | 3783884 + 8984 | 3148979 + 1829 | 3053937 + 6262 | 3013326 + 5380 | 3000193 + 9142 | 2847247 + 8411 | 2809541 + 2126 | 2648497 + 2681 | 2321799 + 2893 | 1919087 + 210 | 1809552 +(19 rows) + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 0; + seqno | random +-------+--------- + 210 | 1809552 + 2893 | 1919087 + 2681 | 2321799 + 2126 | 2648497 + 8411 | 2809541 + 9142 | 2847247 + 5380 | 3000193 + 6262 | 3013326 + 1829 | 3053937 + 8984 | 3148979 + 9004 | 3783884 + 6448 | 4157193 + 4408 | 4488889 + 8391 | 4825069 + 2859 | 5224694 + 6320 | 5257716 + 8729 | 5450460 + 6862 | 5556001 + 1836 | 5593978 +(19 rows) + +DROP INDEX bt_i4_heap_random_idx; +-- test parallel KNN scan +-- Serializable isolation would disable parallel query, so explicitly use an +-- arbitrary other level. +BEGIN ISOLATION LEVEL REPEATABLE READ; +SET parallel_setup_cost = 0; +SET parallel_tuple_cost = 0; +SET min_parallel_table_scan_size = 0; +SET max_parallel_workers = 4; +SET max_parallel_workers_per_gather = 4; +SET cpu_operator_cost = 0; +RESET enable_indexscan; +\set bt_knn_row_count 100000 +CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, :bt_knn_row_count) i; +CREATE INDEX bt_knn_test_idx ON bt_knn_test (i); +ALTER TABLE bt_knn_test SET (parallel_workers = 4); +ANALYZE bt_knn_test; +-- set the point inside the range +\set bt_knn_point (4 * :bt_knn_row_count + 3) +CREATE TABLE bt_knn_test2 AS + SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i + FROM generate_series(1, :bt_knn_row_count) i; +SET enable_sort = OFF; +EXPLAIN (COSTS OFF) +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + QUERY PLAN +--------------------------------------------------------------------------------- + Hash Join + Hash Cond: ((row_number() OVER (?)) = t2.n) + Join Filter: (bt_knn_test.i <> t2.i) + -> WindowAgg + -> Gather Merge + Workers Planned: 4 + -> Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test + Order By: (i <-> 400003) + -> Hash + -> Gather + Workers Planned: 4 + -> Parallel Seq Scan on bt_knn_test2 t2 +(12 rows) + +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + n | i | i +---+---+--- +(0 rows) + +RESET enable_sort; +DROP TABLE bt_knn_test2; +-- set the point to the right of the range +\set bt_knn_point (11 * :bt_knn_row_count) +CREATE TABLE bt_knn_test2 AS + SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i + FROM generate_series(1, :bt_knn_row_count) i; +SET enable_sort = OFF; +EXPLAIN (COSTS OFF) +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + QUERY PLAN +--------------------------------------------------------------------------------- + Hash Join + Hash Cond: ((row_number() OVER (?)) = t2.n) + Join Filter: (bt_knn_test.i <> t2.i) + -> WindowAgg + -> Gather Merge + Workers Planned: 4 + -> Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test + Order By: (i <-> 1100000) + -> Hash + -> Gather + Workers Planned: 4 + -> Parallel Seq Scan on bt_knn_test2 t2 +(12 rows) + +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + n | i | i +---+---+--- +(0 rows) + +RESET enable_sort; +DROP TABLE bt_knn_test2; +-- set the point to the left of the range +\set bt_knn_point (-:bt_knn_row_count) +CREATE TABLE bt_knn_test2 AS + SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i + FROM generate_series(1, :bt_knn_row_count) i; +SET enable_sort = OFF; +EXPLAIN (COSTS OFF) +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + QUERY PLAN +--------------------------------------------------------------------------------- + Hash Join + Hash Cond: ((row_number() OVER (?)) = t2.n) + Join Filter: (bt_knn_test.i <> t2.i) + -> WindowAgg + -> Gather Merge + Workers Planned: 4 + -> Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test + Order By: (i <-> '-100000'::integer) + -> Hash + -> Gather + Workers Planned: 4 + -> Parallel Seq Scan on bt_knn_test2 t2 +(12 rows) + +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + n | i | i +---+---+--- +(0 rows) + +RESET enable_sort; +DROP TABLE bt_knn_test; +\set knn_row_count 30000 +CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, :knn_row_count) j; +CREATE INDEX bt_knn_test_idx ON bt_knn_test (i); +ALTER TABLE bt_knn_test SET (parallel_workers = 4); +ANALYZE bt_knn_test; +SET enable_sort = OFF; +EXPLAIN (COSTS OFF) +WITH +t1 AS ( + SELECT row_number() OVER () AS n, i + FROM bt_knn_test + WHERE i IN (3, 4, 7, 8, 2) + ORDER BY i <-> 4 +), +t2 AS ( + SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i + FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j +) +SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i; + QUERY PLAN +--------------------------------------------------------------------------------- + Hash Join + Hash Cond: ((row_number() OVER (?)) = ((i.i * 30000) + j.j)) + Join Filter: (bt_knn_test.i <> ('{4,3,2,7,8}'::integer[])[(i.i + 1)]) + -> WindowAgg + -> Gather Merge + Workers Planned: 4 + -> Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test + Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[])) + Order By: (i <-> 4) + -> Hash + -> Nested Loop + -> Function Scan on generate_series i + -> Function Scan on generate_series j +(13 rows) + +WITH +t1 AS ( + SELECT row_number() OVER () AS n, i + FROM bt_knn_test + WHERE i IN (3, 4, 7, 8, 2) + ORDER BY i <-> 4 +), +t2 AS ( + SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i + FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j +) +SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i; + n | i | i +---+---+--- +(0 rows) + +RESET enable_sort; +RESET parallel_setup_cost; +RESET parallel_tuple_cost; +RESET min_parallel_table_scan_size; +RESET max_parallel_workers; +RESET max_parallel_workers_per_gather; +RESET cpu_operator_cost; +ROLLBACK; +-- enable bt_i4_index index on bt_i4_heap(seqno) +UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass; +CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1; +INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3); +-- Test distance ordering by ASC index +CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous); +EXPLAIN (COSTS OFF) +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 998; + QUERY PLAN +----------------------------------------------------------- + Index Only Scan using tenk3_idx on tenk3 + Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000)) + Order By: (thousand <-> 998) +(3 rows) + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 998; + thousand | tenthous +----------+---------- + 998 | 998 + 998 | 1998 + 998 | 2998 + 998 | 3998 + 998 | 4998 + 998 | 5998 + 998 | 6998 + 998 | 7998 + 998 | 8998 + 998 | 9998 + 999 | 999 + 999 | 1999 + 999 | 2999 + 999 | 3999 + 999 | 4999 + 999 | 5999 + 999 | 6999 + 999 | 7999 + 999 | 8999 + 999 | 9999 + 997 | 9997 + 997 | 8997 + 997 | 7997 + 997 | 6997 + 997 | 5997 +(25 rows) + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 0; + thousand | tenthous +----------+---------- + 997 | 5997 + 997 | 6997 + 997 | 7997 + 997 | 8997 + 997 | 9997 + 998 | 998 + 998 | 1998 + 998 | 2998 + 998 | 3998 + 998 | 4998 + 998 | 5998 + 998 | 6998 + 998 | 7998 + 998 | 8998 + 998 | 9998 + 999 | 999 + 999 | 1999 + 999 | 2999 + 999 | 3999 + 999 | 4999 + 999 | 5999 + 999 | 6999 + 999 | 7999 + 999 | 8999 + 999 | 9999 +(25 rows) + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000 +ORDER BY thousand <-> 10000; + thousand | tenthous +----------+---------- + 999 | 9999 + 999 | 8999 + 999 | 7999 + 999 | 6999 + 999 | 5999 + 999 | 4999 + 999 | 3999 + 999 | 2999 + 999 | 1999 + 999 | 999 + 998 | 9998 + 998 | 8998 + 998 | 7998 + 998 | 6998 + 998 | 5998 + 998 | 4998 + 998 | 3998 + 998 | 2998 + 998 | 1998 + 998 | 998 + 997 | 9997 + 997 | 8997 + 997 | 7997 + 997 | 6997 + 997 | 5997 +(25 rows) + +SELECT thousand, tenthous FROM tenk3 +ORDER BY thousand <-> 500 +OFFSET 9970; + thousand | tenthous +----------+---------- + 999 | 999 + 999 | 1999 + 999 | 2999 + 999 | 3999 + 999 | 4999 + 999 | 5999 + 999 | 6999 + 999 | 7999 + 999 | 8999 + 999 | 9999 + 1 | 9001 + 1 | 8001 + 1 | 7001 + 1 | 6001 + 1 | 5001 + 1 | 4001 + 1 | 3001 + 1 | 2001 + 1 | 1001 + 1 | 1 + 0 | 9000 + 0 | 8000 + 0 | 7000 + 0 | 6000 + 0 | 5000 + 0 | 4000 + 0 | 3000 + 0 | 2000 + 0 | 1000 + 0 | 0 + | 1 + | 2 + | 3 +(33 rows) + +EXPLAIN (COSTS OFF) +SELECT * FROM tenk3 +WHERE thousand > 100 AND thousand < 800 AND + thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[]) +ORDER BY thousand <-> 300::int8; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Index Only Scan using tenk3_idx on tenk3 + Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[]))) + Order By: (thousand <-> '300'::bigint) +(3 rows) + +SELECT * FROM tenk3 +WHERE thousand > 100 AND thousand < 800 AND + thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[]) +ORDER BY thousand <-> 300::int8; + thousand | tenthous +----------+---------- + 345 | 345 + 345 | 1345 + 345 | 2345 + 345 | 3345 + 345 | 4345 + 345 | 5345 + 345 | 6345 + 345 | 7345 + 345 | 8345 + 345 | 9345 + 234 | 234 + 234 | 1234 + 234 | 2234 + 234 | 3234 + 234 | 4234 + 234 | 5234 + 234 | 6234 + 234 | 7234 + 234 | 8234 + 234 | 9234 + 456 | 456 + 456 | 1456 + 456 | 2456 + 456 | 3456 + 456 | 4456 + 456 | 5456 + 456 | 6456 + 456 | 7456 + 456 | 8456 + 456 | 9456 + 123 | 123 + 123 | 1123 + 123 | 2123 + 123 | 3123 + 123 | 4123 + 123 | 5123 + 123 | 6123 + 123 | 7123 + 123 | 8123 + 123 | 9123 + 678 | 678 + 678 | 1678 + 678 | 2678 + 678 | 3678 + 678 | 4678 + 678 | 5678 + 678 | 6678 + 678 | 7678 + 678 | 8678 + 678 | 9678 +(50 rows) + +DROP INDEX tenk3_idx; +-- Test distance ordering by DESC index +CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous); +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 998; + thousand | tenthous +----------+---------- + 998 | 998 + 998 | 1998 + 998 | 2998 + 998 | 3998 + 998 | 4998 + 998 | 5998 + 998 | 6998 + 998 | 7998 + 998 | 8998 + 998 | 9998 + 997 | 5997 + 997 | 6997 + 997 | 7997 + 997 | 8997 + 997 | 9997 + 999 | 9999 + 999 | 8999 + 999 | 7999 + 999 | 6999 + 999 | 5999 + 999 | 4999 + 999 | 3999 + 999 | 2999 + 999 | 1999 + 999 | 999 +(25 rows) + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 0; + thousand | tenthous +----------+---------- + 997 | 9997 + 997 | 8997 + 997 | 7997 + 997 | 6997 + 997 | 5997 + 998 | 9998 + 998 | 8998 + 998 | 7998 + 998 | 6998 + 998 | 5998 + 998 | 4998 + 998 | 3998 + 998 | 2998 + 998 | 1998 + 998 | 998 + 999 | 9999 + 999 | 8999 + 999 | 7999 + 999 | 6999 + 999 | 5999 + 999 | 4999 + 999 | 3999 + 999 | 2999 + 999 | 1999 + 999 | 999 +(25 rows) + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000 +ORDER BY thousand <-> 10000; + thousand | tenthous +----------+---------- + 999 | 999 + 999 | 1999 + 999 | 2999 + 999 | 3999 + 999 | 4999 + 999 | 5999 + 999 | 6999 + 999 | 7999 + 999 | 8999 + 999 | 9999 + 998 | 998 + 998 | 1998 + 998 | 2998 + 998 | 3998 + 998 | 4998 + 998 | 5998 + 998 | 6998 + 998 | 7998 + 998 | 8998 + 998 | 9998 + 997 | 5997 + 997 | 6997 + 997 | 7997 + 997 | 8997 + 997 | 9997 +(25 rows) + +SELECT thousand, tenthous FROM tenk3 +ORDER BY thousand <-> 500 +OFFSET 9970; + thousand | tenthous +----------+---------- + 1 | 1 + 1 | 1001 + 1 | 2001 + 1 | 3001 + 1 | 4001 + 1 | 5001 + 1 | 6001 + 1 | 7001 + 1 | 8001 + 1 | 9001 + 999 | 9999 + 999 | 8999 + 999 | 7999 + 999 | 6999 + 999 | 5999 + 999 | 4999 + 999 | 3999 + 999 | 2999 + 999 | 1999 + 999 | 999 + 0 | 0 + 0 | 1000 + 0 | 2000 + 0 | 3000 + 0 | 4000 + 0 | 5000 + 0 | 6000 + 0 | 7000 + 0 | 8000 + 0 | 9000 + | 3 + | 2 + | 1 +(33 rows) + +DROP INDEX tenk3_idx; +DROP TABLE tenk3; +-- Test distance ordering on by-ref types +CREATE TABLE knn_btree_ts (ts timestamp); +INSERT INTO knn_btree_ts +SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour' +FROM tenk1; +CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts); +SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20; + ts | ?column? +--------------------------+------------------- + Wed May 03 00:00:00 2017 | @ 2 days + Wed May 03 01:00:00 2017 | @ 2 days 1 hour + Wed May 03 02:00:00 2017 | @ 2 days 2 hours + Wed May 03 03:00:00 2017 | @ 2 days 3 hours + Wed May 03 04:00:00 2017 | @ 2 days 4 hours + Wed May 03 05:00:00 2017 | @ 2 days 5 hours + Wed May 03 06:00:00 2017 | @ 2 days 6 hours + Wed May 03 07:00:00 2017 | @ 2 days 7 hours + Wed May 03 08:00:00 2017 | @ 2 days 8 hours + Wed May 03 09:00:00 2017 | @ 2 days 9 hours + Wed May 03 10:00:00 2017 | @ 2 days 10 hours + Wed May 03 11:00:00 2017 | @ 2 days 11 hours + Wed May 03 12:00:00 2017 | @ 2 days 12 hours + Wed May 03 13:00:00 2017 | @ 2 days 13 hours + Wed May 03 14:00:00 2017 | @ 2 days 14 hours + Wed May 03 15:00:00 2017 | @ 2 days 15 hours + Wed May 03 16:00:00 2017 | @ 2 days 16 hours + Wed May 03 17:00:00 2017 | @ 2 days 17 hours + Wed May 03 18:00:00 2017 | @ 2 days 18 hours + Wed May 03 19:00:00 2017 | @ 2 days 19 hours +(20 rows) + +SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20; + ts | ?column? +--------------------------+------------ + Mon Jan 01 00:00:00 2018 | @ 0 + Mon Jan 01 01:00:00 2018 | @ 1 hour + Sun Dec 31 23:00:00 2017 | @ 1 hour + Mon Jan 01 02:00:00 2018 | @ 2 hours + Sun Dec 31 22:00:00 2017 | @ 2 hours + Mon Jan 01 03:00:00 2018 | @ 3 hours + Sun Dec 31 21:00:00 2017 | @ 3 hours + Mon Jan 01 04:00:00 2018 | @ 4 hours + Sun Dec 31 20:00:00 2017 | @ 4 hours + Mon Jan 01 05:00:00 2018 | @ 5 hours + Sun Dec 31 19:00:00 2017 | @ 5 hours + Mon Jan 01 06:00:00 2018 | @ 6 hours + Sun Dec 31 18:00:00 2017 | @ 6 hours + Mon Jan 01 07:00:00 2018 | @ 7 hours + Sun Dec 31 17:00:00 2017 | @ 7 hours + Mon Jan 01 08:00:00 2018 | @ 8 hours + Sun Dec 31 16:00:00 2017 | @ 8 hours + Mon Jan 01 09:00:00 2018 | @ 9 hours + Sun Dec 31 15:00:00 2017 | @ 9 hours + Mon Jan 01 10:00:00 2018 | @ 10 hours +(20 rows) + +DROP TABLE knn_btree_ts; +RESET enable_bitmapscan; +-- Test backward kNN scan +SET enable_sort = OFF; +EXPLAIN (COSTS OFF) SELECT thousand, tenthous FROM tenk1 ORDER BY thousand <-> 510; + QUERY PLAN +----------------------------------------------------- + Index Only Scan using tenk1_thous_tenthous on tenk1 + Order By: (thousand <-> 510) +(2 rows) + +BEGIN work; +DECLARE knn SCROLL CURSOR FOR +SELECT thousand, tenthous FROM tenk1 ORDER BY thousand <-> 510; +FETCH LAST FROM knn; + thousand | tenthous +----------+---------- + 0 | 0 +(1 row) + +FETCH BACKWARD 15 FROM knn; + thousand | tenthous +----------+---------- + 0 | 1000 + 0 | 2000 + 0 | 3000 + 0 | 4000 + 0 | 5000 + 0 | 6000 + 0 | 7000 + 0 | 8000 + 0 | 9000 + 1 | 1 + 1 | 1001 + 1 | 2001 + 1 | 3001 + 1 | 4001 + 1 | 5001 +(15 rows) + +FETCH RELATIVE -200 FROM knn; + thousand | tenthous +----------+---------- + 21 | 5021 +(1 row) + +FETCH BACKWARD 20 FROM knn; + thousand | tenthous +----------+---------- + 21 | 6021 + 21 | 7021 + 21 | 8021 + 21 | 9021 + 999 | 9999 + 999 | 8999 + 999 | 7999 + 999 | 6999 + 999 | 5999 + 999 | 4999 + 999 | 3999 + 999 | 2999 + 999 | 1999 + 999 | 999 + 22 | 22 + 22 | 1022 + 22 | 2022 + 22 | 3022 + 22 | 4022 + 22 | 5022 +(20 rows) + +FETCH FIRST FROM knn; + thousand | tenthous +----------+---------- + 510 | 510 +(1 row) + +FETCH LAST FROM knn; + thousand | tenthous +----------+---------- + 0 | 0 +(1 row) + +FETCH RELATIVE -215 FROM knn; + thousand | tenthous +----------+---------- + 21 | 5021 +(1 row) + +FETCH BACKWARD 20 FROM knn; + thousand | tenthous +----------+---------- + 21 | 6021 + 21 | 7021 + 21 | 8021 + 21 | 9021 + 999 | 9999 + 999 | 8999 + 999 | 7999 + 999 | 6999 + 999 | 5999 + 999 | 4999 + 999 | 3999 + 999 | 2999 + 999 | 1999 + 999 | 999 + 22 | 22 + 22 | 1022 + 22 | 2022 + 22 | 3022 + 22 | 4022 + 22 | 5022 +(20 rows) + +ROLLBACK work; +RESET enable_sort; diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out index f5949f3d174d..611c669137ae 100644 --- a/src/test/regress/expected/date.out +++ b/src/test/regress/expected/date.out @@ -1532,3 +1532,67 @@ select make_time(10, 55, 100.1); ERROR: time field value out of range: 10:55:100.1 select make_time(24, 0, 2.1); ERROR: time field value out of range: 24:00:2.1 +-- distance operators +SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL; + Fifteen | Distance +---------+---------- + | 16006 + | 15941 + | 1802 + | 1801 + | 1800 + | 1799 + | 1436 + | 1435 + | 1434 + | 308 + | 307 + | 306 + | 13578 + | 13944 + | 14311 + | 1475514 +(16 rows) + +SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL; + Fifteen | Distance +---------+--------------------------------------- + | @ 16006 days 1 hour 23 mins 45 secs + | @ 15941 days 1 hour 23 mins 45 secs + | @ 1802 days 1 hour 23 mins 45 secs + | @ 1801 days 1 hour 23 mins 45 secs + | @ 1800 days 1 hour 23 mins 45 secs + | @ 1799 days 1 hour 23 mins 45 secs + | @ 1436 days 1 hour 23 mins 45 secs + | @ 1435 days 1 hour 23 mins 45 secs + | @ 1434 days 1 hour 23 mins 45 secs + | @ 308 days 1 hour 23 mins 45 secs + | @ 307 days 1 hour 23 mins 45 secs + | @ 306 days 1 hour 23 mins 45 secs + | @ 13577 days 22 hours 36 mins 15 secs + | @ 13943 days 22 hours 36 mins 15 secs + | @ 14310 days 22 hours 36 mins 15 secs + | @ 1475514 days 1 hour 23 mins 45 secs +(16 rows) + +SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL; + Fifteen | Distance +---------+----------------------------------------- + | @ 16005 days 14 hours 23 mins 45 secs + | @ 15940 days 14 hours 23 mins 45 secs + | @ 1801 days 14 hours 23 mins 45 secs + | @ 1800 days 14 hours 23 mins 45 secs + | @ 1799 days 14 hours 23 mins 45 secs + | @ 1798 days 14 hours 23 mins 45 secs + | @ 1435 days 14 hours 23 mins 45 secs + | @ 1434 days 14 hours 23 mins 45 secs + | @ 1433 days 14 hours 23 mins 45 secs + | @ 307 days 14 hours 23 mins 45 secs + | @ 306 days 14 hours 23 mins 45 secs + | @ 305 days 15 hours 23 mins 45 secs + | @ 13578 days 8 hours 36 mins 15 secs + | @ 13944 days 8 hours 36 mins 15 secs + | @ 14311 days 8 hours 36 mins 15 secs + | @ 1475513 days 14 hours 23 mins 45 secs +(16 rows) + diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out index 65ee82caaeeb..a04ea173cae4 100644 --- a/src/test/regress/expected/float4.out +++ b/src/test/regress/expected/float4.out @@ -318,6 +318,26 @@ SELECT * FROM FLOAT4_TBL; -1.2345679e-20 (5 rows) +SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f; + five | f1 | dist +------+----------------+--------------- + | 0 | 1004.3 + | -34.84 | 1039.14 + | -1004.3 | 2008.6 + | -1.2345679e+20 | 1.2345679e+20 + | -1.2345679e-20 | 1004.3 +(5 rows) + +SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f; + five | f1 | dist +------+----------------+------------------------ + | 0 | 1004.3 + | -34.84 | 1039.1400001525878 + | -1004.3 | 2008.5999877929687 + | -1.2345679e+20 | 1.2345678955701443e+20 + | -1.2345679e-20 | 1004.3 +(5 rows) + -- test edge-case coercions to integer SELECT '32767.4'::float4::int2; int2 diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out index 344d6b7d6d76..7fcf3b7d6e68 100644 --- a/src/test/regress/expected/float8.out +++ b/src/test/regress/expected/float8.out @@ -617,6 +617,27 @@ SELECT f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f; 1.2345678901234e-200 | 2.3112042409018e-67 (5 rows) +-- distance +SELECT f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f; + f1 | dist +----------------------+---------------------- + 0 | 1004.3 + 1004.3 | 0 + -34.84 | 1039.14 + 1.2345678901234e+200 | 1.2345678901234e+200 + 1.2345678901234e-200 | 1004.3 +(5 rows) + +SELECT f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f; + f1 | dist +----------------------+---------------------- + 0 | 1004.29998779297 + 1004.3 | 1.22070312045253e-05 + -34.84 | 1039.13998779297 + 1.2345678901234e+200 | 1.2345678901234e+200 + 1.2345678901234e-200 | 1004.29998779297 +(5 rows) + SELECT * FROM FLOAT8_TBL; f1 ---------------------- diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out index 4e03a5faee0c..0631e028c64d 100644 --- a/src/test/regress/expected/int2.out +++ b/src/test/regress/expected/int2.out @@ -284,6 +284,39 @@ SELECT i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i; -32767 | -16383 (5 rows) +-- distance +SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i; +ERROR: smallint out of range +SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i +WHERE f1 > -32767; + four | f1 | x +------+-------+------- + | 0 | 2 + | 1234 | 1232 + | -1234 | 1236 + | 32767 | 32765 +(4 rows) + +SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i; + five | f1 | x +------+--------+------- + | 0 | 2 + | 1234 | 1232 + | -1234 | 1236 + | 32767 | 32765 + | -32767 | 32769 +(5 rows) + +SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i; + five | f1 | x +------+--------+------- + | 0 | 2 + | 1234 | 1232 + | -1234 | 1236 + | 32767 | 32765 + | -32767 | 32769 +(5 rows) + -- corner cases SELECT (-1::int2<<15)::text; text diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out index b1a15888ef8f..261bc5b263ce 100644 --- a/src/test/regress/expected/int4.out +++ b/src/test/regress/expected/int4.out @@ -266,6 +266,38 @@ SELECT i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i; -2147483647 | -1073741823 (5 rows) +SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i; +ERROR: integer out of range +SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i +WHERE f1 > -2147483647; + four | f1 | x +------+------------+------------ + | 0 | 2 + | 123456 | 123454 + | -123456 | 123458 + | 2147483647 | 2147483645 +(4 rows) + +SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i +WHERE f1 > -2147483647; + four | f1 | x +------+------------+------------ + | 0 | 2 + | 123456 | 123454 + | -123456 | 123458 + | 2147483647 | 2147483645 +(4 rows) + +SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i; + five | f1 | x +------+-------------+------------ + | 0 | 2 + | 123456 | 123454 + | -123456 | 123458 + | 2147483647 | 2147483645 + | -2147483647 | 2147483649 +(5 rows) + -- -- more complex expressions -- diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out index fddc09f6305a..412ba3e4f4cd 100644 --- a/src/test/regress/expected/int8.out +++ b/src/test/regress/expected/int8.out @@ -452,6 +452,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A 4567890123457035 | -4567890123456543 | 1123700970370370094 | 0 (5 rows) +-- distance +SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i; + five | q2 | dist +------+-------------------+------------------ + | 456 | 333 + | 4567890123456789 | 4567890123456666 + | 123 | 0 + | 4567890123456789 | 4567890123456666 + | -4567890123456789 | 4567890123456912 +(5 rows) + +SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i; + five | q2 | dist +------+-------------------+------------------ + | 456 | 333 + | 4567890123456789 | 4567890123456666 + | 123 | 0 + | 4567890123456789 | 4567890123456666 + | -4567890123456789 | 4567890123456912 +(5 rows) + +SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i; + five | q2 | dist +------+-------------------+------------------ + | 456 | 333 + | 4567890123456789 | 4567890123456666 + | 123 | 0 + | 4567890123456789 | 4567890123456666 + | -4567890123456789 | 4567890123456912 +(5 rows) + SELECT q2, abs(q2) FROM INT8_TBL; q2 | abs -------------------+------------------ diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 51ae010c7bab..a055a5d42a0d 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -325,6 +325,23 @@ SELECT -('-9223372036854775807 us'::interval); -- ok SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail ERROR: interval out of range +SELECT f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL; + ?column? +---------------------------- + 2 days 02:59:00 + 2 days -02:00:00 + 8 days -03:00:00 + 34 years -2 days -03:00:00 + 3 mons -2 days -03:00:00 + 2 days 03:00:14 + 1 day 00:56:56 + 6 years -2 days -03:00:00 + 5 mons -2 days -03:00:00 + 5 mons -2 days +09:00:00 + infinity + infinity +(12 rows) + -- Test intervals that are large enough to overflow 64 bits in comparisons CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval); INSERT INTO INTERVAL_TBL_OF (f1) VALUES diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out index cc2ff4d96e80..b2cbc86aa0d4 100644 --- a/src/test/regress/expected/money.out +++ b/src/test/regress/expected/money.out @@ -125,6 +125,12 @@ SELECT m / 2::float4 FROM money_data; $61.50 (1 row) +SELECT m <-> '$123.45' FROM money_data; + ?column? +---------- + $0.45 +(1 row) + -- All true SELECT m = '$123.00' FROM money_data; ?column? diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out index b80cb47e0c1d..1d705042c2ad 100644 --- a/src/test/regress/expected/oid.out +++ b/src/test/regress/expected/oid.out @@ -181,4 +181,17 @@ SELECT o.* FROM OID_TBL o WHERE o.f1 > '1234'; 99999999 (3 rows) +SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL; + eight | f1 | ?column? +-------+------------+------------ + | 1234 | 1111 + | 1235 | 1112 + | 987 | 864 + | 4294966256 | 4294966133 + | 99999999 | 99999876 + | 5 | 118 + | 10 | 113 + | 15 | 108 +(8 rows) + DROP TABLE OID_TBL; diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 9d047b21b88e..4a5124179275 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1432,6 +1432,8 @@ WHERE o1.oprnegate = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND -- Btree comparison operators' functions should have the same volatility -- and leakproofness markings as the associated comparison support function. +-- Btree ordering operators' functions may be not leakproof, while the +-- associated comparison support function is leakproof. SELECT pp.oid::regprocedure as proc, pp.provolatile as vp, pp.proleakproof as lp, po.oid::regprocedure as opr, po.provolatile as vo, po.proleakproof as lo FROM pg_proc pp, pg_proc po, pg_operator o, pg_amproc ap, pg_amop ao @@ -1442,7 +1444,10 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND ao.amoprighttype = ap.amprocrighttype AND ap.amprocnum = 1 AND (pp.provolatile != po.provolatile OR - pp.proleakproof != po.proleakproof) + (pp.proleakproof != po.proleakproof AND + ao.amoppurpose = 's') OR + (pp.proleakproof < po.proleakproof AND + ao.amoppurpose = 'o')) ORDER BY 1; proc | vp | lp | opr | vo | lo ------+----+----+-----+----+---- @@ -1980,6 +1985,7 @@ ORDER BY 1, 2, 3; 403 | 5 | *> 403 | 5 | > 403 | 5 | ~>~ + 403 | 6 | <-> 405 | 1 | = 783 | 1 | << 783 | 1 | @@ @@ -2090,7 +2096,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(124 rows) +(125 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 3bbe4c5f974d..c7f7f6bd3a3c 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5083,30 +5083,34 @@ List of access methods (1 row) \dAo+ btree float_ops - List of operators of operator families - AM | Operator family | Operator | Strategy | Purpose | Sort opfamily --------+-----------------+---------------------------------------+----------+---------+--------------- - btree | float_ops | <(double precision,double precision) | 1 | search | - btree | float_ops | <=(double precision,double precision) | 2 | search | - btree | float_ops | =(double precision,double precision) | 3 | search | - btree | float_ops | >=(double precision,double precision) | 4 | search | - btree | float_ops | >(double precision,double precision) | 5 | search | - btree | float_ops | <(real,real) | 1 | search | - btree | float_ops | <=(real,real) | 2 | search | - btree | float_ops | =(real,real) | 3 | search | - btree | float_ops | >=(real,real) | 4 | search | - btree | float_ops | >(real,real) | 5 | search | - btree | float_ops | <(double precision,real) | 1 | search | - btree | float_ops | <=(double precision,real) | 2 | search | - btree | float_ops | =(double precision,real) | 3 | search | - btree | float_ops | >=(double precision,real) | 4 | search | - btree | float_ops | >(double precision,real) | 5 | search | - btree | float_ops | <(real,double precision) | 1 | search | - btree | float_ops | <=(real,double precision) | 2 | search | - btree | float_ops | =(real,double precision) | 3 | search | - btree | float_ops | >=(real,double precision) | 4 | search | - btree | float_ops | >(real,double precision) | 5 | search | -(20 rows) + List of operators of operator families + AM | Operator family | Operator | Strategy | Purpose | Sort opfamily +-------+-----------------+----------------------------------------+----------+----------+--------------- + btree | float_ops | <(double precision,double precision) | 1 | search | + btree | float_ops | <=(double precision,double precision) | 2 | search | + btree | float_ops | =(double precision,double precision) | 3 | search | + btree | float_ops | >=(double precision,double precision) | 4 | search | + btree | float_ops | >(double precision,double precision) | 5 | search | + btree | float_ops | <->(double precision,double precision) | 6 | ordering | float_ops + btree | float_ops | <(real,real) | 1 | search | + btree | float_ops | <=(real,real) | 2 | search | + btree | float_ops | =(real,real) | 3 | search | + btree | float_ops | >=(real,real) | 4 | search | + btree | float_ops | >(real,real) | 5 | search | + btree | float_ops | <->(real,real) | 6 | ordering | float_ops + btree | float_ops | <(double precision,real) | 1 | search | + btree | float_ops | <=(double precision,real) | 2 | search | + btree | float_ops | =(double precision,real) | 3 | search | + btree | float_ops | >=(double precision,real) | 4 | search | + btree | float_ops | >(double precision,real) | 5 | search | + btree | float_ops | <->(double precision,real) | 6 | ordering | float_ops + btree | float_ops | <(real,double precision) | 1 | search | + btree | float_ops | <=(real,double precision) | 2 | search | + btree | float_ops | =(real,double precision) | 3 | search | + btree | float_ops | >=(real,double precision) | 4 | search | + btree | float_ops | >(real,double precision) | 5 | search | + btree | float_ops | <->(real,double precision) | 6 | ordering | float_ops +(24 rows) \dAo * pg_catalog.jsonb_path_ops List of operators of operator families diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out index 4247fae9412b..38280d4449a2 100644 --- a/src/test/regress/expected/time.out +++ b/src/test/regress/expected/time.out @@ -229,3 +229,19 @@ SELECT date_part('epoch', TIME '2020-05-26 13:30:25.575401'); 48625.575401 (1 row) +-- distance +SELECT f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL; + Distance +------------------------------- + @ 1 hour 23 mins 45 secs + @ 23 mins 45 secs + @ 39 mins 15 secs + @ 10 hours 35 mins 15 secs + @ 10 hours 36 mins 15 secs + @ 10 hours 37 mins 15 secs + @ 22 hours 35 mins 15 secs + @ 22 hours 36 mins 14.99 secs + @ 14 hours 12 mins 54 secs + @ 14 hours 12 mins 54 secs +(10 rows) + diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index cf337da517ef..2c7cab01d520 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -2201,3 +2201,424 @@ select age(timestamp '-infinity', timestamp 'infinity'); select age(timestamp '-infinity', timestamp '-infinity'); ERROR: interval out of range +SELECT make_timestamp(2014,12,28,6,30,45.887); + make_timestamp +------------------------------ + Sun Dec 28 06:30:45.887 2014 +(1 row) + +-- distance operators +SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL; + Distance +--------------------------------------- + infinity + infinity + @ 11356 days + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 58 secs + @ 1453 days 6 hours 27 mins 58.6 secs + @ 1453 days 6 hours 27 mins 58.5 secs + @ 1453 days 6 hours 27 mins 58.4 secs + @ 1493 days + @ 1492 days 20 hours 55 mins 55 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1333 days 6 hours 27 mins 59 secs + @ 231 days 18 hours 19 mins 20 secs + @ 324 days 15 hours 45 mins 59 secs + @ 324 days 10 hours 45 mins 58 secs + @ 324 days 11 hours 45 mins 57 secs + @ 324 days 20 hours 45 mins 56 secs + @ 324 days 21 hours 45 mins 55 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 28 mins + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1333 days 5 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1452 days 6 hours 27 mins 59 secs + @ 1451 days 6 hours 27 mins 59 secs + @ 1450 days 6 hours 27 mins 59 secs + @ 1449 days 6 hours 27 mins 59 secs + @ 1448 days 6 hours 27 mins 59 secs + @ 1447 days 6 hours 27 mins 59 secs + @ 765901 days 6 hours 27 mins 59 secs + @ 695407 days 6 hours 27 mins 59 secs + @ 512786 days 6 hours 27 mins 59 secs + @ 330165 days 6 hours 27 mins 59 secs + @ 111019 days 6 hours 27 mins 59 secs + @ 74495 days 6 hours 27 mins 59 secs + @ 37971 days 6 hours 27 mins 59 secs + @ 1447 days 6 hours 27 mins 59 secs + @ 35077 days 17 hours 32 mins 1 sec + @ 1801 days 6 hours 27 mins 59 secs + @ 1800 days 6 hours 27 mins 59 secs + @ 1799 days 6 hours 27 mins 59 secs + @ 1495 days 6 hours 27 mins 59 secs + @ 1494 days 6 hours 27 mins 59 secs + @ 1493 days 6 hours 27 mins 59 secs + @ 1435 days 6 hours 27 mins 59 secs + @ 1434 days 6 hours 27 mins 59 secs + @ 1130 days 6 hours 27 mins 59 secs + @ 1129 days 6 hours 27 mins 59 secs + @ 399 days 6 hours 27 mins 59 secs + @ 398 days 6 hours 27 mins 59 secs + @ 33 days 6 hours 27 mins 59 secs + @ 32 days 6 hours 27 mins 59 secs +(65 rows) + +SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1); + Distance +--------------------------------------- + @ 11356 days + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 58 secs + @ 1453 days 6 hours 27 mins 58.6 secs + @ 1453 days 6 hours 27 mins 58.5 secs + @ 1453 days 6 hours 27 mins 58.4 secs + @ 1493 days + @ 1492 days 20 hours 55 mins 55 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1333 days 6 hours 27 mins 59 secs + @ 231 days 18 hours 19 mins 20 secs + @ 324 days 15 hours 45 mins 59 secs + @ 324 days 10 hours 45 mins 58 secs + @ 324 days 11 hours 45 mins 57 secs + @ 324 days 20 hours 45 mins 56 secs + @ 324 days 21 hours 45 mins 55 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 28 mins + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1333 days 5 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1452 days 6 hours 27 mins 59 secs + @ 1451 days 6 hours 27 mins 59 secs + @ 1450 days 6 hours 27 mins 59 secs + @ 1449 days 6 hours 27 mins 59 secs + @ 1448 days 6 hours 27 mins 59 secs + @ 1447 days 6 hours 27 mins 59 secs + @ 765901 days 6 hours 27 mins 59 secs + @ 695407 days 6 hours 27 mins 59 secs + @ 512786 days 6 hours 27 mins 59 secs + @ 330165 days 6 hours 27 mins 59 secs + @ 111019 days 6 hours 27 mins 59 secs + @ 74495 days 6 hours 27 mins 59 secs + @ 37971 days 6 hours 27 mins 59 secs + @ 1447 days 6 hours 27 mins 59 secs + @ 35077 days 17 hours 32 mins 1 sec + @ 1801 days 6 hours 27 mins 59 secs + @ 1800 days 6 hours 27 mins 59 secs + @ 1799 days 6 hours 27 mins 59 secs + @ 1495 days 6 hours 27 mins 59 secs + @ 1494 days 6 hours 27 mins 59 secs + @ 1493 days 6 hours 27 mins 59 secs + @ 1435 days 6 hours 27 mins 59 secs + @ 1434 days 6 hours 27 mins 59 secs + @ 1130 days 6 hours 27 mins 59 secs + @ 1129 days 6 hours 27 mins 59 secs + @ 399 days 6 hours 27 mins 59 secs + @ 398 days 6 hours 27 mins 59 secs + @ 33 days 6 hours 27 mins 59 secs + @ 32 days 6 hours 27 mins 59 secs +(63 rows) + +SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL; + Distance +--------------------------------------- + infinity + infinity + @ 11356 days 1 hour 23 mins 45 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 43 secs + @ 1453 days 7 hours 51 mins 43.6 secs + @ 1453 days 7 hours 51 mins 43.5 secs + @ 1453 days 7 hours 51 mins 43.4 secs + @ 1493 days 1 hour 23 mins 45 secs + @ 1492 days 22 hours 19 mins 40 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1333 days 7 hours 51 mins 44 secs + @ 231 days 16 hours 55 mins 35 secs + @ 324 days 17 hours 9 mins 44 secs + @ 324 days 12 hours 9 mins 43 secs + @ 324 days 13 hours 9 mins 42 secs + @ 324 days 22 hours 9 mins 41 secs + @ 324 days 23 hours 9 mins 40 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 45 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1333 days 6 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1452 days 7 hours 51 mins 44 secs + @ 1451 days 7 hours 51 mins 44 secs + @ 1450 days 7 hours 51 mins 44 secs + @ 1449 days 7 hours 51 mins 44 secs + @ 1448 days 7 hours 51 mins 44 secs + @ 1447 days 7 hours 51 mins 44 secs + @ 765901 days 7 hours 51 mins 44 secs + @ 695407 days 7 hours 51 mins 44 secs + @ 512786 days 7 hours 51 mins 44 secs + @ 330165 days 7 hours 51 mins 44 secs + @ 111019 days 7 hours 51 mins 44 secs + @ 74495 days 7 hours 51 mins 44 secs + @ 37971 days 7 hours 51 mins 44 secs + @ 1447 days 7 hours 51 mins 44 secs + @ 35077 days 16 hours 8 mins 16 secs + @ 1801 days 7 hours 51 mins 44 secs + @ 1800 days 7 hours 51 mins 44 secs + @ 1799 days 7 hours 51 mins 44 secs + @ 1495 days 7 hours 51 mins 44 secs + @ 1494 days 7 hours 51 mins 44 secs + @ 1493 days 7 hours 51 mins 44 secs + @ 1435 days 7 hours 51 mins 44 secs + @ 1434 days 7 hours 51 mins 44 secs + @ 1130 days 7 hours 51 mins 44 secs + @ 1129 days 7 hours 51 mins 44 secs + @ 399 days 7 hours 51 mins 44 secs + @ 398 days 7 hours 51 mins 44 secs + @ 33 days 7 hours 51 mins 44 secs + @ 32 days 7 hours 51 mins 44 secs +(65 rows) + +SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1); + Distance +--------------------------------------- + @ 11356 days 1 hour 23 mins 45 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 43 secs + @ 1453 days 7 hours 51 mins 43.6 secs + @ 1453 days 7 hours 51 mins 43.5 secs + @ 1453 days 7 hours 51 mins 43.4 secs + @ 1493 days 1 hour 23 mins 45 secs + @ 1492 days 22 hours 19 mins 40 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1333 days 7 hours 51 mins 44 secs + @ 231 days 16 hours 55 mins 35 secs + @ 324 days 17 hours 9 mins 44 secs + @ 324 days 12 hours 9 mins 43 secs + @ 324 days 13 hours 9 mins 42 secs + @ 324 days 22 hours 9 mins 41 secs + @ 324 days 23 hours 9 mins 40 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 45 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1333 days 6 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1452 days 7 hours 51 mins 44 secs + @ 1451 days 7 hours 51 mins 44 secs + @ 1450 days 7 hours 51 mins 44 secs + @ 1449 days 7 hours 51 mins 44 secs + @ 1448 days 7 hours 51 mins 44 secs + @ 1447 days 7 hours 51 mins 44 secs + @ 765901 days 7 hours 51 mins 44 secs + @ 695407 days 7 hours 51 mins 44 secs + @ 512786 days 7 hours 51 mins 44 secs + @ 330165 days 7 hours 51 mins 44 secs + @ 111019 days 7 hours 51 mins 44 secs + @ 74495 days 7 hours 51 mins 44 secs + @ 37971 days 7 hours 51 mins 44 secs + @ 1447 days 7 hours 51 mins 44 secs + @ 35077 days 16 hours 8 mins 16 secs + @ 1801 days 7 hours 51 mins 44 secs + @ 1800 days 7 hours 51 mins 44 secs + @ 1799 days 7 hours 51 mins 44 secs + @ 1495 days 7 hours 51 mins 44 secs + @ 1494 days 7 hours 51 mins 44 secs + @ 1493 days 7 hours 51 mins 44 secs + @ 1435 days 7 hours 51 mins 44 secs + @ 1434 days 7 hours 51 mins 44 secs + @ 1130 days 7 hours 51 mins 44 secs + @ 1129 days 7 hours 51 mins 44 secs + @ 399 days 7 hours 51 mins 44 secs + @ 398 days 7 hours 51 mins 44 secs + @ 33 days 7 hours 51 mins 44 secs + @ 32 days 7 hours 51 mins 44 secs +(63 rows) + +SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL; + Distance +---------------------------------------- + infinity + infinity + @ 11355 days 14 hours 23 mins 45 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 43 secs + @ 1452 days 20 hours 51 mins 43.6 secs + @ 1452 days 20 hours 51 mins 43.5 secs + @ 1452 days 20 hours 51 mins 43.4 secs + @ 1492 days 14 hours 23 mins 45 secs + @ 1492 days 11 hours 19 mins 40 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1332 days 21 hours 51 mins 44 secs + @ 232 days 2 hours 55 mins 35 secs + @ 324 days 6 hours 9 mins 44 secs + @ 324 days 1 hour 9 mins 43 secs + @ 324 days 2 hours 9 mins 42 secs + @ 324 days 11 hours 9 mins 41 secs + @ 324 days 12 hours 9 mins 40 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 45 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1332 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1451 days 20 hours 51 mins 44 secs + @ 1450 days 20 hours 51 mins 44 secs + @ 1449 days 20 hours 51 mins 44 secs + @ 1448 days 20 hours 51 mins 44 secs + @ 1447 days 20 hours 51 mins 44 secs + @ 1446 days 20 hours 51 mins 44 secs + @ 765900 days 20 hours 51 mins 44 secs + @ 695406 days 20 hours 51 mins 44 secs + @ 512785 days 20 hours 51 mins 44 secs + @ 330164 days 20 hours 51 mins 44 secs + @ 111018 days 20 hours 51 mins 44 secs + @ 74494 days 20 hours 51 mins 44 secs + @ 37970 days 20 hours 51 mins 44 secs + @ 1446 days 20 hours 51 mins 44 secs + @ 35078 days 3 hours 8 mins 16 secs + @ 1800 days 20 hours 51 mins 44 secs + @ 1799 days 20 hours 51 mins 44 secs + @ 1798 days 20 hours 51 mins 44 secs + @ 1494 days 20 hours 51 mins 44 secs + @ 1493 days 20 hours 51 mins 44 secs + @ 1492 days 20 hours 51 mins 44 secs + @ 1434 days 20 hours 51 mins 44 secs + @ 1433 days 20 hours 51 mins 44 secs + @ 1129 days 20 hours 51 mins 44 secs + @ 1128 days 20 hours 51 mins 44 secs + @ 398 days 20 hours 51 mins 44 secs + @ 397 days 20 hours 51 mins 44 secs + @ 32 days 20 hours 51 mins 44 secs + @ 31 days 20 hours 51 mins 44 secs +(65 rows) + +SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1); + Distance +---------------------------------------- + @ 11355 days 14 hours 23 mins 45 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 43 secs + @ 1452 days 20 hours 51 mins 43.6 secs + @ 1452 days 20 hours 51 mins 43.5 secs + @ 1452 days 20 hours 51 mins 43.4 secs + @ 1492 days 14 hours 23 mins 45 secs + @ 1492 days 11 hours 19 mins 40 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1332 days 21 hours 51 mins 44 secs + @ 232 days 2 hours 55 mins 35 secs + @ 324 days 6 hours 9 mins 44 secs + @ 324 days 1 hour 9 mins 43 secs + @ 324 days 2 hours 9 mins 42 secs + @ 324 days 11 hours 9 mins 41 secs + @ 324 days 12 hours 9 mins 40 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 45 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1332 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1451 days 20 hours 51 mins 44 secs + @ 1450 days 20 hours 51 mins 44 secs + @ 1449 days 20 hours 51 mins 44 secs + @ 1448 days 20 hours 51 mins 44 secs + @ 1447 days 20 hours 51 mins 44 secs + @ 1446 days 20 hours 51 mins 44 secs + @ 765900 days 20 hours 51 mins 44 secs + @ 695406 days 20 hours 51 mins 44 secs + @ 512785 days 20 hours 51 mins 44 secs + @ 330164 days 20 hours 51 mins 44 secs + @ 111018 days 20 hours 51 mins 44 secs + @ 74494 days 20 hours 51 mins 44 secs + @ 37970 days 20 hours 51 mins 44 secs + @ 1446 days 20 hours 51 mins 44 secs + @ 35078 days 3 hours 8 mins 16 secs + @ 1800 days 20 hours 51 mins 44 secs + @ 1799 days 20 hours 51 mins 44 secs + @ 1798 days 20 hours 51 mins 44 secs + @ 1494 days 20 hours 51 mins 44 secs + @ 1493 days 20 hours 51 mins 44 secs + @ 1492 days 20 hours 51 mins 44 secs + @ 1434 days 20 hours 51 mins 44 secs + @ 1433 days 20 hours 51 mins 44 secs + @ 1129 days 20 hours 51 mins 44 secs + @ 1128 days 20 hours 51 mins 44 secs + @ 398 days 20 hours 51 mins 44 secs + @ 397 days 20 hours 51 mins 44 secs + @ 32 days 20 hours 51 mins 44 secs + @ 31 days 20 hours 51 mins 44 secs +(63 rows) + diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index bfb3825ff6cf..850686c7cb17 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -3286,3 +3286,424 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity'); SELECT age(timestamptz '-infinity', timestamptz '-infinity'); ERROR: interval out of range +-- distance operators +SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL; + Distance +--------------------------------------- + infinity + infinity + @ 11356 days 8 hours + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 58 secs + @ 1453 days 6 hours 27 mins 58.6 secs + @ 1453 days 6 hours 27 mins 58.5 secs + @ 1453 days 6 hours 27 mins 58.4 secs + @ 1493 days + @ 1492 days 20 hours 55 mins 55 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1333 days 7 hours 27 mins 59 secs + @ 231 days 17 hours 19 mins 20 secs + @ 324 days 15 hours 45 mins 59 secs + @ 324 days 19 hours 45 mins 58 secs + @ 324 days 21 hours 45 mins 57 secs + @ 324 days 20 hours 45 mins 56 secs + @ 324 days 22 hours 45 mins 55 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 28 mins + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 14 hours 27 mins 59 secs + @ 1453 days 14 hours 27 mins 59 secs + @ 1453 days 14 hours 27 mins 59 secs + @ 1453 days 9 hours 27 mins 59 secs + @ 1303 days 10 hours 27 mins 59 secs + @ 1333 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1452 days 6 hours 27 mins 59 secs + @ 1451 days 6 hours 27 mins 59 secs + @ 1450 days 6 hours 27 mins 59 secs + @ 1449 days 6 hours 27 mins 59 secs + @ 1448 days 6 hours 27 mins 59 secs + @ 1447 days 6 hours 27 mins 59 secs + @ 765901 days 6 hours 27 mins 59 secs + @ 695407 days 6 hours 27 mins 59 secs + @ 512786 days 6 hours 27 mins 59 secs + @ 330165 days 6 hours 27 mins 59 secs + @ 111019 days 6 hours 27 mins 59 secs + @ 74495 days 6 hours 27 mins 59 secs + @ 37971 days 6 hours 27 mins 59 secs + @ 1447 days 6 hours 27 mins 59 secs + @ 35077 days 17 hours 32 mins 1 sec + @ 1801 days 6 hours 27 mins 59 secs + @ 1800 days 6 hours 27 mins 59 secs + @ 1799 days 6 hours 27 mins 59 secs + @ 1495 days 6 hours 27 mins 59 secs + @ 1494 days 6 hours 27 mins 59 secs + @ 1493 days 6 hours 27 mins 59 secs + @ 1435 days 6 hours 27 mins 59 secs + @ 1434 days 6 hours 27 mins 59 secs + @ 1130 days 6 hours 27 mins 59 secs + @ 1129 days 6 hours 27 mins 59 secs + @ 399 days 6 hours 27 mins 59 secs + @ 398 days 6 hours 27 mins 59 secs + @ 33 days 6 hours 27 mins 59 secs + @ 32 days 6 hours 27 mins 59 secs +(66 rows) + +SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1); + Distance +--------------------------------------- + @ 11356 days 8 hours + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 58 secs + @ 1453 days 6 hours 27 mins 58.6 secs + @ 1453 days 6 hours 27 mins 58.5 secs + @ 1453 days 6 hours 27 mins 58.4 secs + @ 1493 days + @ 1492 days 20 hours 55 mins 55 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1333 days 7 hours 27 mins 59 secs + @ 231 days 17 hours 19 mins 20 secs + @ 324 days 15 hours 45 mins 59 secs + @ 324 days 19 hours 45 mins 58 secs + @ 324 days 21 hours 45 mins 57 secs + @ 324 days 20 hours 45 mins 56 secs + @ 324 days 22 hours 45 mins 55 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 28 mins + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1453 days 14 hours 27 mins 59 secs + @ 1453 days 14 hours 27 mins 59 secs + @ 1453 days 14 hours 27 mins 59 secs + @ 1453 days 9 hours 27 mins 59 secs + @ 1303 days 10 hours 27 mins 59 secs + @ 1333 days 6 hours 27 mins 59 secs + @ 1453 days 6 hours 27 mins 59 secs + @ 1452 days 6 hours 27 mins 59 secs + @ 1451 days 6 hours 27 mins 59 secs + @ 1450 days 6 hours 27 mins 59 secs + @ 1449 days 6 hours 27 mins 59 secs + @ 1448 days 6 hours 27 mins 59 secs + @ 1447 days 6 hours 27 mins 59 secs + @ 765901 days 6 hours 27 mins 59 secs + @ 695407 days 6 hours 27 mins 59 secs + @ 512786 days 6 hours 27 mins 59 secs + @ 330165 days 6 hours 27 mins 59 secs + @ 111019 days 6 hours 27 mins 59 secs + @ 74495 days 6 hours 27 mins 59 secs + @ 37971 days 6 hours 27 mins 59 secs + @ 1447 days 6 hours 27 mins 59 secs + @ 35077 days 17 hours 32 mins 1 sec + @ 1801 days 6 hours 27 mins 59 secs + @ 1800 days 6 hours 27 mins 59 secs + @ 1799 days 6 hours 27 mins 59 secs + @ 1495 days 6 hours 27 mins 59 secs + @ 1494 days 6 hours 27 mins 59 secs + @ 1493 days 6 hours 27 mins 59 secs + @ 1435 days 6 hours 27 mins 59 secs + @ 1434 days 6 hours 27 mins 59 secs + @ 1130 days 6 hours 27 mins 59 secs + @ 1129 days 6 hours 27 mins 59 secs + @ 399 days 6 hours 27 mins 59 secs + @ 398 days 6 hours 27 mins 59 secs + @ 33 days 6 hours 27 mins 59 secs + @ 32 days 6 hours 27 mins 59 secs +(64 rows) + +SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL; + Distance +--------------------------------------- + infinity + infinity + @ 11356 days 9 hours 23 mins 45 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 43 secs + @ 1453 days 7 hours 51 mins 43.6 secs + @ 1453 days 7 hours 51 mins 43.5 secs + @ 1453 days 7 hours 51 mins 43.4 secs + @ 1493 days 1 hour 23 mins 45 secs + @ 1492 days 22 hours 19 mins 40 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1333 days 8 hours 51 mins 44 secs + @ 231 days 15 hours 55 mins 35 secs + @ 324 days 17 hours 9 mins 44 secs + @ 324 days 21 hours 9 mins 43 secs + @ 324 days 23 hours 9 mins 42 secs + @ 324 days 22 hours 9 mins 41 secs + @ 325 days 9 mins 40 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 45 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 15 hours 51 mins 44 secs + @ 1453 days 15 hours 51 mins 44 secs + @ 1453 days 15 hours 51 mins 44 secs + @ 1453 days 10 hours 51 mins 44 secs + @ 1303 days 11 hours 51 mins 44 secs + @ 1333 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1452 days 7 hours 51 mins 44 secs + @ 1451 days 7 hours 51 mins 44 secs + @ 1450 days 7 hours 51 mins 44 secs + @ 1449 days 7 hours 51 mins 44 secs + @ 1448 days 7 hours 51 mins 44 secs + @ 1447 days 7 hours 51 mins 44 secs + @ 765901 days 7 hours 51 mins 44 secs + @ 695407 days 7 hours 51 mins 44 secs + @ 512786 days 7 hours 51 mins 44 secs + @ 330165 days 7 hours 51 mins 44 secs + @ 111019 days 7 hours 51 mins 44 secs + @ 74495 days 7 hours 51 mins 44 secs + @ 37971 days 7 hours 51 mins 44 secs + @ 1447 days 7 hours 51 mins 44 secs + @ 35077 days 16 hours 8 mins 16 secs + @ 1801 days 7 hours 51 mins 44 secs + @ 1800 days 7 hours 51 mins 44 secs + @ 1799 days 7 hours 51 mins 44 secs + @ 1495 days 7 hours 51 mins 44 secs + @ 1494 days 7 hours 51 mins 44 secs + @ 1493 days 7 hours 51 mins 44 secs + @ 1435 days 7 hours 51 mins 44 secs + @ 1434 days 7 hours 51 mins 44 secs + @ 1130 days 7 hours 51 mins 44 secs + @ 1129 days 7 hours 51 mins 44 secs + @ 399 days 7 hours 51 mins 44 secs + @ 398 days 7 hours 51 mins 44 secs + @ 33 days 7 hours 51 mins 44 secs + @ 32 days 7 hours 51 mins 44 secs +(66 rows) + +SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1); + Distance +--------------------------------------- + @ 11356 days 9 hours 23 mins 45 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 43 secs + @ 1453 days 7 hours 51 mins 43.6 secs + @ 1453 days 7 hours 51 mins 43.5 secs + @ 1453 days 7 hours 51 mins 43.4 secs + @ 1493 days 1 hour 23 mins 45 secs + @ 1492 days 22 hours 19 mins 40 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1333 days 8 hours 51 mins 44 secs + @ 231 days 15 hours 55 mins 35 secs + @ 324 days 17 hours 9 mins 44 secs + @ 324 days 21 hours 9 mins 43 secs + @ 324 days 23 hours 9 mins 42 secs + @ 324 days 22 hours 9 mins 41 secs + @ 325 days 9 mins 40 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 45 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1453 days 15 hours 51 mins 44 secs + @ 1453 days 15 hours 51 mins 44 secs + @ 1453 days 15 hours 51 mins 44 secs + @ 1453 days 10 hours 51 mins 44 secs + @ 1303 days 11 hours 51 mins 44 secs + @ 1333 days 7 hours 51 mins 44 secs + @ 1453 days 7 hours 51 mins 44 secs + @ 1452 days 7 hours 51 mins 44 secs + @ 1451 days 7 hours 51 mins 44 secs + @ 1450 days 7 hours 51 mins 44 secs + @ 1449 days 7 hours 51 mins 44 secs + @ 1448 days 7 hours 51 mins 44 secs + @ 1447 days 7 hours 51 mins 44 secs + @ 765901 days 7 hours 51 mins 44 secs + @ 695407 days 7 hours 51 mins 44 secs + @ 512786 days 7 hours 51 mins 44 secs + @ 330165 days 7 hours 51 mins 44 secs + @ 111019 days 7 hours 51 mins 44 secs + @ 74495 days 7 hours 51 mins 44 secs + @ 37971 days 7 hours 51 mins 44 secs + @ 1447 days 7 hours 51 mins 44 secs + @ 35077 days 16 hours 8 mins 16 secs + @ 1801 days 7 hours 51 mins 44 secs + @ 1800 days 7 hours 51 mins 44 secs + @ 1799 days 7 hours 51 mins 44 secs + @ 1495 days 7 hours 51 mins 44 secs + @ 1494 days 7 hours 51 mins 44 secs + @ 1493 days 7 hours 51 mins 44 secs + @ 1435 days 7 hours 51 mins 44 secs + @ 1434 days 7 hours 51 mins 44 secs + @ 1130 days 7 hours 51 mins 44 secs + @ 1129 days 7 hours 51 mins 44 secs + @ 399 days 7 hours 51 mins 44 secs + @ 398 days 7 hours 51 mins 44 secs + @ 33 days 7 hours 51 mins 44 secs + @ 32 days 7 hours 51 mins 44 secs +(64 rows) + +SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL; + Distance +---------------------------------------- + infinity + infinity + @ 11355 days 22 hours 23 mins 45 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 43 secs + @ 1452 days 20 hours 51 mins 43.6 secs + @ 1452 days 20 hours 51 mins 43.5 secs + @ 1452 days 20 hours 51 mins 43.4 secs + @ 1492 days 14 hours 23 mins 45 secs + @ 1492 days 11 hours 19 mins 40 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1332 days 21 hours 51 mins 44 secs + @ 232 days 2 hours 55 mins 35 secs + @ 324 days 6 hours 9 mins 44 secs + @ 324 days 10 hours 9 mins 43 secs + @ 324 days 12 hours 9 mins 42 secs + @ 324 days 11 hours 9 mins 41 secs + @ 324 days 13 hours 9 mins 40 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 45 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1453 days 4 hours 51 mins 44 secs + @ 1453 days 4 hours 51 mins 44 secs + @ 1453 days 4 hours 51 mins 44 secs + @ 1452 days 23 hours 51 mins 44 secs + @ 1303 days 51 mins 44 secs + @ 1332 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1451 days 20 hours 51 mins 44 secs + @ 1450 days 20 hours 51 mins 44 secs + @ 1449 days 20 hours 51 mins 44 secs + @ 1448 days 20 hours 51 mins 44 secs + @ 1447 days 20 hours 51 mins 44 secs + @ 1446 days 20 hours 51 mins 44 secs + @ 765900 days 20 hours 51 mins 44 secs + @ 695406 days 20 hours 51 mins 44 secs + @ 512785 days 20 hours 51 mins 44 secs + @ 330164 days 20 hours 51 mins 44 secs + @ 111018 days 20 hours 51 mins 44 secs + @ 74494 days 20 hours 51 mins 44 secs + @ 37970 days 20 hours 51 mins 44 secs + @ 1446 days 20 hours 51 mins 44 secs + @ 35078 days 3 hours 8 mins 16 secs + @ 1800 days 20 hours 51 mins 44 secs + @ 1799 days 20 hours 51 mins 44 secs + @ 1798 days 20 hours 51 mins 44 secs + @ 1494 days 20 hours 51 mins 44 secs + @ 1493 days 20 hours 51 mins 44 secs + @ 1492 days 20 hours 51 mins 44 secs + @ 1434 days 20 hours 51 mins 44 secs + @ 1433 days 20 hours 51 mins 44 secs + @ 1129 days 20 hours 51 mins 44 secs + @ 1128 days 20 hours 51 mins 44 secs + @ 398 days 20 hours 51 mins 44 secs + @ 397 days 20 hours 51 mins 44 secs + @ 32 days 20 hours 51 mins 44 secs + @ 31 days 20 hours 51 mins 44 secs +(66 rows) + +SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1); + Distance +---------------------------------------- + @ 11355 days 22 hours 23 mins 45 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 43 secs + @ 1452 days 20 hours 51 mins 43.6 secs + @ 1452 days 20 hours 51 mins 43.5 secs + @ 1452 days 20 hours 51 mins 43.4 secs + @ 1492 days 14 hours 23 mins 45 secs + @ 1492 days 11 hours 19 mins 40 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1332 days 21 hours 51 mins 44 secs + @ 232 days 2 hours 55 mins 35 secs + @ 324 days 6 hours 9 mins 44 secs + @ 324 days 10 hours 9 mins 43 secs + @ 324 days 12 hours 9 mins 42 secs + @ 324 days 11 hours 9 mins 41 secs + @ 324 days 13 hours 9 mins 40 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 45 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1453 days 4 hours 51 mins 44 secs + @ 1453 days 4 hours 51 mins 44 secs + @ 1453 days 4 hours 51 mins 44 secs + @ 1452 days 23 hours 51 mins 44 secs + @ 1303 days 51 mins 44 secs + @ 1332 days 20 hours 51 mins 44 secs + @ 1452 days 20 hours 51 mins 44 secs + @ 1451 days 20 hours 51 mins 44 secs + @ 1450 days 20 hours 51 mins 44 secs + @ 1449 days 20 hours 51 mins 44 secs + @ 1448 days 20 hours 51 mins 44 secs + @ 1447 days 20 hours 51 mins 44 secs + @ 1446 days 20 hours 51 mins 44 secs + @ 765900 days 20 hours 51 mins 44 secs + @ 695406 days 20 hours 51 mins 44 secs + @ 512785 days 20 hours 51 mins 44 secs + @ 330164 days 20 hours 51 mins 44 secs + @ 111018 days 20 hours 51 mins 44 secs + @ 74494 days 20 hours 51 mins 44 secs + @ 37970 days 20 hours 51 mins 44 secs + @ 1446 days 20 hours 51 mins 44 secs + @ 35078 days 3 hours 8 mins 16 secs + @ 1800 days 20 hours 51 mins 44 secs + @ 1799 days 20 hours 51 mins 44 secs + @ 1798 days 20 hours 51 mins 44 secs + @ 1494 days 20 hours 51 mins 44 secs + @ 1493 days 20 hours 51 mins 44 secs + @ 1492 days 20 hours 51 mins 44 secs + @ 1434 days 20 hours 51 mins 44 secs + @ 1433 days 20 hours 51 mins 44 secs + @ 1129 days 20 hours 51 mins 44 secs + @ 1128 days 20 hours 51 mins 44 secs + @ 398 days 20 hours 51 mins 44 secs + @ 397 days 20 hours 51 mins 44 secs + @ 32 days 20 hours 51 mins 44 secs + @ 31 days 20 hours 51 mins 44 secs +(64 rows) + diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql index de58d268d310..bd3710d631b7 100644 --- a/src/test/regress/sql/alter_generic.sql +++ b/src/test/regress/sql/alter_generic.sql @@ -306,8 +306,8 @@ ROLLBACK; -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP CREATE OPERATOR FAMILY alt_opf4 USING btree; ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD OPERATOR 1 < (int4, int2); -- invalid indexing_method -ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5 -ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5 +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6 +ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- invalid options parsing function ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5 @@ -351,10 +351,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist; ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops; DROP OPERATOR FAMILY alt_opf9 USING gist; --- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY +-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY +BEGIN TRANSACTION; CREATE OPERATOR FAMILY alt_opf10 USING btree; ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops; DROP OPERATOR FAMILY alt_opf10 USING btree; +ROLLBACK; -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY CREATE OPERATOR FAMILY alt_opf11 USING gist; diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql index 0d2a33f37053..988994922350 100644 --- a/src/test/regress/sql/btree_index.sql +++ b/src/test/regress/sql/btree_index.sql @@ -282,3 +282,315 @@ CREATE TABLE btree_part (id int4) PARTITION BY RANGE (id); CREATE INDEX btree_part_idx ON btree_part(id); ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100); DROP TABLE btree_part; + +--- +--- Test B-tree distance ordering +--- + +SET enable_bitmapscan = OFF; + +-- temporarily disable bt_i4_index index on bt_i4_heap(seqno) +UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass; + +CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno); + +-- test unsupported orderings (by non-first index attribute or by more than one order keys) +EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0; +EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0; +EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1; + +EXPLAIN (COSTS OFF) +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 4000000; + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 4000000; + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 10000000; + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 0; + +EXPLAIN (COSTS OFF) +SELECT * FROM bt_i4_heap +WHERE + random > 1000000 AND (random, seqno) < (6000000, 0) AND + random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL) +ORDER BY random <-> 3000000; + +SELECT * FROM bt_i4_heap +WHERE + random > 1000000 AND (random, seqno) < (6000000, 0) AND + random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL) +ORDER BY random <-> 3000000; + +DROP INDEX bt_i4_heap_random_idx; + +CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno); + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 4000000; + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 10000000; + +SELECT * FROM bt_i4_heap +WHERE random > 1000000 AND (random, seqno) < (6000000, 0) +ORDER BY random <-> 0; + +DROP INDEX bt_i4_heap_random_idx; + +-- test parallel KNN scan + +-- Serializable isolation would disable parallel query, so explicitly use an +-- arbitrary other level. +BEGIN ISOLATION LEVEL REPEATABLE READ; + +SET parallel_setup_cost = 0; +SET parallel_tuple_cost = 0; +SET min_parallel_table_scan_size = 0; +SET max_parallel_workers = 4; +SET max_parallel_workers_per_gather = 4; +SET cpu_operator_cost = 0; + +RESET enable_indexscan; + +\set bt_knn_row_count 100000 + +CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, :bt_knn_row_count) i; +CREATE INDEX bt_knn_test_idx ON bt_knn_test (i); +ALTER TABLE bt_knn_test SET (parallel_workers = 4); +ANALYZE bt_knn_test; + +-- set the point inside the range +\set bt_knn_point (4 * :bt_knn_row_count + 3) + +CREATE TABLE bt_knn_test2 AS + SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i + FROM generate_series(1, :bt_knn_row_count) i; + +SET enable_sort = OFF; + +EXPLAIN (COSTS OFF) +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + +RESET enable_sort; + +DROP TABLE bt_knn_test2; + +-- set the point to the right of the range +\set bt_knn_point (11 * :bt_knn_row_count) + +CREATE TABLE bt_knn_test2 AS + SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i + FROM generate_series(1, :bt_knn_row_count) i; + +SET enable_sort = OFF; + +EXPLAIN (COSTS OFF) +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + +RESET enable_sort; + +DROP TABLE bt_knn_test2; + +-- set the point to the left of the range +\set bt_knn_point (-:bt_knn_row_count) + +CREATE TABLE bt_knn_test2 AS + SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i + FROM generate_series(1, :bt_knn_row_count) i; + +SET enable_sort = OFF; + +EXPLAIN (COSTS OFF) +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + +WITH bt_knn_test1 AS ( + SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test +) +SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i; + +RESET enable_sort; + +DROP TABLE bt_knn_test; + +\set knn_row_count 30000 +CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, :knn_row_count) j; +CREATE INDEX bt_knn_test_idx ON bt_knn_test (i); +ALTER TABLE bt_knn_test SET (parallel_workers = 4); +ANALYZE bt_knn_test; + +SET enable_sort = OFF; + +EXPLAIN (COSTS OFF) +WITH +t1 AS ( + SELECT row_number() OVER () AS n, i + FROM bt_knn_test + WHERE i IN (3, 4, 7, 8, 2) + ORDER BY i <-> 4 +), +t2 AS ( + SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i + FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j +) +SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i; + +WITH +t1 AS ( + SELECT row_number() OVER () AS n, i + FROM bt_knn_test + WHERE i IN (3, 4, 7, 8, 2) + ORDER BY i <-> 4 +), +t2 AS ( + SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i + FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j +) +SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i; + +RESET enable_sort; + +RESET parallel_setup_cost; +RESET parallel_tuple_cost; +RESET min_parallel_table_scan_size; +RESET max_parallel_workers; +RESET max_parallel_workers_per_gather; +RESET cpu_operator_cost; + +ROLLBACK; + +-- enable bt_i4_index index on bt_i4_heap(seqno) +UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass; + + +CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1; + +INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3); + +-- Test distance ordering by ASC index +CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous); + +EXPLAIN (COSTS OFF) +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 998; + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 998; + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 0; + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000 +ORDER BY thousand <-> 10000; + +SELECT thousand, tenthous FROM tenk3 +ORDER BY thousand <-> 500 +OFFSET 9970; + +EXPLAIN (COSTS OFF) +SELECT * FROM tenk3 +WHERE thousand > 100 AND thousand < 800 AND + thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[]) +ORDER BY thousand <-> 300::int8; + +SELECT * FROM tenk3 +WHERE thousand > 100 AND thousand < 800 AND + thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[]) +ORDER BY thousand <-> 300::int8; + +DROP INDEX tenk3_idx; + +-- Test distance ordering by DESC index +CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous); + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 998; + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) +ORDER BY thousand <-> 0; + +SELECT thousand, tenthous FROM tenk3 +WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000 +ORDER BY thousand <-> 10000; + +SELECT thousand, tenthous FROM tenk3 +ORDER BY thousand <-> 500 +OFFSET 9970; + +DROP INDEX tenk3_idx; + +DROP TABLE tenk3; + +-- Test distance ordering on by-ref types +CREATE TABLE knn_btree_ts (ts timestamp); + +INSERT INTO knn_btree_ts +SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour' +FROM tenk1; + +CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts); + +SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20; +SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20; + +DROP TABLE knn_btree_ts; + +RESET enable_bitmapscan; + +-- Test backward kNN scan + +SET enable_sort = OFF; + +EXPLAIN (COSTS OFF) SELECT thousand, tenthous FROM tenk1 ORDER BY thousand <-> 510; + +BEGIN work; + +DECLARE knn SCROLL CURSOR FOR +SELECT thousand, tenthous FROM tenk1 ORDER BY thousand <-> 510; + +FETCH LAST FROM knn; +FETCH BACKWARD 15 FROM knn; +FETCH RELATIVE -200 FROM knn; +FETCH BACKWARD 20 FROM knn; +FETCH FIRST FROM knn; +FETCH LAST FROM knn; +FETCH RELATIVE -215 FROM knn; +FETCH BACKWARD 20 FROM knn; + +ROLLBACK work; + +RESET enable_sort; \ No newline at end of file diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql index 1c58ff6966db..43bdd65417e0 100644 --- a/src/test/regress/sql/date.sql +++ b/src/test/regress/sql/date.sql @@ -373,3 +373,8 @@ select make_date(2013, 13, 1); select make_date(2013, 11, -1); select make_time(10, 55, 100.1); select make_time(24, 0, 2.1); + +-- distance operators +SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL; +SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL; +SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL; diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql index 8fb12368c39b..594ac2fadf2b 100644 --- a/src/test/regress/sql/float4.sql +++ b/src/test/regress/sql/float4.sql @@ -100,6 +100,9 @@ UPDATE FLOAT4_TBL SELECT * FROM FLOAT4_TBL; +SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f; +SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f; + -- test edge-case coercions to integer SELECT '32767.4'::float4::int2; SELECT '32767.6'::float4::int2; diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql index 98e9926c9e05..e477534a59b0 100644 --- a/src/test/regress/sql/float8.sql +++ b/src/test/regress/sql/float8.sql @@ -176,6 +176,9 @@ SELECT ||/ float8 '27' AS three; SELECT f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f; +-- distance +SELECT f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f; +SELECT f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f; SELECT * FROM FLOAT8_TBL; diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql index df1e46d4e2e6..30678e0a13ba 100644 --- a/src/test/regress/sql/int2.sql +++ b/src/test/regress/sql/int2.sql @@ -87,6 +87,16 @@ SELECT i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i; SELECT i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i; +-- distance +SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i; + +SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i +WHERE f1 > -32767; + +SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i; + +SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i; + -- corner cases SELECT (-1::int2<<15)::text; SELECT ((-1::int2<<15)+1::int2)::text; diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql index e9d89e8111ff..888da6eedec1 100644 --- a/src/test/regress/sql/int4.sql +++ b/src/test/regress/sql/int4.sql @@ -87,6 +87,16 @@ SELECT i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i; SELECT i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i; +SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i; + +SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i +WHERE f1 > -2147483647; + +SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i +WHERE f1 > -2147483647; + +SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i; + -- -- more complex expressions -- diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql index fffb28906a1e..4e759a9838d3 100644 --- a/src/test/regress/sql/int8.sql +++ b/src/test/regress/sql/int8.sql @@ -90,6 +90,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS " -- int2 op int8 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL; +-- distance +SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i; +SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i; +SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i; + SELECT q2, abs(q2) FROM INT8_TBL; SELECT min(q1), min(q2) FROM INT8_TBL; SELECT max(q1), max(q2) FROM INT8_TBL; diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index fbf6e064d66b..6737a278b43e 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -81,6 +81,8 @@ SELECT -('-9223372036854775808 us'::interval); -- should fail SELECT -('-9223372036854775807 us'::interval); -- ok SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail +SELECT f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL; + -- Test intervals that are large enough to overflow 64 bits in comparisons CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval); INSERT INTO INTERVAL_TBL_OF (f1) VALUES diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql index b888ec21c30c..0fafd2e66ebe 100644 --- a/src/test/regress/sql/money.sql +++ b/src/test/regress/sql/money.sql @@ -27,6 +27,7 @@ SELECT m / 2::float8 FROM money_data; SELECT m * 2::float4 FROM money_data; SELECT 2::float4 * m FROM money_data; SELECT m / 2::float4 FROM money_data; +SELECT m <-> '$123.45' FROM money_data; -- All true SELECT m = '$123.00' FROM money_data; diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql index a96b2aa1e3d4..223c082c730c 100644 --- a/src/test/regress/sql/oid.sql +++ b/src/test/regress/sql/oid.sql @@ -54,4 +54,6 @@ SELECT o.* FROM OID_TBL o WHERE o.f1 >= '1234'; SELECT o.* FROM OID_TBL o WHERE o.f1 > '1234'; +SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL; + DROP TABLE OID_TBL; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 2fe7b6dcc498..d871c80866d8 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -814,6 +814,8 @@ WHERE o1.oprnegate = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND -- Btree comparison operators' functions should have the same volatility -- and leakproofness markings as the associated comparison support function. +-- Btree ordering operators' functions may be not leakproof, while the +-- associated comparison support function is leakproof. SELECT pp.oid::regprocedure as proc, pp.provolatile as vp, pp.proleakproof as lp, po.oid::regprocedure as opr, po.provolatile as vo, po.proleakproof as lo FROM pg_proc pp, pg_proc po, pg_operator o, pg_amproc ap, pg_amop ao @@ -824,7 +826,10 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND ao.amoprighttype = ap.amprocrighttype AND ap.amprocnum = 1 AND (pp.provolatile != po.provolatile OR - pp.proleakproof != po.proleakproof) + (pp.proleakproof != po.proleakproof AND + ao.amoppurpose = 's') OR + (pp.proleakproof < po.proleakproof AND + ao.amoppurpose = 'o')) ORDER BY 1; diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql index eb375a36e9ad..4c4015866916 100644 --- a/src/test/regress/sql/time.sql +++ b/src/test/regress/sql/time.sql @@ -77,3 +77,6 @@ SELECT date_part('microsecond', TIME '2020-05-26 13:30:25.575401'); SELECT date_part('millisecond', TIME '2020-05-26 13:30:25.575401'); SELECT date_part('second', TIME '2020-05-26 13:30:25.575401'); SELECT date_part('epoch', TIME '2020-05-26 13:30:25.575401'); + +-- distance +SELECT f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL; diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index 820ef7752ac7..d670c8895718 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -424,3 +424,12 @@ select age(timestamp 'infinity', timestamp 'infinity'); select age(timestamp 'infinity', timestamp '-infinity'); select age(timestamp '-infinity', timestamp 'infinity'); select age(timestamp '-infinity', timestamp '-infinity'); +SELECT make_timestamp(2014,12,28,6,30,45.887); + +-- distance operators +SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL; +SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1); +SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL; +SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1); +SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL; +SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1); diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index ccfd90d6467a..a0c8889e770d 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -668,3 +668,11 @@ SELECT age(timestamptz 'infinity', timestamptz 'infinity'); SELECT age(timestamptz 'infinity', timestamptz '-infinity'); SELECT age(timestamptz '-infinity', timestamptz 'infinity'); SELECT age(timestamptz '-infinity', timestamptz '-infinity'); + +-- distance operators +SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL; +SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1); +SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL; +SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1); +SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL; +SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);