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"/>.
|
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>
|
||||||
|
@ -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>>^</literal>
|
<literal>>^</literal>
|
||||||
<literal>~=</literal>
|
<literal>~=</literal>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<literal><-></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>>^</literal>
|
<literal>>^</literal>
|
||||||
<literal>~=</literal>
|
<literal>~=</literal>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<literal><-></literal>
|
||||||
|
</entry>
|
||||||
</row>
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry><literal>range_ops</literal></entry>
|
<entry><literal>range_ops</literal></entry>
|
||||||
@ -111,6 +118,8 @@
|
|||||||
<literal>>></literal>
|
<literal>>></literal>
|
||||||
<literal>@></literal>
|
<literal>@></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>|>></literal>
|
<literal>|>></literal>
|
||||||
<literal>|&></literal>
|
<literal>|&></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>|>></literal>
|
<literal>|>></literal>
|
||||||
<literal>|&></literal>
|
<literal>|&></literal>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<literal><-></literal>
|
||||||
|
</entry>
|
||||||
</row>
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry><literal>text_ops</literal></entry>
|
<entry><literal>text_ops</literal></entry>
|
||||||
@ -163,6 +177,8 @@
|
|||||||
<literal>~>~</literal>
|
<literal>~>~</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><=</literal>
|
<literal><=</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><-></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 */
|
||||||
@ -761,6 +799,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;
|
||||||
</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>
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 */
|
||||||
|
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/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
@ -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;
|
||||||
|
}
|
||||||
|
@ -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,9 +201,12 @@ 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))
|
{
|
||||||
|
/* ... 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,
|
ereport(INFO,
|
||||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||||
@ -211,9 +215,12 @@ spgvalidate(Oid opclassoid)
|
|||||||
format_operator(oprform->amopopr))));
|
format_operator(oprform->amopopr))));
|
||||||
result = false;
|
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))
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
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;
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 */
|
||||||
|
@ -53,6 +53,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 201809181
|
#define CATALOG_VERSION_NO 201809191
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -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',
|
||||||
|
@ -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 */
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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',
|
||||||
|
@ -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';
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user