mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-03-13 19:57:53 +08:00
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:
parent
d0cfc3d6a4
commit
2a6368343f
@ -281,6 +281,13 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
|
||||
For more information see <xref linkend="spgist"/>.
|
||||
</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>
|
||||
<indexterm>
|
||||
<primary>index</primary>
|
||||
|
@ -64,12 +64,13 @@
|
||||
|
||||
<table id="spgist-builtin-opclasses-table">
|
||||
<title>Built-in <acronym>SP-GiST</acronym> Operator Classes</title>
|
||||
<tgroup cols="3">
|
||||
<tgroup cols="4">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Name</entry>
|
||||
<entry>Indexed Data Type</entry>
|
||||
<entry>Indexable Operators</entry>
|
||||
<entry>Ordering Operators</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -84,6 +85,9 @@
|
||||
<literal>>^</literal>
|
||||
<literal>~=</literal>
|
||||
</entry>
|
||||
<entry>
|
||||
<literal><-></literal>
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>quad_point_ops</literal></entry>
|
||||
@ -96,6 +100,9 @@
|
||||
<literal>>^</literal>
|
||||
<literal>~=</literal>
|
||||
</entry>
|
||||
<entry>
|
||||
<literal><-></literal>
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>range_ops</literal></entry>
|
||||
@ -111,6 +118,8 @@
|
||||
<literal>>></literal>
|
||||
<literal>@></literal>
|
||||
</entry>
|
||||
<entry>
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>box_ops</literal></entry>
|
||||
@ -129,6 +138,8 @@
|
||||
<literal>|>></literal>
|
||||
<literal>|&></literal>
|
||||
</entry>
|
||||
<entry>
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>poly_ops</literal></entry>
|
||||
@ -147,6 +158,9 @@
|
||||
<literal>|>></literal>
|
||||
<literal>|&></literal>
|
||||
</entry>
|
||||
<entry>
|
||||
<literal><-></literal>
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>text_ops</literal></entry>
|
||||
@ -163,6 +177,8 @@
|
||||
<literal>~>~</literal>
|
||||
<literal>^@</literal>
|
||||
</entry>
|
||||
<entry>
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>inet_ops</literal></entry>
|
||||
@ -180,6 +196,8 @@
|
||||
<literal><=</literal>
|
||||
<literal>=</literal>
|
||||
</entry>
|
||||
<entry>
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
@ -191,6 +209,12 @@
|
||||
supports the same operators but uses a different index data structure which
|
||||
may offer better performance in some applications.
|
||||
</para>
|
||||
<para>
|
||||
The <literal>quad_point_ops</literal>, <literal>kd_point_ops</literal> and
|
||||
<literal>poly_ops</literal> operator classes support the <literal><-></literal>
|
||||
ordering operator, which enables the k-nearest neighbor (<literal>k-NN</literal>)
|
||||
search over indexed point or polygon datasets.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
@ -630,7 +654,10 @@ CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ...
|
||||
typedef struct spgInnerConsistentIn
|
||||
{
|
||||
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 */
|
||||
void *traversalValue; /* opclass-specific traverse value */
|
||||
@ -653,6 +680,7 @@ typedef struct spgInnerConsistentOut
|
||||
int *levelAdds; /* increment level by this much for each */
|
||||
Datum *reconstructedValues; /* associated reconstructed values */
|
||||
void **traversalValues; /* opclass-specific traverse values */
|
||||
double **distances; /* associated distances */
|
||||
} spgInnerConsistentOut;
|
||||
</programlisting>
|
||||
|
||||
@ -667,6 +695,8 @@ typedef struct spgInnerConsistentOut
|
||||
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
|
||||
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
|
||||
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
|
||||
@ -709,6 +739,10 @@ typedef struct spgInnerConsistentOut
|
||||
of <structname>spgConfigOut</structname>.<structfield>leafType</structfield> type
|
||||
reconstructed for each child node to be visited; otherwise, leave
|
||||
<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
|
||||
(<quote>traverse values</quote>) to lower levels of the tree search,
|
||||
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
|
||||
responsible for palloc'ing the
|
||||
<structfield>nodeNumbers</structfield>, <structfield>levelAdds</structfield>,
|
||||
<structfield>distances</structfield>,
|
||||
<structfield>reconstructedValues</structfield>, and
|
||||
<structfield>traversalValues</structfield> arrays in the current memory context.
|
||||
However, any output traverse values pointed to by
|
||||
@ -747,7 +782,10 @@ CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ...
|
||||
typedef struct spgLeafConsistentIn
|
||||
{
|
||||
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 */
|
||||
void *traversalValue; /* opclass-specific traverse value */
|
||||
@ -759,8 +797,10 @@ typedef struct spgLeafConsistentIn
|
||||
|
||||
typedef struct spgLeafConsistentOut
|
||||
{
|
||||
Datum leafValue; /* reconstructed original data, if any */
|
||||
bool recheck; /* set true if operator must be rechecked */
|
||||
Datum leafValue; /* reconstructed original data, if any */
|
||||
bool recheck; /* set true if operator must be rechecked */
|
||||
bool recheckDistances; /* set true if distances must be rechecked */
|
||||
double *distances; /* associated distances */
|
||||
} spgLeafConsistentOut;
|
||||
</programlisting>
|
||||
|
||||
@ -775,6 +815,8 @@ typedef struct spgLeafConsistentOut
|
||||
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
|
||||
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
|
||||
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
|
||||
@ -803,6 +845,12 @@ typedef struct spgLeafConsistentOut
|
||||
<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
|
||||
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>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
@ -1242,7 +1242,7 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
|
||||
<title>Ordering Operators</title>
|
||||
|
||||
<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
|
||||
are <firstterm>search operators</firstterm>. A search operator is one for which
|
||||
the index can be searched to find all rows satisfying
|
||||
|
@ -14,9 +14,9 @@
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/genam.h"
|
||||
#include "access/gist_private.h"
|
||||
#include "access/relscan.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "miscadmin.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "storage/predicate.h"
|
||||
@ -543,7 +543,6 @@ getNextNearest(IndexScanDesc scan)
|
||||
{
|
||||
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
||||
bool res = false;
|
||||
int i;
|
||||
|
||||
if (scan->xs_hitup)
|
||||
{
|
||||
@ -564,45 +563,10 @@ getNextNearest(IndexScanDesc scan)
|
||||
/* found a heap item at currently minimal distance */
|
||||
scan->xs_ctup.t_self = item->data.heap.heapPtr;
|
||||
scan->xs_recheck = item->data.heap.recheck;
|
||||
scan->xs_recheckorderby = item->data.heap.recheckDistances;
|
||||
for (i = 0; i < scan->numberOfOrderBys; i++)
|
||||
{
|
||||
if (so->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(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;
|
||||
}
|
||||
}
|
||||
|
||||
index_store_float8_orderby_distances(scan, so->orderByTypes,
|
||||
item->distances,
|
||||
item->data.heap.recheckDistances);
|
||||
|
||||
/* in an index-only scan, also return the reconstructed tuple. */
|
||||
if (scan->xs_want_itup)
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/float.h"
|
||||
#include "utils/syscache.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
||||
|
||||
/*
|
||||
@ -871,12 +872,6 @@ gistproperty(Oid index_oid, int attno,
|
||||
IndexAMProperty prop, const char *propname,
|
||||
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,
|
||||
opfamily,
|
||||
opcintype;
|
||||
@ -910,41 +905,19 @@ gistproperty(Oid index_oid, int attno,
|
||||
}
|
||||
|
||||
/* First we need to know the column's opclass. */
|
||||
|
||||
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
opclass = get_index_column_opclass(index_oid, attno);
|
||||
if (!OidIsValid(opclass))
|
||||
{
|
||||
*isnull = 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. */
|
||||
|
||||
tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
|
||||
{
|
||||
*isnull = 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. */
|
||||
|
||||
@ -967,6 +940,8 @@ gistproperty(Oid index_oid, int attno,
|
||||
Int16GetDatum(GIST_COMPRESS_PROC));
|
||||
}
|
||||
|
||||
*isnull = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -74,6 +74,7 @@
|
||||
#include "access/transam.h"
|
||||
#include "access/xlog.h"
|
||||
#include "catalog/index.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "pgstat.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
@ -897,3 +898,72 @@ index_getprocinfo(Relation irel,
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global
|
||||
|
||||
OBJS = spgutils.o spginsert.o spgscan.o spgvacuum.o spgvalidate.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
|
||||
|
@ -41,7 +41,11 @@ contain exactly one inner tuple.
|
||||
|
||||
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
|
||||
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
|
||||
just one node to descend to from each inner tuple. Insertion might also have
|
||||
|
@ -16,9 +16,11 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/spgist.h"
|
||||
#include "access/spgist_private.h"
|
||||
#include "access/stratnum.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/float.h"
|
||||
#include "utils/geo_decls.h"
|
||||
|
||||
|
||||
@ -162,6 +164,7 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS)
|
||||
double coord;
|
||||
int which;
|
||||
int i;
|
||||
BOX bboxes[2];
|
||||
|
||||
Assert(in->hasPrefix);
|
||||
coord = DatumGetFloat8(in->prefixDatum);
|
||||
@ -248,12 +251,87 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
/* We must descend into the children identified by which */
|
||||
out->nodeNumbers = (int *) palloc(sizeof(int) * 2);
|
||||
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++)
|
||||
{
|
||||
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 */
|
||||
|
88
src/backend/access/spgist/spgproc.c
Normal file
88
src/backend/access/spgist/spgproc.c
Normal 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;
|
||||
}
|
@ -17,8 +17,10 @@
|
||||
|
||||
#include "access/spgist.h"
|
||||
#include "access/stratnum.h"
|
||||
#include "access/spgist_private.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/float.h"
|
||||
#include "utils/geo_decls.h"
|
||||
|
||||
|
||||
@ -77,6 +79,38 @@ getQuadrant(Point *centroid, Point *tst)
|
||||
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
|
||||
spg_quad_choose(PG_FUNCTION_ARGS)
|
||||
@ -196,19 +230,68 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
|
||||
spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
|
||||
spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
|
||||
Point *centroid;
|
||||
BOX infbbox;
|
||||
BOX *bbox = NULL;
|
||||
int which;
|
||||
int i;
|
||||
|
||||
Assert(in->hasPrefix);
|
||||
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)
|
||||
{
|
||||
/* 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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@ -286,13 +369,37 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
|
||||
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 */
|
||||
out->nodeNumbers = (int *) palloc(sizeof(int) * 4);
|
||||
out->nNodes = 0;
|
||||
|
||||
for (i = 1; i <= 4; 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();
|
||||
@ -356,5 +463,11 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
|
||||
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);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,17 +15,26 @@
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/amvalidate.h"
|
||||
#include "access/htup_details.h"
|
||||
#include "access/reloptions.h"
|
||||
#include "access/spgist_private.h"
|
||||
#include "access/transam.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/pg_amop.h"
|
||||
#include "optimizer/paths.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/indexfsm.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/catcache.h"
|
||||
#include "utils/index_selfuncs.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
|
||||
@ -39,7 +48,7 @@ spghandler(PG_FUNCTION_ARGS)
|
||||
amroutine->amstrategies = 0;
|
||||
amroutine->amsupport = SPGISTNProc;
|
||||
amroutine->amcanorder = false;
|
||||
amroutine->amcanorderbyop = false;
|
||||
amroutine->amcanorderbyop = true;
|
||||
amroutine->amcanbackward = false;
|
||||
amroutine->amcanunique = false;
|
||||
amroutine->amcanmulticol = false;
|
||||
@ -61,7 +70,7 @@ spghandler(PG_FUNCTION_ARGS)
|
||||
amroutine->amcanreturn = spgcanreturn;
|
||||
amroutine->amcostestimate = spgcostestimate;
|
||||
amroutine->amoptions = spgoptions;
|
||||
amroutine->amproperty = NULL;
|
||||
amroutine->amproperty = spgproperty;
|
||||
amroutine->amvalidate = spgvalidate;
|
||||
amroutine->ambeginscan = spgbeginscan;
|
||||
amroutine->amrescan = spgrescan;
|
||||
@ -949,3 +958,82 @@ SpGistPageAddNewItem(SpGistState *state, Page page, Item item, Size size,
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -187,6 +187,7 @@ spgvalidate(Oid opclassoid)
|
||||
{
|
||||
HeapTuple oprtup = &oprlist->members[i]->tuple;
|
||||
Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
|
||||
Oid op_rettype;
|
||||
|
||||
/* TODO: Check that only allowed strategy numbers exist */
|
||||
if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63)
|
||||
@ -200,20 +201,26 @@ spgvalidate(Oid opclassoid)
|
||||
result = false;
|
||||
}
|
||||
|
||||
/* spgist doesn't support ORDER BY operators */
|
||||
if (oprform->amoppurpose != AMOP_SEARCH ||
|
||||
OidIsValid(oprform->amopsortfamily))
|
||||
/* spgist 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, "spgist",
|
||||
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, "spgist",
|
||||
format_operator(oprform->amopopr))));
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
op_rettype = BOOLOID;
|
||||
|
||||
/* 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->amoprighttype))
|
||||
{
|
||||
|
@ -74,9 +74,11 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/spgist.h"
|
||||
#include "access/spgist_private.h"
|
||||
#include "access/stratnum.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "utils/float.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/fmgrprotos.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);
|
||||
}
|
||||
|
||||
/* 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
|
||||
*/
|
||||
@ -534,17 +561,6 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
|
||||
RangeBox *centroid,
|
||||
**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 have just begun to walk the tree.
|
||||
@ -554,6 +570,40 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
|
||||
else
|
||||
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
|
||||
* following operations.
|
||||
@ -571,6 +621,8 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
|
||||
out->nNodes = 0;
|
||||
out->nodeNumbers = (int *) palloc(sizeof(int) * 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
|
||||
@ -648,6 +700,22 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS)
|
||||
{
|
||||
out->traversalValues[out->nNodes] = next_rect_box;
|
||||
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++;
|
||||
}
|
||||
else
|
||||
@ -763,6 +831,17 @@ spg_box_quad_leaf_consistent(PG_FUNCTION_ARGS)
|
||||
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);
|
||||
}
|
||||
|
||||
|
68
src/backend/utils/cache/lsyscache.c
vendored
68
src/backend/utils/cache/lsyscache.c
vendored
@ -1067,6 +1067,32 @@ get_opclass_input_type(Oid opclass)
|
||||
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 ---------- */
|
||||
|
||||
/*
|
||||
@ -3106,3 +3132,45 @@ get_range_subtype(Oid rangeOid)
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
@ -174,6 +174,9 @@ extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
|
||||
uint16 procnum);
|
||||
extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
|
||||
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)
|
||||
|
@ -136,7 +136,10 @@ typedef struct spgPickSplitOut
|
||||
typedef struct spgInnerConsistentIn
|
||||
{
|
||||
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 */
|
||||
void *traversalValue; /* opclass-specific traverse value */
|
||||
@ -159,6 +162,7 @@ typedef struct spgInnerConsistentOut
|
||||
int *levelAdds; /* increment level by this much for each */
|
||||
Datum *reconstructedValues; /* associated reconstructed values */
|
||||
void **traversalValues; /* opclass-specific traverse values */
|
||||
double **distances; /* associated distances */
|
||||
} spgInnerConsistentOut;
|
||||
|
||||
/*
|
||||
@ -167,7 +171,10 @@ typedef struct spgInnerConsistentOut
|
||||
typedef struct spgLeafConsistentIn
|
||||
{
|
||||
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 */
|
||||
void *traversalValue; /* opclass-specific traverse value */
|
||||
@ -181,6 +188,8 @@ typedef struct spgLeafConsistentOut
|
||||
{
|
||||
Datum leafValue; /* reconstructed original data, if any */
|
||||
bool recheck; /* set true if operator must be rechecked */
|
||||
bool recheckDistances; /* set true if distances must be rechecked */
|
||||
double *distances; /* associated distances */
|
||||
} spgLeafConsistentOut;
|
||||
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "access/spgist.h"
|
||||
#include "nodes/tidbitmap.h"
|
||||
#include "storage/buf.h"
|
||||
#include "utils/geo_decls.h"
|
||||
#include "utils/relcache.h"
|
||||
|
||||
|
||||
@ -130,14 +131,35 @@ typedef struct SpGistState
|
||||
bool isBuild; /* true if doing index build */
|
||||
} 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
|
||||
*/
|
||||
typedef struct SpGistScanOpaqueData
|
||||
{
|
||||
SpGistState state; /* see above */
|
||||
pairingheap *scanQueue; /* queue of to be visited items */
|
||||
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 */
|
||||
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) */
|
||||
int numberOfKeys; /* number of index qualifier conditions */
|
||||
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 */
|
||||
List *scanStack; /* List of ScanStackEntrys */
|
||||
/* Opclass defined functions: */
|
||||
FmgrInfo innerConsistentFn;
|
||||
FmgrInfo leafConsistentFn;
|
||||
|
||||
/* Pre-allocated workspace arrays: */
|
||||
double *zeroDistances;
|
||||
double *infDistances;
|
||||
|
||||
/* These fields are only used in amgetbitmap scans: */
|
||||
TIDBitmap *tbm; /* bitmap being filled */
|
||||
@ -161,7 +192,10 @@ typedef struct SpGistScanOpaqueData
|
||||
int iPtr; /* index for scanning through same */
|
||||
ItemPointerData heapPtrs[MaxIndexTuplesPerPage]; /* TIDs from cur page */
|
||||
bool recheck[MaxIndexTuplesPerPage]; /* their recheck flags */
|
||||
bool recheckDistances[MaxIndexTuplesPerPage]; /* distance recheck
|
||||
* flags */
|
||||
HeapTuple reconTups[MaxIndexTuplesPerPage]; /* reconstructed tuples */
|
||||
double *distances[MaxIndexTuplesPerPage]; /* distances (for recheck) */
|
||||
|
||||
/*
|
||||
* Note: using MaxIndexTuplesPerPage above is a bit hokey since
|
||||
@ -410,6 +444,9 @@ extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page,
|
||||
Item item, Size size,
|
||||
OffsetNumber *startOffset,
|
||||
bool errorOK);
|
||||
extern bool spgproperty(Oid index_oid, int attno,
|
||||
IndexAMProperty prop, const char *propname,
|
||||
bool *res, bool *isnull);
|
||||
|
||||
/* spgdoinsert.c */
|
||||
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,
|
||||
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 */
|
||||
|
@ -53,6 +53,6 @@
|
||||
*/
|
||||
|
||||
/* yyyymmddN */
|
||||
#define CATALOG_VERSION_NO 201809181
|
||||
#define CATALOG_VERSION_NO 201809191
|
||||
|
||||
#endif
|
||||
|
@ -1401,6 +1401,10 @@
|
||||
{ amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point',
|
||||
amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)',
|
||||
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
|
||||
{ amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point',
|
||||
@ -1421,6 +1425,10 @@
|
||||
{ amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point',
|
||||
amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)',
|
||||
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
|
||||
{ amopfamily => 'spgist/text_ops', amoplefttype => 'text',
|
||||
@ -1590,6 +1598,10 @@
|
||||
{ amopfamily => 'spgist/poly_ops', amoplefttype => 'polygon',
|
||||
amoprighttype => 'polygon', amopstrategy => '12',
|
||||
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
|
||||
{ amopfamily => 'gist/network_ops', amoplefttype => 'inet',
|
||||
|
@ -95,6 +95,8 @@ extern char *get_constraint_name(Oid conoid);
|
||||
extern char *get_language_name(Oid langoid, bool missing_ok);
|
||||
extern Oid get_opclass_family(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 char *get_opname(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_or_temp(Oid nspid);
|
||||
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)
|
||||
/* type_is_array_domain accepts both plain arrays and domains over arrays */
|
||||
|
@ -83,7 +83,8 @@ select prop,
|
||||
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('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('brinidx'::regclass, 1, prop) as brin
|
||||
from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
|
||||
@ -92,18 +93,18 @@ select prop,
|
||||
'bogus']::text[])
|
||||
with ordinality as u(prop,ord)
|
||||
order by ord;
|
||||
prop | btree | hash | gist | spgist | gin | brin
|
||||
--------------------+-------+------+------+--------+-----+------
|
||||
asc | t | f | f | f | f | f
|
||||
desc | f | f | f | f | f | f
|
||||
nulls_first | f | f | f | f | f | f
|
||||
nulls_last | t | f | f | f | f | f
|
||||
orderable | t | f | f | f | f | f
|
||||
distance_orderable | f | f | t | f | f | f
|
||||
returnable | t | f | f | t | f | f
|
||||
search_array | t | f | f | f | f | f
|
||||
search_nulls | t | f | t | t | f | t
|
||||
bogus | | | | | |
|
||||
prop | btree | hash | gist | spgist_radix | spgist_quad | gin | brin
|
||||
--------------------+-------+------+------+--------------+-------------+-----+------
|
||||
asc | t | f | f | f | f | f | f
|
||||
desc | f | f | f | f | f | f | f
|
||||
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
|
||||
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
|
||||
bogus | | | | | | |
|
||||
(10 rows)
|
||||
|
||||
select prop,
|
||||
|
@ -294,6 +294,15 @@ SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
|
||||
1
|
||||
(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';
|
||||
count
|
||||
-------
|
||||
@ -888,6 +897,71 @@ SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
|
||||
1
|
||||
(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)
|
||||
SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
|
||||
QUERY PLAN
|
||||
@ -993,6 +1067,71 @@ SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
|
||||
1
|
||||
(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)
|
||||
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
|
||||
QUERY PLAN
|
||||
|
@ -1911,6 +1911,7 @@ ORDER BY 1, 2, 3;
|
||||
4000 | 12 | <=
|
||||
4000 | 12 | |&>
|
||||
4000 | 14 | >=
|
||||
4000 | 15 | <->
|
||||
4000 | 15 | >
|
||||
4000 | 16 | @>
|
||||
4000 | 18 | =
|
||||
@ -1924,7 +1925,7 @@ ORDER BY 1, 2, 3;
|
||||
4000 | 26 | >>
|
||||
4000 | 27 | >>=
|
||||
4000 | 28 | ^@
|
||||
(122 rows)
|
||||
(123 rows)
|
||||
|
||||
-- Check that all opclass search operators have selectivity estimators.
|
||||
-- This is not absolutely required, but it seems a reasonable thing
|
||||
|
@ -462,6 +462,54 @@ SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(2
|
||||
1000
|
||||
(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_indexscan;
|
||||
RESET enable_bitmapscan;
|
||||
|
@ -40,7 +40,8 @@ select prop,
|
||||
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('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('brinidx'::regclass, 1, prop) as brin
|
||||
from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last',
|
||||
|
@ -198,6 +198,18 @@ SELECT count(*) FROM quad_point_tbl WHERE p >^ '(5000, 4000)';
|
||||
|
||||
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 = '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)';
|
||||
|
||||
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)
|
||||
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)';
|
||||
|
||||
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)
|
||||
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
|
||||
SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef';
|
||||
|
@ -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))';
|
||||
|
||||
-- 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_indexscan;
|
||||
RESET enable_bitmapscan;
|
||||
|
Loading…
x
Reference in New Issue
Block a user