Add support for nearest-neighbor (KNN) searches to SP-GiST

Currently, KNN searches were supported only by GiST.  SP-GiST also capable to
support them.  This commit implements that support.  SP-GiST scan stack is
replaced with queue, which serves as stack if no ordering is specified.  KNN
support is provided for three SP-GIST opclasses: quad_point_ops, kd_point_ops
and poly_ops (catversion is bumped).  Some common parts between GiST and SP-GiST
KNNs are extracted into separate functions.

Discussion: https://postgr.es/m/570825e8-47d0-4732-2bf6-88d67d2d51c8%40postgrespro.ru
Author: Nikita Glukhov, Alexander Korotkov based on GSoC work by Vlad Sterzhanov
Review: Andrey Borodin, Alexander Korotkov
This commit is contained in:
Alexander Korotkov 2018-09-19 01:54:10 +03:00
parent d0cfc3d6a4
commit 2a6368343f
29 changed files with 1681 additions and 428 deletions

View File

@ -281,6 +281,13 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
For more information see <xref linkend="spgist"/>. For more information see <xref linkend="spgist"/>.
</para> </para>
<para>
Like GiST, SP-GiST supports <quote>nearest-neighbor</quote> searches.
For SP-GiST operator classes that support distance ordering, the
corresponding operator is specified in the <quote>Ordering Operators</quote>
column in <xref linkend="spgist-builtin-opclasses-table"/>.
</para>
<para> <para>
<indexterm> <indexterm>
<primary>index</primary> <primary>index</primary>

View File

@ -64,12 +64,13 @@
<table id="spgist-builtin-opclasses-table"> <table id="spgist-builtin-opclasses-table">
<title>Built-in <acronym>SP-GiST</acronym> Operator Classes</title> <title>Built-in <acronym>SP-GiST</acronym> Operator Classes</title>
<tgroup cols="3"> <tgroup cols="4">
<thead> <thead>
<row> <row>
<entry>Name</entry> <entry>Name</entry>
<entry>Indexed Data Type</entry> <entry>Indexed Data Type</entry>
<entry>Indexable Operators</entry> <entry>Indexable Operators</entry>
<entry>Ordering Operators</entry>
</row> </row>
</thead> </thead>
<tbody> <tbody>
@ -84,6 +85,9 @@
<literal>&gt;^</literal> <literal>&gt;^</literal>
<literal>~=</literal> <literal>~=</literal>
</entry> </entry>
<entry>
<literal>&lt;-&gt;</literal>
</entry>
</row> </row>
<row> <row>
<entry><literal>quad_point_ops</literal></entry> <entry><literal>quad_point_ops</literal></entry>
@ -96,6 +100,9 @@
<literal>&gt;^</literal> <literal>&gt;^</literal>
<literal>~=</literal> <literal>~=</literal>
</entry> </entry>
<entry>
<literal>&lt;-&gt;</literal>
</entry>
</row> </row>
<row> <row>
<entry><literal>range_ops</literal></entry> <entry><literal>range_ops</literal></entry>
@ -111,6 +118,8 @@
<literal>&gt;&gt;</literal> <literal>&gt;&gt;</literal>
<literal>@&gt;</literal> <literal>@&gt;</literal>
</entry> </entry>
<entry>
</entry>
</row> </row>
<row> <row>
<entry><literal>box_ops</literal></entry> <entry><literal>box_ops</literal></entry>
@ -129,6 +138,8 @@
<literal>|&gt;&gt;</literal> <literal>|&gt;&gt;</literal>
<literal>|&amp;&gt;</literal> <literal>|&amp;&gt;</literal>
</entry> </entry>
<entry>
</entry>
</row> </row>
<row> <row>
<entry><literal>poly_ops</literal></entry> <entry><literal>poly_ops</literal></entry>
@ -147,6 +158,9 @@
<literal>|&gt;&gt;</literal> <literal>|&gt;&gt;</literal>
<literal>|&amp;&gt;</literal> <literal>|&amp;&gt;</literal>
</entry> </entry>
<entry>
<literal>&lt;-&gt;</literal>
</entry>
</row> </row>
<row> <row>
<entry><literal>text_ops</literal></entry> <entry><literal>text_ops</literal></entry>
@ -163,6 +177,8 @@
<literal>~&gt;~</literal> <literal>~&gt;~</literal>
<literal>^@</literal> <literal>^@</literal>
</entry> </entry>
<entry>
</entry>
</row> </row>
<row> <row>
<entry><literal>inet_ops</literal></entry> <entry><literal>inet_ops</literal></entry>
@ -180,6 +196,8 @@
<literal>&lt;=</literal> <literal>&lt;=</literal>
<literal>=</literal> <literal>=</literal>
</entry> </entry>
<entry>
</entry>
</row> </row>
</tbody> </tbody>
</tgroup> </tgroup>
@ -191,6 +209,12 @@
supports the same operators but uses a different index data structure which supports the same operators but uses a different index data structure which
may offer better performance in some applications. may offer better performance in some applications.
</para> </para>
<para>
The <literal>quad_point_ops</literal>, <literal>kd_point_ops</literal> and
<literal>poly_ops</literal> operator classes support the <literal>&lt;-&gt;</literal>
ordering operator, which enables the k-nearest neighbor (<literal>k-NN</literal>)
search over indexed point or polygon datasets.
</para>
</sect1> </sect1>
@ -630,7 +654,10 @@ CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ...
typedef struct spgInnerConsistentIn typedef struct spgInnerConsistentIn
{ {
ScanKey scankeys; /* array of operators and comparison values */ ScanKey scankeys; /* array of operators and comparison values */
int nkeys; /* length of array */ ScanKey orderbys; /* array of ordering operators and comparison
* values */
int nkeys; /* length of scankeys array */
int norderbys; /* length of orderbys array */
Datum reconstructedValue; /* value reconstructed at parent */ Datum reconstructedValue; /* value reconstructed at parent */
void *traversalValue; /* opclass-specific traverse value */ void *traversalValue; /* opclass-specific traverse value */
@ -653,6 +680,7 @@ typedef struct spgInnerConsistentOut
int *levelAdds; /* increment level by this much for each */ int *levelAdds; /* increment level by this much for each */
Datum *reconstructedValues; /* associated reconstructed values */ Datum *reconstructedValues; /* associated reconstructed values */
void **traversalValues; /* opclass-specific traverse values */ void **traversalValues; /* opclass-specific traverse values */
double **distances; /* associated distances */
} spgInnerConsistentOut; } spgInnerConsistentOut;
</programlisting> </programlisting>
@ -667,6 +695,8 @@ typedef struct spgInnerConsistentOut
In particular it is not necessary to check <structfield>sk_flags</structfield> to In particular it is not necessary to check <structfield>sk_flags</structfield> to
see if the comparison value is NULL, because the SP-GiST core code see if the comparison value is NULL, because the SP-GiST core code
will filter out such conditions. will filter out such conditions.
The array <structfield>orderbys</structfield>, of length <structfield>norderbys</structfield>,
describes ordering operators (if any) in the same manner.
<structfield>reconstructedValue</structfield> is the value reconstructed for the <structfield>reconstructedValue</structfield> is the value reconstructed for the
parent tuple; it is <literal>(Datum) 0</literal> at the root level or if the parent tuple; it is <literal>(Datum) 0</literal> at the root level or if the
<function>inner_consistent</function> function did not provide a value at the <function>inner_consistent</function> function did not provide a value at the
@ -709,6 +739,10 @@ typedef struct spgInnerConsistentOut
of <structname>spgConfigOut</structname>.<structfield>leafType</structfield> type of <structname>spgConfigOut</structname>.<structfield>leafType</structfield> type
reconstructed for each child node to be visited; otherwise, leave reconstructed for each child node to be visited; otherwise, leave
<structfield>reconstructedValues</structfield> as NULL. <structfield>reconstructedValues</structfield> as NULL.
If ordered search is performed, set <structfield>distances</structfield>
to an array of distance values according to <structfield>orderbys</structfield>
array (nodes with lowest distances will be processed first). Leave it
NULL otherwise.
If it is desired to pass down additional out-of-band information If it is desired to pass down additional out-of-band information
(<quote>traverse values</quote>) to lower levels of the tree search, (<quote>traverse values</quote>) to lower levels of the tree search,
set <structfield>traversalValues</structfield> to an array of the appropriate set <structfield>traversalValues</structfield> to an array of the appropriate
@ -717,6 +751,7 @@ typedef struct spgInnerConsistentOut
Note that the <function>inner_consistent</function> function is Note that the <function>inner_consistent</function> function is
responsible for palloc'ing the responsible for palloc'ing the
<structfield>nodeNumbers</structfield>, <structfield>levelAdds</structfield>, <structfield>nodeNumbers</structfield>, <structfield>levelAdds</structfield>,
<structfield>distances</structfield>,
<structfield>reconstructedValues</structfield>, and <structfield>reconstructedValues</structfield>, and
<structfield>traversalValues</structfield> arrays in the current memory context. <structfield>traversalValues</structfield> arrays in the current memory context.
However, any output traverse values pointed to by However, any output traverse values pointed to by
@ -747,7 +782,10 @@ CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ...
typedef struct spgLeafConsistentIn typedef struct spgLeafConsistentIn
{ {
ScanKey scankeys; /* array of operators and comparison values */ ScanKey scankeys; /* array of operators and comparison values */
int nkeys; /* length of array */ ScanKey orderbys; /* array of ordering operators and comparison
* values */
int nkeys; /* length of scankeys array */
int norderbys; /* length of orderbys array */
Datum reconstructedValue; /* value reconstructed at parent */ Datum reconstructedValue; /* value reconstructed at parent */
void *traversalValue; /* opclass-specific traverse value */ void *traversalValue; /* opclass-specific traverse value */
@ -759,8 +797,10 @@ typedef struct spgLeafConsistentIn
typedef struct spgLeafConsistentOut typedef struct spgLeafConsistentOut
{ {
Datum leafValue; /* reconstructed original data, if any */ Datum leafValue; /* reconstructed original data, if any */
bool recheck; /* set true if operator must be rechecked */ bool recheck; /* set true if operator must be rechecked */
bool recheckDistances; /* set true if distances must be rechecked */
double *distances; /* associated distances */
} spgLeafConsistentOut; } spgLeafConsistentOut;
</programlisting> </programlisting>
@ -775,6 +815,8 @@ typedef struct spgLeafConsistentOut
In particular it is not necessary to check <structfield>sk_flags</structfield> to In particular it is not necessary to check <structfield>sk_flags</structfield> to
see if the comparison value is NULL, because the SP-GiST core code see if the comparison value is NULL, because the SP-GiST core code
will filter out such conditions. will filter out such conditions.
The array <structfield>orderbys</structfield>, of length <structfield>norderbys</structfield>,
describes the ordering operators in the same manner.
<structfield>reconstructedValue</structfield> is the value reconstructed for the <structfield>reconstructedValue</structfield> is the value reconstructed for the
parent tuple; it is <literal>(Datum) 0</literal> at the root level or if the parent tuple; it is <literal>(Datum) 0</literal> at the root level or if the
<function>inner_consistent</function> function did not provide a value at the <function>inner_consistent</function> function did not provide a value at the
@ -803,6 +845,12 @@ typedef struct spgLeafConsistentOut
<structfield>recheck</structfield> may be set to <literal>true</literal> if the match <structfield>recheck</structfield> may be set to <literal>true</literal> if the match
is uncertain and so the operator(s) must be re-applied to the actual is uncertain and so the operator(s) must be re-applied to the actual
heap tuple to verify the match. heap tuple to verify the match.
If ordered search is performed, set <structfield>distances</structfield>
to an array of distance values according to <structfield>orderbys</structfield>
array. Leave it NULL otherwise. If at least one of returned distances
is not exact, set <structfield>recheckDistances</structfield> to true.
In this case, the executor will calculate the exact distances after
fetching the tuple from the heap, and will reorder the tuples if needed.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -1242,7 +1242,7 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
<title>Ordering Operators</title> <title>Ordering Operators</title>
<para> <para>
Some index access methods (currently, only GiST) support the concept of Some index access methods (currently, only GiST and SP-GiST) support the concept of
<firstterm>ordering operators</firstterm>. What we have been discussing so far <firstterm>ordering operators</firstterm>. What we have been discussing so far
are <firstterm>search operators</firstterm>. A search operator is one for which are <firstterm>search operators</firstterm>. A search operator is one for which
the index can be searched to find all rows satisfying the index can be searched to find all rows satisfying

View File

@ -14,9 +14,9 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "access/genam.h"
#include "access/gist_private.h" #include "access/gist_private.h"
#include "access/relscan.h" #include "access/relscan.h"
#include "catalog/pg_type.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "storage/predicate.h" #include "storage/predicate.h"
@ -543,7 +543,6 @@ getNextNearest(IndexScanDesc scan)
{ {
GISTScanOpaque so = (GISTScanOpaque) scan->opaque; GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
bool res = false; bool res = false;
int i;
if (scan->xs_hitup) if (scan->xs_hitup)
{ {
@ -564,45 +563,10 @@ getNextNearest(IndexScanDesc scan)
/* found a heap item at currently minimal distance */ /* found a heap item at currently minimal distance */
scan->xs_ctup.t_self = item->data.heap.heapPtr; scan->xs_ctup.t_self = item->data.heap.heapPtr;
scan->xs_recheck = item->data.heap.recheck; scan->xs_recheck = item->data.heap.recheck;
scan->xs_recheckorderby = item->data.heap.recheckDistances;
for (i = 0; i < scan->numberOfOrderBys; i++) index_store_float8_orderby_distances(scan, so->orderByTypes,
{ item->distances,
if (so->orderByTypes[i] == FLOAT8OID) item->data.heap.recheckDistances);
{
#ifndef USE_FLOAT8_BYVAL
/* must free any old value to avoid memory leakage */
if (!scan->xs_orderbynulls[i])
pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
#endif
scan->xs_orderbyvals[i] = Float8GetDatum(item->distances[i]);
scan->xs_orderbynulls[i] = false;
}
else if (so->orderByTypes[i] == FLOAT4OID)
{
/* convert distance function's result to ORDER BY type */
#ifndef USE_FLOAT4_BYVAL
/* must free any old value to avoid memory leakage */
if (!scan->xs_orderbynulls[i])
pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
#endif
scan->xs_orderbyvals[i] = Float4GetDatum((float4) item->distances[i]);
scan->xs_orderbynulls[i] = false;
}
else
{
/*
* If the ordering operator's return value is anything
* else, we don't know how to convert the float8 bound
* calculated by the distance function to that. The
* executor won't actually need the order by values we
* return here, if there are no lossy results, so only
* insist on converting if the *recheck flag is set.
*/
if (scan->xs_recheckorderby)
elog(ERROR, "GiST operator family's FOR ORDER BY operator must return float8 or float4 if the distance function is lossy");
scan->xs_orderbynulls[i] = true;
}
}
/* in an index-only scan, also return the reconstructed tuple. */ /* in an index-only scan, also return the reconstructed tuple. */
if (scan->xs_want_itup) if (scan->xs_want_itup)

View File

@ -23,6 +23,7 @@
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "utils/float.h" #include "utils/float.h"
#include "utils/syscache.h" #include "utils/syscache.h"
#include "utils/lsyscache.h"
/* /*
@ -871,12 +872,6 @@ gistproperty(Oid index_oid, int attno,
IndexAMProperty prop, const char *propname, IndexAMProperty prop, const char *propname,
bool *res, bool *isnull) bool *res, bool *isnull)
{ {
HeapTuple tuple;
Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
Form_pg_opclass rd_opclass;
Datum datum;
bool disnull;
oidvector *indclass;
Oid opclass, Oid opclass,
opfamily, opfamily,
opcintype; opcintype;
@ -910,41 +905,19 @@ gistproperty(Oid index_oid, int attno,
} }
/* First we need to know the column's opclass. */ /* First we need to know the column's opclass. */
opclass = get_index_column_opclass(index_oid, attno);
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); if (!OidIsValid(opclass))
if (!HeapTupleIsValid(tuple))
{ {
*isnull = true; *isnull = true;
return true; return true;
} }
rd_index = (Form_pg_index) GETSTRUCT(tuple);
/* caller is supposed to guarantee this */
Assert(attno > 0 && attno <= rd_index->indnatts);
datum = SysCacheGetAttr(INDEXRELID, tuple,
Anum_pg_index_indclass, &disnull);
Assert(!disnull);
indclass = ((oidvector *) DatumGetPointer(datum));
opclass = indclass->values[attno - 1];
ReleaseSysCache(tuple);
/* Now look up the opclass family and input datatype. */ /* Now look up the opclass family and input datatype. */
if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
if (!HeapTupleIsValid(tuple))
{ {
*isnull = true; *isnull = true;
return true; return true;
} }
rd_opclass = (Form_pg_opclass) GETSTRUCT(tuple);
opfamily = rd_opclass->opcfamily;
opcintype = rd_opclass->opcintype;
ReleaseSysCache(tuple);
/* And now we can check whether the function is provided. */ /* And now we can check whether the function is provided. */
@ -967,6 +940,8 @@ gistproperty(Oid index_oid, int attno,
Int16GetDatum(GIST_COMPRESS_PROC)); Int16GetDatum(GIST_COMPRESS_PROC));
} }
*isnull = false;
return true; return true;
} }

View File

@ -74,6 +74,7 @@
#include "access/transam.h" #include "access/transam.h"
#include "access/xlog.h" #include "access/xlog.h"
#include "catalog/index.h" #include "catalog/index.h"
#include "catalog/pg_type.h"
#include "pgstat.h" #include "pgstat.h"
#include "storage/bufmgr.h" #include "storage/bufmgr.h"
#include "storage/lmgr.h" #include "storage/lmgr.h"
@ -897,3 +898,72 @@ index_getprocinfo(Relation irel,
return locinfo; return locinfo;
} }
/* ----------------
* index_store_float8_orderby_distances
*
* Convert AM distance function's results (that can be inexact)
* to ORDER BY types and save them into xs_orderbyvals/xs_orderbynulls
* for a possible recheck.
* ----------------
*/
void
index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes,
double *distances, bool recheckOrderBy)
{
int i;
scan->xs_recheckorderby = recheckOrderBy;
if (!distances)
{
Assert(!scan->xs_recheckorderby);
for (i = 0; i < scan->numberOfOrderBys; i++)
{
scan->xs_orderbyvals[i] = (Datum) 0;
scan->xs_orderbynulls[i] = true;
}
return;
}
for (i = 0; i < scan->numberOfOrderBys; i++)
{
if (orderByTypes[i] == FLOAT8OID)
{
#ifndef USE_FLOAT8_BYVAL
/* must free any old value to avoid memory leakage */
if (!scan->xs_orderbynulls[i])
pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
#endif
scan->xs_orderbyvals[i] = Float8GetDatum(distances[i]);
scan->xs_orderbynulls[i] = false;
}
else if (orderByTypes[i] == FLOAT4OID)
{
/* convert distance function's result to ORDER BY type */
#ifndef USE_FLOAT4_BYVAL
/* must free any old value to avoid memory leakage */
if (!scan->xs_orderbynulls[i])
pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
#endif
scan->xs_orderbyvals[i] = Float4GetDatum((float4) distances[i]);
scan->xs_orderbynulls[i] = false;
}
else
{
/*
* If the ordering operator's return value is anything else, we
* don't know how to convert the float8 bound calculated by the
* distance function to that. The executor won't actually need
* the order by values we return here, if there are no lossy
* results, so only insist on converting if the *recheck flag is
* set.
*/
if (scan->xs_recheckorderby)
elog(ERROR, "ORDER BY operator must return float8 or float4 if the distance function is lossy");
scan->xs_orderbynulls[i] = true;
}
}
}

View File

@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = spgutils.o spginsert.o spgscan.o spgvacuum.o spgvalidate.o \ OBJS = spgutils.o spginsert.o spgscan.o spgvacuum.o spgvalidate.o \
spgdoinsert.o spgxlog.o \ spgdoinsert.o spgxlog.o \
spgtextproc.o spgquadtreeproc.o spgkdtreeproc.o spgtextproc.o spgquadtreeproc.o spgkdtreeproc.o \
spgproc.o
include $(top_srcdir)/src/backend/common.mk include $(top_srcdir)/src/backend/common.mk

View File

@ -41,7 +41,11 @@ contain exactly one inner tuple.
When the search traversal algorithm reaches an inner tuple, it chooses a set When the search traversal algorithm reaches an inner tuple, it chooses a set
of nodes to continue tree traverse in depth. If it reaches a leaf page it of nodes to continue tree traverse in depth. If it reaches a leaf page it
scans a list of leaf tuples to find the ones that match the query. scans a list of leaf tuples to find the ones that match the query. SP-GiST
also supports ordered (nearest-neighbor) searches - that is during scan pending
nodes are put into priority queue, so traversal is performed by the
closest-first model.
The insertion algorithm descends the tree similarly, except it must choose The insertion algorithm descends the tree similarly, except it must choose
just one node to descend to from each inner tuple. Insertion might also have just one node to descend to from each inner tuple. Insertion might also have

View File

@ -16,9 +16,11 @@
#include "postgres.h" #include "postgres.h"
#include "access/spgist.h" #include "access/spgist.h"
#include "access/spgist_private.h"
#include "access/stratnum.h" #include "access/stratnum.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/float.h"
#include "utils/geo_decls.h" #include "utils/geo_decls.h"
@ -162,6 +164,7 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS)
double coord; double coord;
int which; int which;
int i; int i;
BOX bboxes[2];
Assert(in->hasPrefix); Assert(in->hasPrefix);
coord = DatumGetFloat8(in->prefixDatum); coord = DatumGetFloat8(in->prefixDatum);
@ -248,12 +251,87 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS)
} }
/* We must descend into the children identified by which */ /* We must descend into the children identified by which */
out->nodeNumbers = (int *) palloc(sizeof(int) * 2);
out->nNodes = 0; out->nNodes = 0;
/* Fast-path for no matching children */
if (!which)
PG_RETURN_VOID();
out->nodeNumbers = (int *) palloc(sizeof(int) * 2);
/*
* When ordering scan keys are specified, we've to calculate distance for
* them. In order to do that, we need calculate bounding boxes for both
* children nodes. Calculation of those bounding boxes on non-zero level
* require knowledge of bounding box of upper node. So, we save bounding
* boxes to traversalValues.
*/
if (in->norderbys > 0)
{
BOX infArea;
BOX *area;
out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
if (in->level == 0)
{
float8 inf = get_float8_infinity();
infArea.high.x = inf;
infArea.high.y = inf;
infArea.low.x = -inf;
infArea.low.y = -inf;
area = &infArea;
}
else
{
area = (BOX *) in->traversalValue;
Assert(area);
}
bboxes[0].low = area->low;
bboxes[1].high = area->high;
if (in->level % 2)
{
/* split box by x */
bboxes[0].high.x = bboxes[1].low.x = coord;
bboxes[0].high.y = area->high.y;
bboxes[1].low.y = area->low.y;
}
else
{
/* split box by y */
bboxes[0].high.y = bboxes[1].low.y = coord;
bboxes[0].high.x = area->high.x;
bboxes[1].low.x = area->low.x;
}
}
for (i = 1; i <= 2; i++) for (i = 1; i <= 2; i++)
{ {
if (which & (1 << i)) if (which & (1 << i))
out->nodeNumbers[out->nNodes++] = i - 1; {
out->nodeNumbers[out->nNodes] = i - 1;
if (in->norderbys > 0)
{
MemoryContext oldCtx = MemoryContextSwitchTo(
in->traversalMemoryContext);
BOX *box = box_copy(&bboxes[i - 1]);
MemoryContextSwitchTo(oldCtx);
out->traversalValues[out->nNodes] = box;
out->distances[out->nNodes] = spg_key_orderbys_distances(
BoxPGetDatum(box), false,
in->orderbys, in->norderbys);
}
out->nNodes++;
}
} }
/* Set up level increments, too */ /* Set up level increments, too */

View File

@ -0,0 +1,88 @@
/*-------------------------------------------------------------------------
*
* spgproc.c
* Common supporting procedures for SP-GiST opclasses.
*
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/backend/access/spgist/spgproc.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <math.h>
#include "access/spgist_private.h"
#include "utils/builtins.h"
#include "utils/float.h"
#include "utils/geo_decls.h"
#define point_point_distance(p1,p2) \
DatumGetFloat8(DirectFunctionCall2(point_distance, \
PointPGetDatum(p1), PointPGetDatum(p2)))
/* Point-box distance in the assumption that box is aligned by axis */
static double
point_box_distance(Point *point, BOX *box)
{
double dx,
dy;
if (isnan(point->x) || isnan(box->low.x) ||
isnan(point->y) || isnan(box->low.y))
return get_float8_nan();
if (point->x < box->low.x)
dx = box->low.x - point->x;
else if (point->x > box->high.x)
dx = point->x - box->high.x;
else
dx = 0.0;
if (point->y < box->low.y)
dy = box->low.y - point->y;
else if (point->y > box->high.y)
dy = point->y - box->high.y;
else
dy = 0.0;
return HYPOT(dx, dy);
}
/*
* Returns distances from given key to array of ordering scan keys. Leaf key
* is expected to be point, non-leaf key is expected to be box. Scan key
* arguments are expected to be points.
*/
double *
spg_key_orderbys_distances(Datum key, bool isLeaf,
ScanKey orderbys, int norderbys)
{
int sk_num;
double *distances = (double *) palloc(norderbys * sizeof(double)),
*distance = distances;
for (sk_num = 0; sk_num < norderbys; ++sk_num, ++orderbys, ++distance)
{
Point *point = DatumGetPointP(orderbys->sk_argument);
*distance = isLeaf ? point_point_distance(point, DatumGetPointP(key))
: point_box_distance(point, DatumGetBoxP(key));
}
return distances;
}
BOX *
box_copy(BOX *orig)
{
BOX *result = palloc(sizeof(BOX));
*result = *orig;
return result;
}

View File

@ -17,8 +17,10 @@
#include "access/spgist.h" #include "access/spgist.h"
#include "access/stratnum.h" #include "access/stratnum.h"
#include "access/spgist_private.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/float.h"
#include "utils/geo_decls.h" #include "utils/geo_decls.h"
@ -77,6 +79,38 @@ getQuadrant(Point *centroid, Point *tst)
return 0; return 0;
} }
/* Returns bounding box of a given quadrant inside given bounding box */
static BOX *
getQuadrantArea(BOX *bbox, Point *centroid, int quadrant)
{
BOX *result = (BOX *) palloc(sizeof(BOX));
switch (quadrant)
{
case 1:
result->high = bbox->high;
result->low = *centroid;
break;
case 2:
result->high.x = bbox->high.x;
result->high.y = centroid->y;
result->low.x = centroid->x;
result->low.y = bbox->low.y;
break;
case 3:
result->high = *centroid;
result->low = bbox->low;
break;
case 4:
result->high.x = centroid->x;
result->high.y = bbox->high.y;
result->low.x = bbox->low.x;
result->low.y = centroid->y;
break;
}
return result;
}
Datum Datum
spg_quad_choose(PG_FUNCTION_ARGS) spg_quad_choose(PG_FUNCTION_ARGS)
@ -196,19 +230,68 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
Point *centroid; Point *centroid;
BOX infbbox;
BOX *bbox = NULL;
int which; int which;
int i; int i;
Assert(in->hasPrefix); Assert(in->hasPrefix);
centroid = DatumGetPointP(in->prefixDatum); centroid = DatumGetPointP(in->prefixDatum);
/*
* When ordering scan keys are specified, we've to calculate distance for
* them. In order to do that, we need calculate bounding boxes for all
* children nodes. Calculation of those bounding boxes on non-zero level
* require knowledge of bounding box of upper node. So, we save bounding
* boxes to traversalValues.
*/
if (in->norderbys > 0)
{
out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
if (in->level == 0)
{
double inf = get_float8_infinity();
infbbox.high.x = inf;
infbbox.high.y = inf;
infbbox.low.x = -inf;
infbbox.low.y = -inf;
bbox = &infbbox;
}
else
{
bbox = in->traversalValue;
Assert(bbox);
}
}
if (in->allTheSame) if (in->allTheSame)
{ {
/* Report that all nodes should be visited */ /* Report that all nodes should be visited */
out->nNodes = in->nNodes; out->nNodes = in->nNodes;
out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
for (i = 0; i < in->nNodes; i++) for (i = 0; i < in->nNodes; i++)
{
out->nodeNumbers[i] = i; out->nodeNumbers[i] = i;
if (in->norderbys > 0)
{
MemoryContext oldCtx = MemoryContextSwitchTo(
in->traversalMemoryContext);
/* Use parent quadrant box as traversalValue */
BOX *quadrant = box_copy(bbox);
MemoryContextSwitchTo(oldCtx);
out->traversalValues[i] = quadrant;
out->distances[i] = spg_key_orderbys_distances(
BoxPGetDatum(quadrant), false,
in->orderbys, in->norderbys);
}
}
PG_RETURN_VOID(); PG_RETURN_VOID();
} }
@ -286,13 +369,37 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
break; /* no need to consider remaining conditions */ break; /* no need to consider remaining conditions */
} }
out->levelAdds = palloc(sizeof(int) * 4);
for (i = 0; i < 4; ++i)
out->levelAdds[i] = 1;
/* We must descend into the quadrant(s) identified by which */ /* We must descend into the quadrant(s) identified by which */
out->nodeNumbers = (int *) palloc(sizeof(int) * 4); out->nodeNumbers = (int *) palloc(sizeof(int) * 4);
out->nNodes = 0; out->nNodes = 0;
for (i = 1; i <= 4; i++) for (i = 1; i <= 4; i++)
{ {
if (which & (1 << i)) if (which & (1 << i))
out->nodeNumbers[out->nNodes++] = i - 1; {
out->nodeNumbers[out->nNodes] = i - 1;
if (in->norderbys > 0)
{
MemoryContext oldCtx = MemoryContextSwitchTo(
in->traversalMemoryContext);
BOX *quadrant = getQuadrantArea(bbox, centroid, i);
MemoryContextSwitchTo(oldCtx);
out->traversalValues[out->nNodes] = quadrant;
out->distances[out->nNodes] = spg_key_orderbys_distances(
BoxPGetDatum(quadrant), false,
in->orderbys, in->norderbys);
}
out->nNodes++;
}
} }
PG_RETURN_VOID(); PG_RETURN_VOID();
@ -356,5 +463,11 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
break; break;
} }
if (res && in->norderbys > 0)
/* ok, it passes -> let's compute the distances */
out->distances = spg_key_orderbys_distances(
BoxPGetDatum(in->leafDatum), true,
in->orderbys, in->norderbys);
PG_RETURN_BOOL(res); PG_RETURN_BOOL(res);
} }

File diff suppressed because it is too large Load Diff

View File

@ -15,17 +15,26 @@
#include "postgres.h" #include "postgres.h"
#include "access/amvalidate.h"
#include "access/htup_details.h"
#include "access/reloptions.h" #include "access/reloptions.h"
#include "access/spgist_private.h" #include "access/spgist_private.h"
#include "access/transam.h" #include "access/transam.h"
#include "access/xact.h" #include "access/xact.h"
#include "catalog/pg_amop.h"
#include "optimizer/paths.h"
#include "storage/bufmgr.h" #include "storage/bufmgr.h"
#include "storage/indexfsm.h" #include "storage/indexfsm.h"
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/index_selfuncs.h" #include "utils/index_selfuncs.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h"
extern Expr *spgcanorderbyop(IndexOptInfo *index,
PathKey *pathkey, int pathkeyno,
Expr *orderby_clause, int *indexcol_p);
/* /*
* SP-GiST handler function: return IndexAmRoutine with access method parameters * SP-GiST handler function: return IndexAmRoutine with access method parameters
@ -39,7 +48,7 @@ spghandler(PG_FUNCTION_ARGS)
amroutine->amstrategies = 0; amroutine->amstrategies = 0;
amroutine->amsupport = SPGISTNProc; amroutine->amsupport = SPGISTNProc;
amroutine->amcanorder = false; amroutine->amcanorder = false;
amroutine->amcanorderbyop = false; amroutine->amcanorderbyop = true;
amroutine->amcanbackward = false; amroutine->amcanbackward = false;
amroutine->amcanunique = false; amroutine->amcanunique = false;
amroutine->amcanmulticol = false; amroutine->amcanmulticol = false;
@ -61,7 +70,7 @@ spghandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = spgcanreturn; amroutine->amcanreturn = spgcanreturn;
amroutine->amcostestimate = spgcostestimate; amroutine->amcostestimate = spgcostestimate;
amroutine->amoptions = spgoptions; amroutine->amoptions = spgoptions;
amroutine->amproperty = NULL; amroutine->amproperty = spgproperty;
amroutine->amvalidate = spgvalidate; amroutine->amvalidate = spgvalidate;
amroutine->ambeginscan = spgbeginscan; amroutine->ambeginscan = spgbeginscan;
amroutine->amrescan = spgrescan; amroutine->amrescan = spgrescan;
@ -949,3 +958,82 @@ SpGistPageAddNewItem(SpGistState *state, Page page, Item item, Size size,
return offnum; return offnum;
} }
/*
* spgproperty() -- Check boolean properties of indexes.
*
* This is optional for most AMs, but is required for SP-GiST because the core
* property code doesn't support AMPROP_DISTANCE_ORDERABLE.
*/
bool
spgproperty(Oid index_oid, int attno,
IndexAMProperty prop, const char *propname,
bool *res, bool *isnull)
{
Oid opclass,
opfamily,
opcintype;
CatCList *catlist;
int i;
/* Only answer column-level inquiries */
if (attno == 0)
return false;
switch (prop)
{
case AMPROP_DISTANCE_ORDERABLE:
break;
default:
return false;
}
/*
* Currently, SP-GiST distance-ordered scans require that there be a
* distance operator in the opclass with the default types. So we assume
* that if such a operator exists, then there's a reason for it.
*/
/* First we need to know the column's opclass. */
opclass = get_index_column_opclass(index_oid, attno);
if (!OidIsValid(opclass))
{
*isnull = true;
return true;
}
/* Now look up the opclass family and input datatype. */
if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
{
*isnull = true;
return true;
}
/* And now we can check whether the operator is provided. */
catlist = SearchSysCacheList1(AMOPSTRATEGY,
ObjectIdGetDatum(opfamily));
*res = false;
for (i = 0; i < catlist->n_members; i++)
{
HeapTuple amoptup = &catlist->members[i]->tuple;
Form_pg_amop amopform = (Form_pg_amop) GETSTRUCT(amoptup);
if (amopform->amoppurpose == AMOP_ORDER &&
(amopform->amoplefttype == opcintype ||
amopform->amoprighttype == opcintype) &&
opfamily_can_sort_type(amopform->amopsortfamily,
get_op_rettype(amopform->amopopr)))
{
*res = true;
break;
}
}
ReleaseSysCacheList(catlist);
*isnull = false;
return true;
}

View File

@ -187,6 +187,7 @@ spgvalidate(Oid opclassoid)
{ {
HeapTuple oprtup = &oprlist->members[i]->tuple; HeapTuple oprtup = &oprlist->members[i]->tuple;
Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
Oid op_rettype;
/* TODO: Check that only allowed strategy numbers exist */ /* TODO: Check that only allowed strategy numbers exist */
if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63) if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63)
@ -200,20 +201,26 @@ spgvalidate(Oid opclassoid)
result = false; result = false;
} }
/* spgist doesn't support ORDER BY operators */ /* spgist supports ORDER BY operators */
if (oprform->amoppurpose != AMOP_SEARCH || if (oprform->amoppurpose != AMOP_SEARCH)
OidIsValid(oprform->amopsortfamily))
{ {
ereport(INFO, /* ... and operator result must match the claimed btree opfamily */
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), op_rettype = get_op_rettype(oprform->amopopr);
errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s", if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
opfamilyname, "spgist", {
format_operator(oprform->amopopr)))); ereport(INFO,
result = false; (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
opfamilyname, "spgist",
format_operator(oprform->amopopr))));
result = false;
}
} }
else
op_rettype = BOOLOID;
/* Check operator signature --- same for all spgist strategies */ /* Check operator signature --- same for all spgist strategies */
if (!check_amop_signature(oprform->amopopr, BOOLOID, if (!check_amop_signature(oprform->amopopr, op_rettype,
oprform->amoplefttype, oprform->amoplefttype,
oprform->amoprighttype)) oprform->amoprighttype))
{ {

View File

@ -74,9 +74,11 @@
#include "postgres.h" #include "postgres.h"
#include "access/spgist.h" #include "access/spgist.h"
#include "access/spgist_private.h"
#include "access/stratnum.h" #include "access/stratnum.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "utils/float.h" #include "utils/float.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h" #include "utils/fmgrprotos.h"
#include "utils/geo_decls.h" #include "utils/geo_decls.h"
@ -367,6 +369,31 @@ overAbove4D(RectBox *rect_box, RangeBox *query)
return overHigher2D(&rect_box->range_box_y, &query->right); return overHigher2D(&rect_box->range_box_y, &query->right);
} }
/* Lower bound for the distance between point and rect_box */
static double
pointToRectBoxDistance(Point *point, RectBox *rect_box)
{
double dx;
double dy;
if (point->x < rect_box->range_box_x.left.low)
dx = rect_box->range_box_x.left.low - point->x;
else if (point->x > rect_box->range_box_x.right.high)
dx = point->x - rect_box->range_box_x.right.high;
else
dx = 0;
if (point->y < rect_box->range_box_y.left.low)
dy = rect_box->range_box_y.left.low - point->y;
else if (point->y > rect_box->range_box_y.right.high)
dy = point->y - rect_box->range_box_y.right.high;
else
dy = 0;
return HYPOT(dx, dy);
}
/* /*
* SP-GiST config function * SP-GiST config function
*/ */
@ -534,17 +561,6 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
RangeBox *centroid, RangeBox *centroid,
**queries; **queries;
if (in->allTheSame)
{
/* Report that all nodes should be visited */
out->nNodes = in->nNodes;
out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
for (i = 0; i < in->nNodes; i++)
out->nodeNumbers[i] = i;
PG_RETURN_VOID();
}
/* /*
* We are saving the traversal value or initialize it an unbounded one, if * We are saving the traversal value or initialize it an unbounded one, if
* we have just begun to walk the tree. * we have just begun to walk the tree.
@ -554,6 +570,40 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
else else
rect_box = initRectBox(); rect_box = initRectBox();
if (in->allTheSame)
{
/* Report that all nodes should be visited */
out->nNodes = in->nNodes;
out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
for (i = 0; i < in->nNodes; i++)
out->nodeNumbers[i] = i;
if (in->norderbys > 0 && in->nNodes > 0)
{
double *distances = palloc(sizeof(double) * in->norderbys);
int j;
for (j = 0; j < in->norderbys; j++)
{
Point *pt = DatumGetPointP(in->orderbys[j].sk_argument);
distances[j] = pointToRectBoxDistance(pt, rect_box);
}
out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
out->distances[0] = distances;
for (i = 1; i < in->nNodes; i++)
{
out->distances[i] = palloc(sizeof(double) * in->norderbys);
memcpy(out->distances[i], distances,
sizeof(double) * in->norderbys);
}
}
PG_RETURN_VOID();
}
/* /*
* We are casting the prefix and queries to RangeBoxes for ease of the * We are casting the prefix and queries to RangeBoxes for ease of the
* following operations. * following operations.
@ -571,6 +621,8 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
out->nNodes = 0; out->nNodes = 0;
out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes); out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
if (in->norderbys > 0)
out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
/* /*
* We switch memory context, because we want to allocate memory for new * We switch memory context, because we want to allocate memory for new
@ -648,6 +700,22 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
{ {
out->traversalValues[out->nNodes] = next_rect_box; out->traversalValues[out->nNodes] = next_rect_box;
out->nodeNumbers[out->nNodes] = quadrant; out->nodeNumbers[out->nNodes] = quadrant;
if (in->norderbys > 0)
{
double *distances = palloc(sizeof(double) * in->norderbys);
int j;
out->distances[out->nNodes] = distances;
for (j = 0; j < in->norderbys; j++)
{
Point *pt = DatumGetPointP(in->orderbys[j].sk_argument);
distances[j] = pointToRectBoxDistance(pt, next_rect_box);
}
}
out->nNodes++; out->nNodes++;
} }
else else
@ -763,6 +831,17 @@ spg_box_quad_leaf_consistent(PG_FUNCTION_ARGS)
break; break;
} }
if (flag && in->norderbys > 0)
{
Oid distfnoid = in->orderbys[0].sk_func.fn_oid;
out->distances = spg_key_orderbys_distances(leaf, false,
in->orderbys, in->norderbys);
/* Recheck is necessary when computing distance to polygon */
out->recheckDistances = distfnoid == F_DIST_POLYP;
}
PG_RETURN_BOOL(flag); PG_RETURN_BOOL(flag);
} }

View File

@ -1067,6 +1067,32 @@ get_opclass_input_type(Oid opclass)
return result; return result;
} }
/*
* get_opclass_family_and_input_type
*
* Returns the OID of the operator family the opclass belongs to,
* the OID of the datatype the opclass indexes
*/
bool
get_opclass_opfamily_and_input_type(Oid opclass, Oid *opfamily, Oid *opcintype)
{
HeapTuple tp;
Form_pg_opclass cla_tup;
tp = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
if (!HeapTupleIsValid(tp))
return false;
cla_tup = (Form_pg_opclass) GETSTRUCT(tp);
*opfamily = cla_tup->opcfamily;
*opcintype = cla_tup->opcintype;
ReleaseSysCache(tp);
return true;
}
/* ---------- OPERATOR CACHE ---------- */ /* ---------- OPERATOR CACHE ---------- */
/* /*
@ -3106,3 +3132,45 @@ get_range_subtype(Oid rangeOid)
else else
return InvalidOid; return InvalidOid;
} }
/* ---------- PG_INDEX CACHE ---------- */
/*
* get_index_column_opclass
*
* Given the index OID and column number,
* return opclass of the index column
* or InvalidOid if the index was not found.
*/
Oid
get_index_column_opclass(Oid index_oid, int attno)
{
HeapTuple tuple;
Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
Datum datum;
bool isnull;
oidvector *indclass;
Oid opclass;
/* First we need to know the column's opclass. */
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
if (!HeapTupleIsValid(tuple))
return InvalidOid;
rd_index = (Form_pg_index) GETSTRUCT(tuple);
/* caller is supposed to guarantee this */
Assert(attno > 0 && attno <= rd_index->indnatts);
datum = SysCacheGetAttr(INDEXRELID, tuple,
Anum_pg_index_indclass, &isnull);
Assert(!isnull);
indclass = ((oidvector *) DatumGetPointer(datum));
opclass = indclass->values[attno - 1];
ReleaseSysCache(tuple);
return opclass;
}

View File

@ -174,6 +174,9 @@ extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
uint16 procnum); uint16 procnum);
extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum, extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
uint16 procnum); uint16 procnum);
extern void index_store_float8_orderby_distances(IndexScanDesc scan,
Oid *orderByTypes, double *distances,
bool recheckOrderBy);
/* /*
* index access method support routines (in genam.c) * index access method support routines (in genam.c)

View File

@ -136,7 +136,10 @@ typedef struct spgPickSplitOut
typedef struct spgInnerConsistentIn typedef struct spgInnerConsistentIn
{ {
ScanKey scankeys; /* array of operators and comparison values */ ScanKey scankeys; /* array of operators and comparison values */
int nkeys; /* length of array */ ScanKey orderbys; /* array of ordering operators and comparison
* values */
int nkeys; /* length of scankeys array */
int norderbys; /* length of orderbys array */
Datum reconstructedValue; /* value reconstructed at parent */ Datum reconstructedValue; /* value reconstructed at parent */
void *traversalValue; /* opclass-specific traverse value */ void *traversalValue; /* opclass-specific traverse value */
@ -159,6 +162,7 @@ typedef struct spgInnerConsistentOut
int *levelAdds; /* increment level by this much for each */ int *levelAdds; /* increment level by this much for each */
Datum *reconstructedValues; /* associated reconstructed values */ Datum *reconstructedValues; /* associated reconstructed values */
void **traversalValues; /* opclass-specific traverse values */ void **traversalValues; /* opclass-specific traverse values */
double **distances; /* associated distances */
} spgInnerConsistentOut; } spgInnerConsistentOut;
/* /*
@ -167,7 +171,10 @@ typedef struct spgInnerConsistentOut
typedef struct spgLeafConsistentIn typedef struct spgLeafConsistentIn
{ {
ScanKey scankeys; /* array of operators and comparison values */ ScanKey scankeys; /* array of operators and comparison values */
int nkeys; /* length of array */ ScanKey orderbys; /* array of ordering operators and comparison
* values */
int nkeys; /* length of scankeys array */
int norderbys; /* length of orderbys array */
Datum reconstructedValue; /* value reconstructed at parent */ Datum reconstructedValue; /* value reconstructed at parent */
void *traversalValue; /* opclass-specific traverse value */ void *traversalValue; /* opclass-specific traverse value */
@ -181,6 +188,8 @@ typedef struct spgLeafConsistentOut
{ {
Datum leafValue; /* reconstructed original data, if any */ Datum leafValue; /* reconstructed original data, if any */
bool recheck; /* set true if operator must be rechecked */ bool recheck; /* set true if operator must be rechecked */
bool recheckDistances; /* set true if distances must be rechecked */
double *distances; /* associated distances */
} spgLeafConsistentOut; } spgLeafConsistentOut;

View File

@ -18,6 +18,7 @@
#include "access/spgist.h" #include "access/spgist.h"
#include "nodes/tidbitmap.h" #include "nodes/tidbitmap.h"
#include "storage/buf.h" #include "storage/buf.h"
#include "utils/geo_decls.h"
#include "utils/relcache.h" #include "utils/relcache.h"
@ -130,14 +131,35 @@ typedef struct SpGistState
bool isBuild; /* true if doing index build */ bool isBuild; /* true if doing index build */
} SpGistState; } SpGistState;
typedef struct SpGistSearchItem
{
pairingheap_node phNode; /* pairing heap node */
Datum value; /* value reconstructed from parent or
* leafValue if heaptuple */
void *traversalValue; /* opclass-specific traverse value */
int level; /* level of items on this page */
ItemPointerData heapPtr; /* heap info, if heap tuple */
bool isNull; /* SearchItem is NULL item */
bool isLeaf; /* SearchItem is heap item */
bool recheck; /* qual recheck is needed */
bool recheckDistances; /* distance recheck is needed */
/* array with numberOfOrderBys entries */
double distances[FLEXIBLE_ARRAY_MEMBER];
} SpGistSearchItem;
#define SizeOfSpGistSearchItem(n_distances) \
(offsetof(SpGistSearchItem, distances) + sizeof(double) * (n_distances))
/* /*
* Private state of an index scan * Private state of an index scan
*/ */
typedef struct SpGistScanOpaqueData typedef struct SpGistScanOpaqueData
{ {
SpGistState state; /* see above */ SpGistState state; /* see above */
pairingheap *scanQueue; /* queue of to be visited items */
MemoryContext tempCxt; /* short-lived memory context */ MemoryContext tempCxt; /* short-lived memory context */
MemoryContext traversalCxt; /* memory context for traversalValues */ MemoryContext traversalCxt; /* single scan lifetime memory context */
/* Control flags showing whether to search nulls and/or non-nulls */ /* Control flags showing whether to search nulls and/or non-nulls */
bool searchNulls; /* scan matches (all) null entries */ bool searchNulls; /* scan matches (all) null entries */
@ -146,9 +168,18 @@ typedef struct SpGistScanOpaqueData
/* Index quals to be passed to opclass (null-related quals removed) */ /* Index quals to be passed to opclass (null-related quals removed) */
int numberOfKeys; /* number of index qualifier conditions */ int numberOfKeys; /* number of index qualifier conditions */
ScanKey keyData; /* array of index qualifier descriptors */ ScanKey keyData; /* array of index qualifier descriptors */
int numberOfOrderBys; /* number of ordering operators */
ScanKey orderByData; /* array of ordering op descriptors */
Oid *orderByTypes; /* array of ordering op return types */
Oid indexCollation; /* collation of index column */
/* Stack of yet-to-be-visited pages */ /* Opclass defined functions: */
List *scanStack; /* List of ScanStackEntrys */ FmgrInfo innerConsistentFn;
FmgrInfo leafConsistentFn;
/* Pre-allocated workspace arrays: */
double *zeroDistances;
double *infDistances;
/* These fields are only used in amgetbitmap scans: */ /* These fields are only used in amgetbitmap scans: */
TIDBitmap *tbm; /* bitmap being filled */ TIDBitmap *tbm; /* bitmap being filled */
@ -161,7 +192,10 @@ typedef struct SpGistScanOpaqueData
int iPtr; /* index for scanning through same */ int iPtr; /* index for scanning through same */
ItemPointerData heapPtrs[MaxIndexTuplesPerPage]; /* TIDs from cur page */ ItemPointerData heapPtrs[MaxIndexTuplesPerPage]; /* TIDs from cur page */
bool recheck[MaxIndexTuplesPerPage]; /* their recheck flags */ bool recheck[MaxIndexTuplesPerPage]; /* their recheck flags */
bool recheckDistances[MaxIndexTuplesPerPage]; /* distance recheck
* flags */
HeapTuple reconTups[MaxIndexTuplesPerPage]; /* reconstructed tuples */ HeapTuple reconTups[MaxIndexTuplesPerPage]; /* reconstructed tuples */
double *distances[MaxIndexTuplesPerPage]; /* distances (for recheck) */
/* /*
* Note: using MaxIndexTuplesPerPage above is a bit hokey since * Note: using MaxIndexTuplesPerPage above is a bit hokey since
@ -410,6 +444,9 @@ extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page,
Item item, Size size, Item item, Size size,
OffsetNumber *startOffset, OffsetNumber *startOffset,
bool errorOK); bool errorOK);
extern bool spgproperty(Oid index_oid, int attno,
IndexAMProperty prop, const char *propname,
bool *res, bool *isnull);
/* spgdoinsert.c */ /* spgdoinsert.c */
extern void spgUpdateNodeLink(SpGistInnerTuple tup, int nodeN, extern void spgUpdateNodeLink(SpGistInnerTuple tup, int nodeN,
@ -421,4 +458,9 @@ extern void spgPageIndexMultiDelete(SpGistState *state, Page page,
extern bool spgdoinsert(Relation index, SpGistState *state, extern bool spgdoinsert(Relation index, SpGistState *state,
ItemPointer heapPtr, Datum datum, bool isnull); ItemPointer heapPtr, Datum datum, bool isnull);
/* spgproc.c */
extern double *spg_key_orderbys_distances(Datum key, bool isLeaf,
ScanKey orderbys, int norderbys);
extern BOX *box_copy(BOX *orig);
#endif /* SPGIST_PRIVATE_H */ #endif /* SPGIST_PRIVATE_H */

View File

@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201809181 #define CATALOG_VERSION_NO 201809191
#endif #endif

View File

@ -1401,6 +1401,10 @@
{ amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point', { amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point',
amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)', amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)',
amopmethod => 'spgist' }, amopmethod => 'spgist' },
{ amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point',
amoprighttype => 'point', amopstrategy => '15', amopopr => '<->(point,point)',
amopmethod => 'spgist', amoppurpose => 'o',
amopsortfamily => 'btree/float_ops' },
# SP-GiST kd_point_ops # SP-GiST kd_point_ops
{ amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point', { amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point',
@ -1421,6 +1425,10 @@
{ amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point', { amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point',
amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)', amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)',
amopmethod => 'spgist' }, amopmethod => 'spgist' },
{ amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point',
amoprighttype => 'point', amopstrategy => '15', amopopr => '<->(point,point)',
amopmethod => 'spgist', amoppurpose => 'o',
amopsortfamily => 'btree/float_ops' },
# SP-GiST text_ops # SP-GiST text_ops
{ amopfamily => 'spgist/text_ops', amoplefttype => 'text', { amopfamily => 'spgist/text_ops', amoplefttype => 'text',
@ -1590,6 +1598,10 @@
{ amopfamily => 'spgist/poly_ops', amoplefttype => 'polygon', { amopfamily => 'spgist/poly_ops', amoplefttype => 'polygon',
amoprighttype => 'polygon', amopstrategy => '12', amoprighttype => 'polygon', amopstrategy => '12',
amopopr => '|&>(polygon,polygon)', amopmethod => 'spgist' }, amopopr => '|&>(polygon,polygon)', amopmethod => 'spgist' },
{ amopfamily => 'spgist/poly_ops', amoplefttype => 'polygon',
amoprighttype => 'point', amopstrategy => '15',
amopopr => '<->(polygon,point)', amoppurpose => 'o', amopmethod => 'spgist',
amopsortfamily => 'btree/float_ops' },
# GiST inet_ops # GiST inet_ops
{ amopfamily => 'gist/network_ops', amoplefttype => 'inet', { amopfamily => 'gist/network_ops', amoplefttype => 'inet',

View File

@ -95,6 +95,8 @@ extern char *get_constraint_name(Oid conoid);
extern char *get_language_name(Oid langoid, bool missing_ok); extern char *get_language_name(Oid langoid, bool missing_ok);
extern Oid get_opclass_family(Oid opclass); extern Oid get_opclass_family(Oid opclass);
extern Oid get_opclass_input_type(Oid opclass); extern Oid get_opclass_input_type(Oid opclass);
extern bool get_opclass_opfamily_and_input_type(Oid opclass,
Oid *opfamily, Oid *opcintype);
extern RegProcedure get_opcode(Oid opno); extern RegProcedure get_opcode(Oid opno);
extern char *get_opname(Oid opno); extern char *get_opname(Oid opno);
extern Oid get_op_rettype(Oid opno); extern Oid get_op_rettype(Oid opno);
@ -176,6 +178,7 @@ extern void free_attstatsslot(AttStatsSlot *sslot);
extern char *get_namespace_name(Oid nspid); extern char *get_namespace_name(Oid nspid);
extern char *get_namespace_name_or_temp(Oid nspid); extern char *get_namespace_name_or_temp(Oid nspid);
extern Oid get_range_subtype(Oid rangeOid); extern Oid get_range_subtype(Oid rangeOid);
extern Oid get_index_column_opclass(Oid index_oid, int attno);
#define type_is_array(typid) (get_element_type(typid) != InvalidOid) #define type_is_array(typid) (get_element_type(typid) != InvalidOid)
/* type_is_array_domain accepts both plain arrays and domains over arrays */ /* type_is_array_domain accepts both plain arrays and domains over arrays */

View File

@ -83,7 +83,8 @@ select prop,
pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree, pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree,
pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash, pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash,
pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist, pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist,
pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist, pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist_radix,
pg_index_column_has_property('sp_quad_ind'::regclass, 1, prop) as spgist_quad,
pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin, pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin,
pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin
from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
@ -92,18 +93,18 @@ select prop,
'bogus']::text[]) 'bogus']::text[])
with ordinality as u(prop,ord) with ordinality as u(prop,ord)
order by ord; order by ord;
prop | btree | hash | gist | spgist | gin | brin prop | btree | hash | gist | spgist_radix | spgist_quad | gin | brin
--------------------+-------+------+------+--------+-----+------ --------------------+-------+------+------+--------------+-------------+-----+------
asc | t | f | f | f | f | f asc | t | f | f | f | f | f | f
desc | f | f | f | f | f | f desc | f | f | f | f | f | f | f
nulls_first | f | f | f | f | f | f nulls_first | f | f | f | f | f | f | f
nulls_last | t | f | f | f | f | f nulls_last | t | f | f | f | f | f | f
orderable | t | f | f | f | f | f orderable | t | f | f | f | f | f | f
distance_orderable | f | f | t | f | f | f distance_orderable | f | f | t | f | t | f | f
returnable | t | f | f | t | f | f returnable | t | f | f | t | t | f | f
search_array | t | f | f | f | f | f search_array | t | f | f | f | f | f | f
search_nulls | t | f | t | t | f | t search_nulls | t | f | t | t | t | f | t
bogus | | | | | | bogus | | | | | | |
(10 rows) (10 rows)
select prop, select prop,

View File

@ -294,6 +294,15 @@ SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
1 1
(1 row) (1 row)
CREATE TEMP TABLE quad_point_tbl_ord_seq1 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl;
CREATE TEMP TABLE quad_point_tbl_ord_seq2 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
CREATE TEMP TABLE quad_point_tbl_ord_seq3 AS
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM quad_point_tbl WHERE p IS NOT NULL;
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
count count
------- -------
@ -888,6 +897,71 @@ SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
1 1
(1 row) (1 row)
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl;
QUERY PLAN
-----------------------------------------------------------
WindowAgg
-> Index Only Scan using sp_quad_ind on quad_point_tbl
Order By: (p <-> '(0,0)'::point)
(3 rows)
CREATE TEMP TABLE quad_point_tbl_ord_idx1 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl;
SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN quad_point_tbl_ord_idx1 idx
ON seq.n = idx.n
AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
n | dist | p | n | dist | p
---+------+---+---+------+---
(0 rows)
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
QUERY PLAN
-----------------------------------------------------------
WindowAgg
-> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: (p <@ '(1000,1000),(200,200)'::box)
Order By: (p <-> '(0,0)'::point)
(4 rows)
CREATE TEMP TABLE quad_point_tbl_ord_idx2 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN quad_point_tbl_ord_idx2 idx
ON seq.n = idx.n
AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
n | dist | p | n | dist | p
---+------+---+---+------+---
(0 rows)
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM quad_point_tbl WHERE p IS NOT NULL;
QUERY PLAN
-----------------------------------------------------------
WindowAgg
-> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: (p IS NOT NULL)
Order By: (p <-> '(333,400)'::point)
(4 rows)
CREATE TEMP TABLE quad_point_tbl_ord_idx3 AS
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM quad_point_tbl WHERE p IS NOT NULL;
SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN quad_point_tbl_ord_idx3 idx
ON seq.n = idx.n
AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
n | dist | p | n | dist | p
---+------+---+---+------+---
(0 rows)
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
QUERY PLAN QUERY PLAN
@ -993,6 +1067,71 @@ SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
1 1
(1 row) (1 row)
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM kd_point_tbl;
QUERY PLAN
-------------------------------------------------------
WindowAgg
-> Index Only Scan using sp_kd_ind on kd_point_tbl
Order By: (p <-> '(0,0)'::point)
(3 rows)
CREATE TEMP TABLE kd_point_tbl_ord_idx1 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM kd_point_tbl;
SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN kd_point_tbl_ord_idx1 idx
ON seq.n = idx.n AND
(seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
n | dist | p | n | dist | p
---+------+---+---+------+---
(0 rows)
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
QUERY PLAN
---------------------------------------------------------
WindowAgg
-> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: (p <@ '(1000,1000),(200,200)'::box)
Order By: (p <-> '(0,0)'::point)
(4 rows)
CREATE TEMP TABLE kd_point_tbl_ord_idx2 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN kd_point_tbl_ord_idx2 idx
ON seq.n = idx.n AND
(seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
n | dist | p | n | dist | p
---+------+---+---+------+---
(0 rows)
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM kd_point_tbl WHERE p IS NOT NULL;
QUERY PLAN
-------------------------------------------------------
WindowAgg
-> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: (p IS NOT NULL)
Order By: (p <-> '(333,400)'::point)
(4 rows)
CREATE TEMP TABLE kd_point_tbl_ord_idx3 AS
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM kd_point_tbl WHERE p IS NOT NULL;
SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx
ON seq.n = idx.n AND
(seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
n | dist | p | n | dist | p
---+------+---+---+------+---
(0 rows)
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
QUERY PLAN QUERY PLAN

View File

@ -1911,6 +1911,7 @@ ORDER BY 1, 2, 3;
4000 | 12 | <= 4000 | 12 | <=
4000 | 12 | |&> 4000 | 12 | |&>
4000 | 14 | >= 4000 | 14 | >=
4000 | 15 | <->
4000 | 15 | > 4000 | 15 | >
4000 | 16 | @> 4000 | 16 | @>
4000 | 18 | = 4000 | 18 | =
@ -1924,7 +1925,7 @@ ORDER BY 1, 2, 3;
4000 | 26 | >> 4000 | 26 | >>
4000 | 27 | >>= 4000 | 27 | >>=
4000 | 28 | ^@ 4000 | 28 | ^@
(122 rows) (123 rows)
-- Check that all opclass search operators have selectivity estimators. -- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing -- This is not absolutely required, but it seems a reasonable thing

View File

@ -462,6 +462,54 @@ SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(2
1000 1000
(1 row) (1 row)
-- test ORDER BY distance
SET enable_indexscan = ON;
SET enable_bitmapscan = OFF;
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id
FROM quad_poly_tbl;
QUERY PLAN
-----------------------------------------------------------
WindowAgg
-> Index Scan using quad_poly_tbl_idx on quad_poly_tbl
Order By: (p <-> '(123,456)'::point)
(3 rows)
CREATE TEMP TABLE quad_poly_tbl_ord_idx1 AS
SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id
FROM quad_poly_tbl;
SELECT *
FROM quad_poly_tbl_ord_seq1 seq FULL JOIN quad_poly_tbl_ord_idx1 idx
ON seq.n = idx.n AND seq.id = idx.id AND
(seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL)
WHERE seq.id IS NULL OR idx.id IS NULL;
n | dist | id | n | dist | id
---+------+----+---+------+----
(0 rows)
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id
FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))';
QUERY PLAN
---------------------------------------------------------------------------------
WindowAgg
-> Index Scan using quad_poly_tbl_idx on quad_poly_tbl
Index Cond: (p <@ '((300,300),(400,600),(600,500),(700,200))'::polygon)
Order By: (p <-> '(123,456)'::point)
(4 rows)
CREATE TEMP TABLE quad_poly_tbl_ord_idx2 AS
SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id
FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))';
SELECT *
FROM quad_poly_tbl_ord_seq2 seq FULL JOIN quad_poly_tbl_ord_idx2 idx
ON seq.n = idx.n AND seq.id = idx.id AND
(seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL)
WHERE seq.id IS NULL OR idx.id IS NULL;
n | dist | id | n | dist | id
---+------+----+---+------+----
(0 rows)
RESET enable_seqscan; RESET enable_seqscan;
RESET enable_indexscan; RESET enable_indexscan;
RESET enable_bitmapscan; RESET enable_bitmapscan;

View File

@ -40,7 +40,8 @@ select prop,
pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree, pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree,
pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash, pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash,
pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist, pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist,
pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist, pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist_radix,
pg_index_column_has_property('sp_quad_ind'::regclass, 1, prop) as spgist_quad,
pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin, pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin,
pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin
from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',

View File

@ -198,6 +198,18 @@ SELECT count(*) FROM quad_point_tbl WHERE p >^ '(5000, 4000)';
SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
CREATE TEMP TABLE quad_point_tbl_ord_seq1 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl;
CREATE TEMP TABLE quad_point_tbl_ord_seq2 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
CREATE TEMP TABLE quad_point_tbl_ord_seq3 AS
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM quad_point_tbl WHERE p IS NOT NULL;
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde'; SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde';
@ -363,6 +375,39 @@ EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl;
CREATE TEMP TABLE quad_point_tbl_ord_idx1 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl;
SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN quad_point_tbl_ord_idx1 idx
ON seq.n = idx.n
AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
CREATE TEMP TABLE quad_point_tbl_ord_idx2 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN quad_point_tbl_ord_idx2 idx
ON seq.n = idx.n
AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM quad_point_tbl WHERE p IS NOT NULL;
CREATE TEMP TABLE quad_point_tbl_ord_idx3 AS
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM quad_point_tbl WHERE p IS NOT NULL;
SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN quad_point_tbl_ord_idx3 idx
ON seq.n = idx.n
AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
@ -391,6 +436,39 @@ EXPLAIN (COSTS OFF)
SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)'; SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)'; SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM kd_point_tbl;
CREATE TEMP TABLE kd_point_tbl_ord_idx1 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM kd_point_tbl;
SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN kd_point_tbl_ord_idx1 idx
ON seq.n = idx.n AND
(seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
CREATE TEMP TABLE kd_point_tbl_ord_idx2 AS
SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p
FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN kd_point_tbl_ord_idx2 idx
ON seq.n = idx.n AND
(seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM kd_point_tbl WHERE p IS NOT NULL;
CREATE TEMP TABLE kd_point_tbl_ord_idx3 AS
SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p
FROM kd_point_tbl WHERE p IS NOT NULL;
SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx
ON seq.n = idx.n AND
(seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL)
WHERE seq.n IS NULL OR idx.n IS NULL;
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';

View File

@ -206,6 +206,39 @@ EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))'; SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))';
SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))'; SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))';
-- test ORDER BY distance
SET enable_indexscan = ON;
SET enable_bitmapscan = OFF;
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id
FROM quad_poly_tbl;
CREATE TEMP TABLE quad_poly_tbl_ord_idx1 AS
SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id
FROM quad_poly_tbl;
SELECT *
FROM quad_poly_tbl_ord_seq1 seq FULL JOIN quad_poly_tbl_ord_idx1 idx
ON seq.n = idx.n AND seq.id = idx.id AND
(seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL)
WHERE seq.id IS NULL OR idx.id IS NULL;
EXPLAIN (COSTS OFF)
SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id
FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))';
CREATE TEMP TABLE quad_poly_tbl_ord_idx2 AS
SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id
FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))';
SELECT *
FROM quad_poly_tbl_ord_seq2 seq FULL JOIN quad_poly_tbl_ord_idx2 idx
ON seq.n = idx.n AND seq.id = idx.id AND
(seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL)
WHERE seq.id IS NULL OR idx.id IS NULL;
RESET enable_seqscan; RESET enable_seqscan;
RESET enable_indexscan; RESET enable_indexscan;
RESET enable_bitmapscan; RESET enable_bitmapscan;