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);