From fe28e829ddd4a175c713b85e89dace5cd2221d13 Mon Sep 17 00:00:00 2001 From: "Anton A. Melnikov" Date: Sun, 31 Dec 2023 14:10:23 +0300 Subject: [PATCH 1/5] Introduce IndexAmRoutine.ammorderbyopfirstcol Currently IndexAmRoutine.amcanorderbyop property means that index access method supports "column op const" ordering for every indexed column. Upcoming knn-btree supports it only for the first column. In future we will probably need to a callback to check whether particular ordering is supported. But now there are no potential use-cases around. So, don't overengineer it and leave with just boolean property. Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru Author: Nikita Glukhov Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov --- contrib/bloom/blutils.c | 1 + doc/src/sgml/indexam.sgml | 9 ++++++++- src/backend/access/brin/brin.c | 1 + src/backend/access/gin/ginutil.c | 1 + src/backend/access/gist/gist.c | 1 + src/backend/access/hash/hash.c | 1 + src/backend/access/nbtree/nbtree.c | 1 + src/backend/access/spgist/spgutils.c | 1 + src/backend/optimizer/path/indxpath.c | 22 +++++++++++++++------- src/include/access/amapi.h | 6 ++++++ src/include/nodes/pathnodes.h | 2 ++ 11 files changed, 38 insertions(+), 8 deletions(-) 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/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/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/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/nbtree.c b/src/backend/access/nbtree/nbtree.c index 686a3206f726..2d65adb22f71 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -107,6 +107,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amoptsprocnum = BTOPTIONS_PROC; amroutine->amcanorder = true; amroutine->amcanorderbyop = false; + amroutine->amorderbyopfirstcol = false; amroutine->amcanbackward = true; amroutine->amcanunique = true; amroutine->amcanmulticol = true; 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/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index c0fcc7d78dfc..c9685a861967 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -3030,6 +3030,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 +3062,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/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/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; From dec6278f7f7662ecb386553c588e0f5535182ff4 Mon Sep 17 00:00:00 2001 From: "Anton A. Melnikov" Date: Sun, 31 Dec 2023 14:34:57 +0300 Subject: [PATCH 2/5] Allow ordering by operator in ordered indexes Currently the only ordered index access method is btree, which doesn't support ordering by operator. So, optimizer has an assumption that ordered index access method can't support ordering by operator. Upcoming knn-btree is going to break this assumption. This commit prepares optimizer for that. Now we assume following. * Index scan ordered by operator doesn't support backward scan independently on amcanbackward. * If index native ordering matches query needs then we don't consider possible operator ordering. Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru Author: Nikita Glukhov Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov --- src/backend/executor/execAmi.c | 6 ++++++ src/backend/executor/nodeIndexscan.c | 6 ++---- src/backend/optimizer/path/indxpath.c | 19 ++++++++++--------- 3 files changed, 18 insertions(+), 13 deletions(-) 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 c9685a861967..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 From c7b0013b68021f577be239c141e64cad9a042bc3 Mon Sep 17 00:00:00 2001 From: "Anton A. Melnikov" Date: Sun, 31 Dec 2023 19:45:35 +0300 Subject: [PATCH 3/5] Extract BTScanState from BTScanOpaqueData Currently BTScanOpaqueData holds both information about scan keys and state of tree scan. That is OK as soon as we're going to scan btree just in single direction. Upcoming knn-btree patch provides btree scan in two directions simultaneously. This commit extracts data structure representing tree scan state in a single direction into separate BTScanState struct in preparation for knn-btree. Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru Author: Nikita Glukhov Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov --- src/backend/access/nbtree/nbtree.c | 181 ++++++----- src/backend/access/nbtree/nbtsearch.c | 424 ++++++++++++++------------ src/backend/access/nbtree/nbtutils.c | 51 ++-- src/include/access/nbtree.h | 42 ++- 4 files changed, 378 insertions(+), 320 deletions(-) diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 2d65adb22f71..fc3954cc1575 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -207,6 +207,7 @@ bool btgettuple(IndexScanDesc scan, ScanDirection dir) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanState state = &so->state; bool res; /* btree indexes are never lossy */ @@ -220,7 +221,7 @@ 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)) res = _bt_first(scan, dir); else { @@ -238,11 +239,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; } /* @@ -267,6 +268,7 @@ int64 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + BTScanPos currPos = &so->state.currPos; int64 ntids = 0; ItemPointer heapTid; @@ -287,7 +289,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)) @@ -295,7 +297,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++; } @@ -323,8 +325,8 @@ btbeginscan(Relation rel, int nkeys, int 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 @@ -336,15 +338,15 @@ 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; scan->xs_itupdesc = RelationGetDescr(rel); @@ -353,6 +355,45 @@ 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) +{ + /* 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); +} + /* * btrescan() -- rescan an index relation */ @@ -361,22 +402,12 @@ 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)) - { - /* 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, state, false); - 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 @@ -394,11 +425,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 @@ -419,19 +447,7 @@ btendscan(IndexScanDesc scan) { BTScanOpaque so = (BTScanOpaque) scan->opaque; - /* we aren't holding any read locks, but gotta drop the pins */ - if (BTScanPosIsValid(so->currPos)) - { - /* Before leaving current page, deal with any killed items */ - if (so->numKilled > 0) - _bt_killitems(scan); - BTScanPosUnpinIfPinned(so->currPos); - } - - so->markItemIndex = -1; - BTScanPosUnpinIfPinned(so->markPos); - - /* No need to invalidate positions, the RAM is about to be freed. */ + _bt_release_scan_state(scan, &so->state, true); /* Release storage */ if (so->keyData != NULL) @@ -439,24 +455,15 @@ btendscan(IndexScanDesc scan) /* 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(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 @@ -464,24 +471,32 @@ 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; } } /* - * btrestrpos() -- restore scan to last saved position + * btmarkpos() -- save current scan position */ void -btrestrpos(IndexScanDesc scan) +btmarkpos(IndexScanDesc scan) { BTScanOpaque so = (BTScanOpaque) scan->opaque; - if (so->markItemIndex >= 0) + _bt_mark_current_position(&so->state); +} + +static void +_bt_restore_marked_position(IndexScanDesc scan, BTScanState state) +{ + BTScanOpaque so = (BTScanOpaque) scan->opaque; + + if (state->markItemIndex >= 0) { /* * The scan has never moved to a new page since the last mark. Just @@ -490,7 +505,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 { @@ -500,34 +515,27 @@ 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); } } @@ -798,6 +806,17 @@ _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); +} + /* * 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..368857d3aa51 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -29,23 +29,26 @@ 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_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, 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); /* @@ -852,6 +855,58 @@ _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) +{ + if (!_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_first() -- Find the first item in a scan. * @@ -877,6 +932,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 +944,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); @@ -1415,19 +1470,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) * their scan. */ _bt_parallel_done(scan); - BTScanPosInvalidate(so->currPos); + 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 +1503,33 @@ _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 (!_bt_load_first_page(scan, &so->state, dir, offnum)) + return false; + +readcomplete: + /* OK, currPos->itemIndex says what to return */ + return _bt_return_current_item(scan, &so->state); +} + +/* + * Advance to next tuple on current page; or if there's no more, + * try to step to the next page with data. + */ +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 true; + return _bt_steppage(scan, state, dir); } /* @@ -1492,44 +1550,20 @@ 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 (!_bt_next_item(scan, &so->state, dir)) + return false; /* 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 true; + 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 +1587,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 +1605,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,7 +1616,7 @@ _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); } @@ -1609,30 +1644,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 @@ -1747,7 +1782,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 +1794,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 +1838,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 +1920,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 +1938,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 +1954,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 +1962,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 +2003,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 +2034,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 +2053,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 +2069,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 +2117,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; } } @@ -2102,27 +2140,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) 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) { @@ -2131,25 +2169,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir) * ended already, bail out. */ status = _bt_parallel_seize(scan, &blkno, false); - BTScanPosUnpinIfPinned(so->currPos); + BTScanPosUnpinIfPinned(*currPos); if (!status) { - BTScanPosInvalidate(so->currPos); + 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 +2203,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 +2222,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); + 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,7 +2240,7 @@ _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) @@ -2213,18 +2252,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) /* nope, keep going */ if (scan->parallel_scan != NULL) { - _bt_relbuf(rel, so->currPos.buf); + _bt_relbuf(rel, currPos->buf); status = _bt_parallel_seize(scan, &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 +2273,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 +2292,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_relbuf(rel, currPos->buf); _bt_parallel_done(scan); - BTScanPosInvalidate(so->currPos); + 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); + BTScanPosInvalidate(*currPos); return false; } @@ -2285,20 +2324,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, BufferGetBlockNumber(currPos->buf)); } /* @@ -2309,14 +2348,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) */ if (scan->parallel_scan != NULL) { - _bt_relbuf(rel, so->currPos.buf); + _bt_relbuf(rel, currPos->buf); status = _bt_parallel_seize(scan, &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); } } } @@ -2337,13 +2376,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir) Assert(!so->needPrimScan); - _bt_initialize_more_data(so, dir); + _bt_initialize_more_data(scan, &so->state, dir); - if (!_bt_readnextpage(scan, blkno, dir)) + if (!_bt_readnextpage(scan, &so->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, &so->state.currPos); return true; } @@ -2561,11 +2600,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 +2620,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir) * exists. */ PredicateLockRelation(rel, scan->xs_snapshot); - BTScanPosInvalidate(so->currPos); + BTScanPosInvalidate(*currPos); return false; } @@ -2610,36 +2649,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)) + return false; - return true; + /* OK, currPos->itemIndex says what to return */ + return _bt_return_current_item(scan, &so->state); } /* @@ -2647,27 +2665,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..a82b2638d827 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -4162,27 +4162,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 +4191,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 +4199,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 +4281,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 +4328,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); } @@ -5170,3 +5168,14 @@ _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; +} diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 749304334809..c60cecf722a3 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -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,27 @@ typedef struct BTScanOpaqueData /* keep these last in struct for efficiency */ BTScanPosData currPos; /* current position data */ BTScanPosData markPos; /* marked position, if any */ +} 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 tree scan */ + BTScanStateData state; } BTScanOpaqueData; typedef BTScanOpaqueData *BTScanOpaque; @@ -1291,7 +1300,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 +1321,7 @@ 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); /* * prototypes for functions in nbtvalidate.c From feafce13ab68058379048f8c258c35a469a2f909 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Mon, 25 Nov 2019 01:22:21 +0300 Subject: [PATCH 4/5] Move scalar distance operators from btree_gist to core Currently btree_gist is the only way to have knn search for scalar datatypes. This is why distance operators for those types are defined inside btree_gist as well. Upcoming knn-btree needs these distance operators to be defined in core. This commit moves them from btree_gist to core. Assuming that extension shared library should still work with non-upgraded extension catalog, we btree_gist still provides wrappers over core functions. Extension upgrade scripts switch opclasses to core operators and drops extension operators. Extension upgrade script has to refer @extschema@ to distinguish between operators with same name. Have to mark btree_gist as non-relocatable in order to do that. Catversion is bumped. btree_gist extension version is bumped. Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru Author: Nikita Glukhov Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov --- contrib/btree_gist/Makefile | 3 +- contrib/btree_gist/btree_cash.c | 16 +- contrib/btree_gist/btree_date.c | 7 +- contrib/btree_gist/btree_float4.c | 11 +- contrib/btree_gist/btree_float8.c | 11 +- contrib/btree_gist/btree_gist--1.7--1.8.sql | 90 +++++ contrib/btree_gist/btree_gist.control | 6 +- contrib/btree_gist/btree_int2.c | 16 +- contrib/btree_gist/btree_int4.c | 16 +- contrib/btree_gist/btree_int8.c | 16 +- contrib/btree_gist/btree_interval.c | 6 +- contrib/btree_gist/btree_oid.c | 11 +- contrib/btree_gist/btree_time.c | 6 +- contrib/btree_gist/btree_ts.c | 38 +- doc/src/sgml/btree-gist.sgml | 13 + src/backend/utils/adt/cash.c | 20 + src/backend/utils/adt/date.c | 112 ++++++ src/backend/utils/adt/float.c | 49 +++ src/backend/utils/adt/int.c | 51 +++ src/backend/utils/adt/int8.c | 44 ++ src/backend/utils/adt/oid.c | 21 + src/backend/utils/adt/timestamp.c | 103 +++++ src/include/catalog/pg_operator.dat | 108 +++++ src/include/catalog/pg_proc.dat | 86 ++++ src/include/utils/datetime.h | 2 + src/include/utils/timestamp.h | 4 +- src/test/regress/expected/date.out | 64 +++ src/test/regress/expected/float4.out | 20 + src/test/regress/expected/float8.out | 21 + src/test/regress/expected/int2.out | 33 ++ src/test/regress/expected/int4.out | 32 ++ src/test/regress/expected/int8.out | 31 ++ src/test/regress/expected/interval.out | 17 + src/test/regress/expected/money.out | 6 + src/test/regress/expected/oid.out | 13 + src/test/regress/expected/time.out | 16 + src/test/regress/expected/timestamp.out | 421 ++++++++++++++++++++ src/test/regress/expected/timestamptz.out | 421 ++++++++++++++++++++ src/test/regress/sql/date.sql | 5 + src/test/regress/sql/float4.sql | 3 + src/test/regress/sql/float8.sql | 3 + src/test/regress/sql/int2.sql | 10 + src/test/regress/sql/int4.sql | 10 + src/test/regress/sql/int8.sql | 5 + src/test/regress/sql/interval.sql | 2 + src/test/regress/sql/money.sql | 1 + src/test/regress/sql/oid.sql | 2 + src/test/regress/sql/time.sql | 3 + src/test/regress/sql/timestamp.sql | 9 + src/test/regress/sql/timestamptz.sql | 8 + 50 files changed, 1882 insertions(+), 140 deletions(-) create mode 100644 contrib/btree_gist/btree_gist--1.7--1.8.sql 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/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/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/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/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/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/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/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); From 80d67e4792c3728f4e036c40dcbf293b7e9eeb39 Mon Sep 17 00:00:00 2001 From: "Anton A. Melnikov" Date: Mon, 1 Jan 2024 05:02:10 +0300 Subject: [PATCH 5/5] Add knn support to btree indexes This commit implements support for knn scans in btree indexes. When knn search is requested, btree index is traversed ascending and descending simultaneously. At each step the closest tuple is returned. Filtering operators can reduce knn to regular ordered scan. Ordering operators are added to opfamilies of scalar datatypes. No extra supporting functions are required: knn-btree algorithm works using comparison function and ordering operator itself. Distance operators are not leakproof, because they throw error on overflow. Therefore we relax opr_sanity check for btree ordering operators. It's OK for them to be leaky while comparison function is leakproof. Catversion is bumped. Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru Author: Nikita Glukhov Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov --- doc/src/sgml/btree.sgml | 47 + doc/src/sgml/indices.sgml | 11 + doc/src/sgml/xindex.sgml | 7 +- src/backend/access/brin/brin_minmax.c | 6 +- src/backend/access/nbtree/README | 22 + src/backend/access/nbtree/nbtree.c | 214 ++++- src/backend/access/nbtree/nbtsearch.c | 361 +++++++- src/backend/access/nbtree/nbtutils.c | 475 +++++++++- src/backend/access/nbtree/nbtvalidate.c | 45 +- src/backend/partitioning/partprune.c | 4 +- src/include/access/nbtree.h | 32 +- src/include/access/stratnum.h | 7 +- src/include/catalog/pg_amop.dat | 104 +++ src/test/regress/expected/alter_generic.out | 13 +- src/test/regress/expected/amutils.out | 6 +- src/test/regress/expected/btree_index.out | 954 ++++++++++++++++++++ src/test/regress/expected/opr_sanity.out | 10 +- src/test/regress/expected/psql.out | 52 +- src/test/regress/sql/alter_generic.sql | 8 +- src/test/regress/sql/btree_index.sql | 312 +++++++ src/test/regress/sql/opr_sanity.sql | 7 +- 21 files changed, 2526 insertions(+), 171 deletions(-) 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/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_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/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 fc3954cc1575..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,8 +109,8 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amsupport = BTNProcs; amroutine->amoptsprocnum = BTOPTIONS_PROC; amroutine->amcanorder = true; - amroutine->amcanorderbyop = false; - amroutine->amorderbyopfirstcol = false; + amroutine->amcanorderbyop = true; + amroutine->amorderbyopfirstcol = true; amroutine->amcanbackward = true; amroutine->amcanunique = true; amroutine->amcanmulticol = true; @@ -208,10 +211,18 @@ 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 @@ -221,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(state->currPos)) + if (!BTScanPosIsValid(state->currPos) && + (!so->backwardState || !BTScanPosIsValid(so->backwardState->currPos))) res = _bt_first(scan, dir); else { @@ -256,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; } @@ -317,9 +329,6 @@ 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); @@ -347,6 +356,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys) * scan->xs_itupdesc whether we'll need it or not, since that's so cheap. */ so->state.currTuples = so->state.markTuples = NULL; + so->backwardState = NULL; + so->distanceTypeByVal = true; + so->scanDirection = NoMovementScanDirection; scan->xs_itupdesc = RelationGetDescr(rel); @@ -376,6 +388,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation, 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); @@ -392,6 +406,18 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free) } 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; } /* @@ -406,6 +432,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, _bt_release_scan_state(scan, state, false); + if (so->backwardState) + { + _bt_release_scan_state(scan, so->backwardState, true); + pfree(so->backwardState); + so->backwardState = NULL; + } + so->needPrimScan = false; so->scanBehind = false; @@ -437,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; } /* @@ -449,6 +490,12 @@ btendscan(IndexScanDesc scan) _bt_release_scan_state(scan, &so->state, true); + if (so->backwardState) + { + _bt_release_scan_state(scan, so->backwardState, true); + pfree(so->backwardState); + } + /* Release storage */ if (so->keyData != NULL) pfree(so->keyData); @@ -460,7 +507,7 @@ btendscan(IndexScanDesc scan) } static void -_bt_mark_current_position(BTScanState state) +_bt_mark_current_position(BTScanOpaque so, BTScanState state) { /* There may be an old mark with a pin (but no lock). */ BTScanPosUnpinIfPinned(state->markPos); @@ -478,6 +525,25 @@ _bt_mark_current_position(BTScanState state) 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); + } + } } /* @@ -488,7 +554,13 @@ btmarkpos(IndexScanDesc scan) { BTScanOpaque so = (BTScanOpaque) scan->opaque; - _bt_mark_current_position(&so->state); + _bt_mark_current_position(so, &so->state); + + if (so->backwardState) + { + _bt_mark_current_position(so, so->backwardState); + so->markRightIsNearest = so->currRightIsNearest; + } } static void @@ -537,6 +609,22 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state) } } } + + /* + * 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); + } } /* @@ -558,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); } @@ -583,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); } @@ -611,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; @@ -649,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); @@ -690,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); @@ -715,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); } /* @@ -738,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) @@ -751,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 */ @@ -780,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 */ @@ -815,6 +977,12 @@ 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; + } } /* diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c index 368857d3aa51..b19802789bc9 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -44,11 +44,13 @@ 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, 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(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); /* @@ -890,9 +892,11 @@ _bt_return_current_item(IndexScanDesc scan, BTScanState state) */ static bool _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir, - OffsetNumber offnum) + OffsetNumber offnum, bool *readPageStatus) { - if (!_bt_readpage(scan, state, dir, offnum, true)) + if (!(readPageStatus ? + *readPageStatus : + _bt_readpage(scan, state, dir, offnum, true))) { /* * There's no actually-matching data on this page. Try to advance to @@ -907,6 +911,173 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir, 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. * @@ -962,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 @@ -978,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 @@ -989,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) @@ -1070,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 @@ -1211,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; @@ -1247,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)); @@ -1412,6 +1629,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) break; case BTGreaterEqualStrategyNumber: + case BTMaxStrategyNumber: /* * Find first item >= scankey @@ -1469,7 +1687,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) * Mark parallel scan as done, so that all the workers can finish * their scan. */ - _bt_parallel_done(scan); + _bt_parallel_done(scan, &so->state); BTScanPosInvalidate(*currPos); return false; } @@ -1503,17 +1721,22 @@ _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_load_first_page(scan, &so->state, dir, offnum)) - return false; + 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 */ -readcomplete: /* OK, currPos->itemIndex says what to return */ return _bt_return_current_item(scan, &so->state); } /* - * Advance to next tuple on current page; or if there's no more, - * try to step to the next page with data. + * _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) @@ -1532,6 +1755,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir) return _bt_steppage(scan, state, dir); } +/* + * _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); +} + /* * _bt_next() -- Get the next item in a scan. * @@ -1551,6 +1819,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir) { BTScanOpaque so = (BTScanOpaque) scan->opaque; + if (so->backwardState) + /* return next neareset item from KNN scan */ + return _bt_next_nearest(scan); + if (!_bt_next_item(scan, &so->state, dir)) return false; @@ -1618,7 +1890,7 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir, OffsetNum else 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); @@ -1709,7 +1981,7 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir, OffsetNum * 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; @@ -2136,7 +2408,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, 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 */ @@ -2168,13 +2440,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, 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); + status = _bt_parallel_seize(scan, state, &blkno, false); BTScanPosUnpinIfPinned(*currPos); if (!status) { BTScanPosInvalidate(*currPos); return false; } + if (blkno == P_NONE) + { + _bt_parallel_done(scan, state); + BTScanPosInvalidate(*currPos); + return false; + } } else { @@ -2224,7 +2502,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, */ if (blkno == P_NONE || !currPos->moreRight) { - _bt_parallel_done(scan); + _bt_parallel_done(scan, state); BTScanPosInvalidate(*currPos); return false; } @@ -2246,14 +2524,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, 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, currPos->buf); - status = _bt_parallel_seize(scan, &blkno, false); + status = _bt_parallel_seize(scan, state, &blkno, false); if (!status) { BTScanPosInvalidate(*currPos); @@ -2303,7 +2581,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, if (!currPos->moreLeft) { _bt_relbuf(rel, currPos->buf); - _bt_parallel_done(scan); + _bt_parallel_done(scan, state); BTScanPosInvalidate(*currPos); return false; } @@ -2314,7 +2592,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, /* if we're physically at end of index, return failure */ if (currPos->buf == InvalidBuffer) { - _bt_parallel_done(scan); + _bt_parallel_done(scan, state); BTScanPosInvalidate(*currPos); return false; } @@ -2337,7 +2615,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, else if (scan->parallel_scan != NULL) { /* allow next page be processed by parallel worker */ - _bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf)); + _bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf)); } /* @@ -2349,7 +2627,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, if (scan->parallel_scan != NULL) { _bt_relbuf(rel, currPos->buf); - status = _bt_parallel_seize(scan, &blkno, false); + status = _bt_parallel_seize(scan, state, &blkno, false); if (!status) { BTScanPosInvalidate(*currPos); @@ -2370,19 +2648,20 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno, * 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(scan, &so->state, dir); + _bt_initialize_more_data(scan, state, dir); - if (!_bt_readnextpage(scan, &so->state, 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->state.currPos); + /* Drop the lock, and maybe the pin, on the current page */ + _bt_drop_lock_and_maybe_pin(scan, &state->currPos); return true; } @@ -2653,7 +2932,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir) _bt_initialize_more_data(scan, &so->state, dir); - if (!_bt_load_first_page(scan, &so->state, dir, start)) + if (!_bt_load_first_page(scan, &so->state, dir, start, NULL)) return false; /* OK, currPos->itemIndex says what to return */ diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index a82b2638d827..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 @@ -4583,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 */ } @@ -5179,3 +5361,216 @@ _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/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/include/access/nbtree.h b/src/include/access/nbtree.h index c60cecf722a3..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 @@ -1064,6 +1064,12 @@ typedef struct BTScanStateData /* 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; @@ -1083,8 +1089,20 @@ typedef struct BTScanOpaqueData FmgrInfo *orderProcs; /* ORDER procs for required equality keys */ MemoryContext arrayContext; /* scan-lifespan context for array data */ - /* the state of tree scan */ + /* 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; @@ -1199,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 */ @@ -1322,6 +1341,9 @@ 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/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/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/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/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;