mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-31 10:30:33 +03: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:
		| @@ -281,6 +281,13 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; | ||||
|    For more information see <xref linkend="spgist"/>. | ||||
|   </para> | ||||
|  | ||||
|   <para> | ||||
|    Like GiST, SP-GiST supports <quote>nearest-neighbor</quote> searches. | ||||
|    For SP-GiST operator classes that support distance ordering, the  | ||||
|    corresponding operator is specified in the <quote>Ordering Operators</quote> | ||||
|    column in <xref linkend="spgist-builtin-opclasses-table"/>. | ||||
|   </para> | ||||
|  | ||||
|   <para> | ||||
|    <indexterm> | ||||
|     <primary>index</primary> | ||||
|   | ||||
| @@ -64,12 +64,13 @@ | ||||
|  | ||||
|   <table id="spgist-builtin-opclasses-table"> | ||||
|    <title>Built-in <acronym>SP-GiST</acronym> Operator Classes</title> | ||||
|    <tgroup cols="3"> | ||||
|    <tgroup cols="4"> | ||||
|     <thead> | ||||
|      <row> | ||||
|       <entry>Name</entry> | ||||
|       <entry>Indexed Data Type</entry> | ||||
|       <entry>Indexable Operators</entry> | ||||
|       <entry>Ordering Operators</entry> | ||||
|      </row> | ||||
|     </thead> | ||||
|     <tbody> | ||||
| @@ -84,6 +85,9 @@ | ||||
|        <literal>>^</literal> | ||||
|        <literal>~=</literal> | ||||
|       </entry> | ||||
|       <entry> | ||||
|        <literal><-></literal> | ||||
|       </entry> | ||||
|      </row> | ||||
|      <row> | ||||
|       <entry><literal>quad_point_ops</literal></entry> | ||||
| @@ -96,6 +100,9 @@ | ||||
|        <literal>>^</literal> | ||||
|        <literal>~=</literal> | ||||
|       </entry> | ||||
|       <entry> | ||||
|        <literal><-></literal> | ||||
|       </entry> | ||||
|      </row> | ||||
|      <row> | ||||
|       <entry><literal>range_ops</literal></entry> | ||||
| @@ -111,6 +118,8 @@ | ||||
|        <literal>>></literal> | ||||
|        <literal>@></literal> | ||||
|       </entry> | ||||
|       <entry> | ||||
|       </entry> | ||||
|      </row> | ||||
|      <row> | ||||
|       <entry><literal>box_ops</literal></entry> | ||||
| @@ -129,6 +138,8 @@ | ||||
|        <literal>|>></literal> | ||||
|        <literal>|&></literal> | ||||
|       </entry> | ||||
|       <entry> | ||||
|       </entry> | ||||
|      </row> | ||||
|      <row> | ||||
|       <entry><literal>poly_ops</literal></entry> | ||||
| @@ -147,6 +158,9 @@ | ||||
|        <literal>|>></literal> | ||||
|        <literal>|&></literal> | ||||
|       </entry> | ||||
|       <entry> | ||||
|         <literal><-></literal> | ||||
|       </entry>       | ||||
|      </row> | ||||
|      <row> | ||||
|       <entry><literal>text_ops</literal></entry> | ||||
| @@ -163,6 +177,8 @@ | ||||
|        <literal>~>~</literal> | ||||
|        <literal>^@</literal> | ||||
|       </entry> | ||||
|       <entry> | ||||
|       </entry> | ||||
|      </row> | ||||
|      <row> | ||||
|       <entry><literal>inet_ops</literal></entry> | ||||
| @@ -180,6 +196,8 @@ | ||||
|        <literal><=</literal> | ||||
|        <literal>=</literal> | ||||
|       </entry> | ||||
|       <entry> | ||||
|       </entry> | ||||
|      </row> | ||||
|     </tbody> | ||||
|    </tgroup> | ||||
| @@ -191,6 +209,12 @@ | ||||
|   supports the same operators but uses a different index data structure which | ||||
|   may offer better performance in some applications. | ||||
|  </para> | ||||
|  <para> | ||||
|   The <literal>quad_point_ops</literal>, <literal>kd_point_ops</literal> and | ||||
|   <literal>poly_ops</literal> operator classes support the <literal><-></literal> | ||||
|   ordering operator, which enables the k-nearest neighbor (<literal>k-NN</literal>) | ||||
|   search over indexed point or polygon datasets. | ||||
|  </para> | ||||
|  | ||||
| </sect1> | ||||
|  | ||||
| @@ -630,7 +654,10 @@ CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ... | ||||
| typedef struct spgInnerConsistentIn | ||||
| { | ||||
|     ScanKey     scankeys;       /* array of operators and comparison values */ | ||||
|     int         nkeys;          /* length of array */ | ||||
|     ScanKey     orderbys;       /* array of ordering operators and comparison | ||||
|                                  * values */ | ||||
|     int         nkeys;          /* length of scankeys array */ | ||||
|     int         norderbys;      /* length of orderbys array */ | ||||
|  | ||||
|     Datum       reconstructedValue;     /* value reconstructed at parent */ | ||||
|     void       *traversalValue; /* opclass-specific traverse value */ | ||||
| @@ -653,6 +680,7 @@ typedef struct spgInnerConsistentOut | ||||
|     int        *levelAdds;      /* increment level by this much for each */ | ||||
|     Datum      *reconstructedValues;    /* associated reconstructed values */ | ||||
|     void      **traversalValues;        /* opclass-specific traverse values */ | ||||
|     double    **distances;              /* associated distances */ | ||||
| } spgInnerConsistentOut; | ||||
| </programlisting> | ||||
|  | ||||
| @@ -667,6 +695,8 @@ typedef struct spgInnerConsistentOut | ||||
|        In particular it is not necessary to check <structfield>sk_flags</structfield> to | ||||
|        see if the comparison value is NULL, because the SP-GiST core code | ||||
|        will filter out such conditions. | ||||
|        The array <structfield>orderbys</structfield>, of length <structfield>norderbys</structfield>, | ||||
|        describes ordering operators (if any) in the same manner. | ||||
|        <structfield>reconstructedValue</structfield> is the value reconstructed for the | ||||
|        parent tuple; it is <literal>(Datum) 0</literal> at the root level or if the | ||||
|        <function>inner_consistent</function> function did not provide a value at the | ||||
| @@ -709,6 +739,10 @@ typedef struct spgInnerConsistentOut | ||||
|        of <structname>spgConfigOut</structname>.<structfield>leafType</structfield> type | ||||
|        reconstructed for each child node to be visited; otherwise, leave | ||||
|        <structfield>reconstructedValues</structfield> as NULL. | ||||
|        If ordered search is performed, set <structfield>distances</structfield> | ||||
|        to an array of distance values according to <structfield>orderbys</structfield> | ||||
|        array (nodes with lowest distances will be processed first).  Leave it | ||||
|        NULL otherwise. | ||||
|        If it is desired to pass down additional out-of-band information | ||||
|        (<quote>traverse values</quote>) to lower levels of the tree search, | ||||
|        set <structfield>traversalValues</structfield> to an array of the appropriate | ||||
| @@ -717,6 +751,7 @@ typedef struct spgInnerConsistentOut | ||||
|        Note that the <function>inner_consistent</function> function is | ||||
|        responsible for palloc'ing the | ||||
|        <structfield>nodeNumbers</structfield>, <structfield>levelAdds</structfield>, | ||||
|        <structfield>distances</structfield>, | ||||
|        <structfield>reconstructedValues</structfield>, and | ||||
|        <structfield>traversalValues</structfield> arrays in the current memory context. | ||||
|        However, any output traverse values pointed to by | ||||
| @@ -747,7 +782,10 @@ CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ... | ||||
| typedef struct spgLeafConsistentIn | ||||
| { | ||||
|     ScanKey     scankeys;       /* array of operators and comparison values */ | ||||
|     int         nkeys;          /* length of array */ | ||||
|     ScanKey     orderbys;       /* array of ordering operators and comparison | ||||
|                                  * values */ | ||||
|     int         nkeys;          /* length of scankeys array */ | ||||
|     int         norderbys;      /* length of orderbys array */ | ||||
|  | ||||
|     Datum       reconstructedValue;     /* value reconstructed at parent */ | ||||
|     void       *traversalValue; /* opclass-specific traverse value */ | ||||
| @@ -759,8 +797,10 @@ typedef struct spgLeafConsistentIn | ||||
|  | ||||
| typedef struct spgLeafConsistentOut | ||||
| { | ||||
|     Datum       leafValue;      /* reconstructed original data, if any */ | ||||
|     bool        recheck;        /* set true if operator must be rechecked */ | ||||
|     Datum       leafValue;        /* reconstructed original data, if any */ | ||||
|     bool        recheck;          /* set true if operator must be rechecked */ | ||||
|     bool        recheckDistances; /* set true if distances must be rechecked */ | ||||
|     double     *distances;        /* associated distances */ | ||||
| } spgLeafConsistentOut; | ||||
| </programlisting> | ||||
|  | ||||
| @@ -775,6 +815,8 @@ typedef struct spgLeafConsistentOut | ||||
|        In particular it is not necessary to check <structfield>sk_flags</structfield> to | ||||
|        see if the comparison value is NULL, because the SP-GiST core code | ||||
|        will filter out such conditions. | ||||
|        The array <structfield>orderbys</structfield>, of length <structfield>norderbys</structfield>, | ||||
|        describes the ordering operators in the same manner. | ||||
|        <structfield>reconstructedValue</structfield> is the value reconstructed for the | ||||
|        parent tuple; it is <literal>(Datum) 0</literal> at the root level or if the | ||||
|        <function>inner_consistent</function> function did not provide a value at the | ||||
| @@ -803,6 +845,12 @@ typedef struct spgLeafConsistentOut | ||||
|        <structfield>recheck</structfield> may be set to <literal>true</literal> if the match | ||||
|        is uncertain and so the operator(s) must be re-applied to the actual | ||||
|        heap tuple to verify the match. | ||||
|        If ordered search is performed, set <structfield>distances</structfield> | ||||
|        to an array of distance values according to <structfield>orderbys</structfield> | ||||
|        array.  Leave it NULL otherwise.  If at least one of returned distances | ||||
|        is not exact, set <structfield>recheckDistances</structfield> to true. | ||||
|        In this case, the executor will calculate the exact distances after | ||||
|        fetching the tuple from the heap, and will reorder the tuples if needed. | ||||
|       </para> | ||||
|      </listitem> | ||||
|     </varlistentry> | ||||
|   | ||||
| @@ -1242,7 +1242,7 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING) | ||||
|   <title>Ordering Operators</title> | ||||
|  | ||||
|   <para> | ||||
|    Some index access methods (currently, only GiST) support the concept of | ||||
|    Some index access methods (currently, only GiST and SP-GiST) support the concept of | ||||
|    <firstterm>ordering operators</firstterm>.  What we have been discussing so far | ||||
|    are <firstterm>search operators</firstterm>.  A search operator is one for which | ||||
|    the index can be searched to find all rows satisfying | ||||
|   | ||||
| @@ -14,9 +14,9 @@ | ||||
|  */ | ||||
| #include "postgres.h" | ||||
|  | ||||
| #include "access/genam.h" | ||||
| #include "access/gist_private.h" | ||||
| #include "access/relscan.h" | ||||
| #include "catalog/pg_type.h" | ||||
| #include "miscadmin.h" | ||||
| #include "storage/lmgr.h" | ||||
| #include "storage/predicate.h" | ||||
| @@ -543,7 +543,6 @@ getNextNearest(IndexScanDesc scan) | ||||
| { | ||||
| 	GISTScanOpaque so = (GISTScanOpaque) scan->opaque; | ||||
| 	bool		res = false; | ||||
| 	int			i; | ||||
|  | ||||
| 	if (scan->xs_hitup) | ||||
| 	{ | ||||
| @@ -564,45 +563,10 @@ getNextNearest(IndexScanDesc scan) | ||||
| 			/* found a heap item at currently minimal distance */ | ||||
| 			scan->xs_ctup.t_self = item->data.heap.heapPtr; | ||||
| 			scan->xs_recheck = item->data.heap.recheck; | ||||
| 			scan->xs_recheckorderby = item->data.heap.recheckDistances; | ||||
| 			for (i = 0; i < scan->numberOfOrderBys; i++) | ||||
| 			{ | ||||
| 				if (so->orderByTypes[i] == FLOAT8OID) | ||||
| 				{ | ||||
| #ifndef USE_FLOAT8_BYVAL | ||||
| 					/* must free any old value to avoid memory leakage */ | ||||
| 					if (!scan->xs_orderbynulls[i]) | ||||
| 						pfree(DatumGetPointer(scan->xs_orderbyvals[i])); | ||||
| #endif | ||||
| 					scan->xs_orderbyvals[i] = Float8GetDatum(item->distances[i]); | ||||
| 					scan->xs_orderbynulls[i] = false; | ||||
| 				} | ||||
| 				else if (so->orderByTypes[i] == FLOAT4OID) | ||||
| 				{ | ||||
| 					/* convert distance function's result to ORDER BY type */ | ||||
| #ifndef USE_FLOAT4_BYVAL | ||||
| 					/* must free any old value to avoid memory leakage */ | ||||
| 					if (!scan->xs_orderbynulls[i]) | ||||
| 						pfree(DatumGetPointer(scan->xs_orderbyvals[i])); | ||||
| #endif | ||||
| 					scan->xs_orderbyvals[i] = Float4GetDatum((float4) item->distances[i]); | ||||
| 					scan->xs_orderbynulls[i] = false; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					/* | ||||
| 					 * If the ordering operator's return value is anything | ||||
| 					 * else, we don't know how to convert the float8 bound | ||||
| 					 * calculated by the distance function to that.  The | ||||
| 					 * executor won't actually need the order by values we | ||||
| 					 * return here, if there are no lossy results, so only | ||||
| 					 * insist on converting if the *recheck flag is set. | ||||
| 					 */ | ||||
| 					if (scan->xs_recheckorderby) | ||||
| 						elog(ERROR, "GiST operator family's FOR ORDER BY operator must return float8 or float4 if the distance function is lossy"); | ||||
| 					scan->xs_orderbynulls[i] = true; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			index_store_float8_orderby_distances(scan, so->orderByTypes, | ||||
| 												 item->distances, | ||||
| 												 item->data.heap.recheckDistances); | ||||
|  | ||||
| 			/* in an index-only scan, also return the reconstructed tuple. */ | ||||
| 			if (scan->xs_want_itup) | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
| #include "storage/lmgr.h" | ||||
| #include "utils/float.h" | ||||
| #include "utils/syscache.h" | ||||
| #include "utils/lsyscache.h" | ||||
|  | ||||
|  | ||||
| /* | ||||
| @@ -871,12 +872,6 @@ gistproperty(Oid index_oid, int attno, | ||||
| 			 IndexAMProperty prop, const char *propname, | ||||
| 			 bool *res, bool *isnull) | ||||
| { | ||||
| 	HeapTuple	tuple; | ||||
| 	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY; | ||||
| 	Form_pg_opclass rd_opclass; | ||||
| 	Datum		datum; | ||||
| 	bool		disnull; | ||||
| 	oidvector  *indclass; | ||||
| 	Oid			opclass, | ||||
| 				opfamily, | ||||
| 				opcintype; | ||||
| @@ -910,41 +905,19 @@ gistproperty(Oid index_oid, int attno, | ||||
| 	} | ||||
|  | ||||
| 	/* First we need to know the column's opclass. */ | ||||
|  | ||||
| 	tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); | ||||
| 	if (!HeapTupleIsValid(tuple)) | ||||
| 	opclass = get_index_column_opclass(index_oid, attno); | ||||
| 	if (!OidIsValid(opclass)) | ||||
| 	{ | ||||
| 		*isnull = true; | ||||
| 		return true; | ||||
| 	} | ||||
| 	rd_index = (Form_pg_index) GETSTRUCT(tuple); | ||||
|  | ||||
| 	/* caller is supposed to guarantee this */ | ||||
| 	Assert(attno > 0 && attno <= rd_index->indnatts); | ||||
|  | ||||
| 	datum = SysCacheGetAttr(INDEXRELID, tuple, | ||||
| 							Anum_pg_index_indclass, &disnull); | ||||
| 	Assert(!disnull); | ||||
|  | ||||
| 	indclass = ((oidvector *) DatumGetPointer(datum)); | ||||
| 	opclass = indclass->values[attno - 1]; | ||||
|  | ||||
| 	ReleaseSysCache(tuple); | ||||
|  | ||||
| 	/* Now look up the opclass family and input datatype. */ | ||||
|  | ||||
| 	tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); | ||||
| 	if (!HeapTupleIsValid(tuple)) | ||||
| 	if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype)) | ||||
| 	{ | ||||
| 		*isnull = true; | ||||
| 		return true; | ||||
| 	} | ||||
| 	rd_opclass = (Form_pg_opclass) GETSTRUCT(tuple); | ||||
|  | ||||
| 	opfamily = rd_opclass->opcfamily; | ||||
| 	opcintype = rd_opclass->opcintype; | ||||
|  | ||||
| 	ReleaseSysCache(tuple); | ||||
|  | ||||
| 	/* And now we can check whether the function is provided. */ | ||||
|  | ||||
| @@ -967,6 +940,8 @@ gistproperty(Oid index_oid, int attno, | ||||
| 									  Int16GetDatum(GIST_COMPRESS_PROC)); | ||||
| 	} | ||||
|  | ||||
| 	*isnull = false; | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -74,6 +74,7 @@ | ||||
| #include "access/transam.h" | ||||
| #include "access/xlog.h" | ||||
| #include "catalog/index.h" | ||||
| #include "catalog/pg_type.h" | ||||
| #include "pgstat.h" | ||||
| #include "storage/bufmgr.h" | ||||
| #include "storage/lmgr.h" | ||||
| @@ -897,3 +898,72 @@ index_getprocinfo(Relation irel, | ||||
|  | ||||
| 	return locinfo; | ||||
| } | ||||
|  | ||||
| /* ---------------- | ||||
|  *		index_store_float8_orderby_distances | ||||
|  * | ||||
|  *		Convert AM distance function's results (that can be inexact) | ||||
|  *		to ORDER BY types and save them into xs_orderbyvals/xs_orderbynulls | ||||
|  *		for a possible recheck. | ||||
|  * ---------------- | ||||
|  */ | ||||
| void | ||||
| index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes, | ||||
| 									 double *distances, bool recheckOrderBy) | ||||
| { | ||||
| 	int			i; | ||||
|  | ||||
| 	scan->xs_recheckorderby = recheckOrderBy; | ||||
|  | ||||
| 	if (!distances) | ||||
| 	{ | ||||
| 		Assert(!scan->xs_recheckorderby); | ||||
|  | ||||
| 		for (i = 0; i < scan->numberOfOrderBys; i++) | ||||
| 		{ | ||||
| 			scan->xs_orderbyvals[i] = (Datum) 0; | ||||
| 			scan->xs_orderbynulls[i] = true; | ||||
| 		} | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	for (i = 0; i < scan->numberOfOrderBys; i++) | ||||
| 	{ | ||||
| 		if (orderByTypes[i] == FLOAT8OID) | ||||
| 		{ | ||||
| #ifndef USE_FLOAT8_BYVAL | ||||
| 			/* must free any old value to avoid memory leakage */ | ||||
| 			if (!scan->xs_orderbynulls[i]) | ||||
| 				pfree(DatumGetPointer(scan->xs_orderbyvals[i])); | ||||
| #endif | ||||
| 			scan->xs_orderbyvals[i] = Float8GetDatum(distances[i]); | ||||
| 			scan->xs_orderbynulls[i] = false; | ||||
| 		} | ||||
| 		else if (orderByTypes[i] == FLOAT4OID) | ||||
| 		{ | ||||
| 			/* convert distance function's result to ORDER BY type */ | ||||
| #ifndef USE_FLOAT4_BYVAL | ||||
| 			/* must free any old value to avoid memory leakage */ | ||||
| 			if (!scan->xs_orderbynulls[i]) | ||||
| 				pfree(DatumGetPointer(scan->xs_orderbyvals[i])); | ||||
| #endif | ||||
| 			scan->xs_orderbyvals[i] = Float4GetDatum((float4) distances[i]); | ||||
| 			scan->xs_orderbynulls[i] = false; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			/* | ||||
| 			 * If the ordering operator's return value is anything else, we | ||||
| 			 * don't know how to convert the float8 bound calculated by the | ||||
| 			 * distance function to that.  The executor won't actually need | ||||
| 			 * the order by values we return here, if there are no lossy | ||||
| 			 * results, so only insist on converting if the *recheck flag is | ||||
| 			 * set. | ||||
| 			 */ | ||||
| 			if (scan->xs_recheckorderby) | ||||
| 				elog(ERROR, "ORDER BY operator must return float8 or float4 if the distance function is lossy"); | ||||
| 			scan->xs_orderbynulls[i] = true; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global | ||||
|  | ||||
| OBJS = spgutils.o spginsert.o spgscan.o spgvacuum.o spgvalidate.o \ | ||||
| 	spgdoinsert.o spgxlog.o \ | ||||
| 	spgtextproc.o spgquadtreeproc.o spgkdtreeproc.o | ||||
| 	spgtextproc.o spgquadtreeproc.o spgkdtreeproc.o \ | ||||
| 	spgproc.o | ||||
|  | ||||
| include $(top_srcdir)/src/backend/common.mk | ||||
|   | ||||
| @@ -41,7 +41,11 @@ contain exactly one inner tuple. | ||||
|  | ||||
| When the search traversal algorithm reaches an inner tuple, it chooses a set | ||||
| of nodes to continue tree traverse in depth.  If it reaches a leaf page it | ||||
| scans a list of leaf tuples to find the ones that match the query. | ||||
| scans a list of leaf tuples to find the ones that match the query. SP-GiST | ||||
| also supports ordered (nearest-neighbor) searches - that is during scan pending | ||||
| nodes are put into priority queue, so traversal is performed by the | ||||
| closest-first model. | ||||
|  | ||||
|  | ||||
| The insertion algorithm descends the tree similarly, except it must choose | ||||
| just one node to descend to from each inner tuple.  Insertion might also have | ||||
|   | ||||
| @@ -16,9 +16,11 @@ | ||||
| #include "postgres.h" | ||||
|  | ||||
| #include "access/spgist.h" | ||||
| #include "access/spgist_private.h" | ||||
| #include "access/stratnum.h" | ||||
| #include "catalog/pg_type.h" | ||||
| #include "utils/builtins.h" | ||||
| #include "utils/float.h" | ||||
| #include "utils/geo_decls.h" | ||||
|  | ||||
|  | ||||
| @@ -162,6 +164,7 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS) | ||||
| 	double		coord; | ||||
| 	int			which; | ||||
| 	int			i; | ||||
| 	BOX			bboxes[2]; | ||||
|  | ||||
| 	Assert(in->hasPrefix); | ||||
| 	coord = DatumGetFloat8(in->prefixDatum); | ||||
| @@ -248,12 +251,87 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS) | ||||
| 	} | ||||
|  | ||||
| 	/* We must descend into the children identified by which */ | ||||
| 	out->nodeNumbers = (int *) palloc(sizeof(int) * 2); | ||||
| 	out->nNodes = 0; | ||||
|  | ||||
| 	/* Fast-path for no matching children */ | ||||
| 	if (!which) | ||||
| 		PG_RETURN_VOID(); | ||||
|  | ||||
| 	out->nodeNumbers = (int *) palloc(sizeof(int) * 2); | ||||
|  | ||||
| 	/* | ||||
| 	 * When ordering scan keys are specified, we've to calculate distance for | ||||
| 	 * them.  In order to do that, we need calculate bounding boxes for both | ||||
| 	 * children nodes.  Calculation of those bounding boxes on non-zero level | ||||
| 	 * require knowledge of bounding box of upper node.  So, we save bounding | ||||
| 	 * boxes to traversalValues. | ||||
| 	 */ | ||||
| 	if (in->norderbys > 0) | ||||
| 	{ | ||||
| 		BOX			infArea; | ||||
| 		BOX		   *area; | ||||
|  | ||||
| 		out->distances = (double **) palloc(sizeof(double *) * in->nNodes); | ||||
| 		out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes); | ||||
|  | ||||
| 		if (in->level == 0) | ||||
| 		{ | ||||
| 			float8		inf = get_float8_infinity(); | ||||
|  | ||||
| 			infArea.high.x = inf; | ||||
| 			infArea.high.y = inf; | ||||
| 			infArea.low.x = -inf; | ||||
| 			infArea.low.y = -inf; | ||||
| 			area = &infArea; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			area = (BOX *) in->traversalValue; | ||||
| 			Assert(area); | ||||
| 		} | ||||
|  | ||||
| 		bboxes[0].low = area->low; | ||||
| 		bboxes[1].high = area->high; | ||||
|  | ||||
| 		if (in->level % 2) | ||||
| 		{ | ||||
| 			/* split box by x */ | ||||
| 			bboxes[0].high.x = bboxes[1].low.x = coord; | ||||
| 			bboxes[0].high.y = area->high.y; | ||||
| 			bboxes[1].low.y = area->low.y; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			/* split box by y */ | ||||
| 			bboxes[0].high.y = bboxes[1].low.y = coord; | ||||
| 			bboxes[0].high.x = area->high.x; | ||||
| 			bboxes[1].low.x = area->low.x; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (i = 1; i <= 2; i++) | ||||
| 	{ | ||||
| 		if (which & (1 << i)) | ||||
| 			out->nodeNumbers[out->nNodes++] = i - 1; | ||||
| 		{ | ||||
| 			out->nodeNumbers[out->nNodes] = i - 1; | ||||
|  | ||||
| 			if (in->norderbys > 0) | ||||
| 			{ | ||||
| 				MemoryContext oldCtx = MemoryContextSwitchTo( | ||||
| 															 in->traversalMemoryContext); | ||||
| 				BOX		   *box = box_copy(&bboxes[i - 1]); | ||||
|  | ||||
| 				MemoryContextSwitchTo(oldCtx); | ||||
|  | ||||
| 				out->traversalValues[out->nNodes] = box; | ||||
|  | ||||
| 				out->distances[out->nNodes] = spg_key_orderbys_distances( | ||||
| 																		 BoxPGetDatum(box), false, | ||||
| 																		 in->orderbys, in->norderbys); | ||||
| 			} | ||||
|  | ||||
| 			out->nNodes++; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* Set up level increments, too */ | ||||
|   | ||||
							
								
								
									
										88
									
								
								src/backend/access/spgist/spgproc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/backend/access/spgist/spgproc.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| /*------------------------------------------------------------------------- | ||||
|  * | ||||
|  * spgproc.c | ||||
|  *	  Common supporting procedures for SP-GiST opclasses. | ||||
|  * | ||||
|  * | ||||
|  * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group | ||||
|  * Portions Copyright (c) 1994, Regents of the University of California | ||||
|  * | ||||
|  * IDENTIFICATION | ||||
|  *			src/backend/access/spgist/spgproc.c | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
|  | ||||
| #include "postgres.h" | ||||
|  | ||||
| #include <math.h> | ||||
|  | ||||
| #include "access/spgist_private.h" | ||||
| #include "utils/builtins.h" | ||||
| #include "utils/float.h" | ||||
| #include "utils/geo_decls.h" | ||||
|  | ||||
| #define point_point_distance(p1,p2) \ | ||||
| 	DatumGetFloat8(DirectFunctionCall2(point_distance, \ | ||||
| 									   PointPGetDatum(p1), PointPGetDatum(p2))) | ||||
|  | ||||
| /* Point-box distance in the assumption that box is aligned by axis */ | ||||
| static double | ||||
| point_box_distance(Point *point, BOX *box) | ||||
| { | ||||
| 	double		dx, | ||||
| 				dy; | ||||
|  | ||||
| 	if (isnan(point->x) || isnan(box->low.x) || | ||||
| 		isnan(point->y) || isnan(box->low.y)) | ||||
| 		return get_float8_nan(); | ||||
|  | ||||
| 	if (point->x < box->low.x) | ||||
| 		dx = box->low.x - point->x; | ||||
| 	else if (point->x > box->high.x) | ||||
| 		dx = point->x - box->high.x; | ||||
| 	else | ||||
| 		dx = 0.0; | ||||
|  | ||||
| 	if (point->y < box->low.y) | ||||
| 		dy = box->low.y - point->y; | ||||
| 	else if (point->y > box->high.y) | ||||
| 		dy = point->y - box->high.y; | ||||
| 	else | ||||
| 		dy = 0.0; | ||||
|  | ||||
| 	return HYPOT(dx, dy); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Returns distances from given key to array of ordering scan keys.  Leaf key | ||||
|  * is expected to be point, non-leaf key is expected to be box.  Scan key | ||||
|  * arguments are expected to be points. | ||||
|  */ | ||||
| double * | ||||
| spg_key_orderbys_distances(Datum key, bool isLeaf, | ||||
| 						   ScanKey orderbys, int norderbys) | ||||
| { | ||||
| 	int			sk_num; | ||||
| 	double	   *distances = (double *) palloc(norderbys * sizeof(double)), | ||||
| 			   *distance = distances; | ||||
|  | ||||
| 	for (sk_num = 0; sk_num < norderbys; ++sk_num, ++orderbys, ++distance) | ||||
| 	{ | ||||
| 		Point	   *point = DatumGetPointP(orderbys->sk_argument); | ||||
|  | ||||
| 		*distance = isLeaf ? point_point_distance(point, DatumGetPointP(key)) | ||||
| 			: point_box_distance(point, DatumGetBoxP(key)); | ||||
| 	} | ||||
|  | ||||
| 	return distances; | ||||
| } | ||||
|  | ||||
| BOX * | ||||
| box_copy(BOX *orig) | ||||
| { | ||||
| 	BOX		   *result = palloc(sizeof(BOX)); | ||||
|  | ||||
| 	*result = *orig; | ||||
| 	return result; | ||||
| } | ||||
| @@ -17,8 +17,10 @@ | ||||
|  | ||||
| #include "access/spgist.h" | ||||
| #include "access/stratnum.h" | ||||
| #include "access/spgist_private.h" | ||||
| #include "catalog/pg_type.h" | ||||
| #include "utils/builtins.h" | ||||
| #include "utils/float.h" | ||||
| #include "utils/geo_decls.h" | ||||
|  | ||||
|  | ||||
| @@ -77,6 +79,38 @@ getQuadrant(Point *centroid, Point *tst) | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* Returns bounding box of a given quadrant inside given bounding box */ | ||||
| static BOX * | ||||
| getQuadrantArea(BOX *bbox, Point *centroid, int quadrant) | ||||
| { | ||||
| 	BOX		   *result = (BOX *) palloc(sizeof(BOX)); | ||||
|  | ||||
| 	switch (quadrant) | ||||
| 	{ | ||||
| 		case 1: | ||||
| 			result->high = bbox->high; | ||||
| 			result->low = *centroid; | ||||
| 			break; | ||||
| 		case 2: | ||||
| 			result->high.x = bbox->high.x; | ||||
| 			result->high.y = centroid->y; | ||||
| 			result->low.x = centroid->x; | ||||
| 			result->low.y = bbox->low.y; | ||||
| 			break; | ||||
| 		case 3: | ||||
| 			result->high = *centroid; | ||||
| 			result->low = bbox->low; | ||||
| 			break; | ||||
| 		case 4: | ||||
| 			result->high.x = centroid->x; | ||||
| 			result->high.y = bbox->high.y; | ||||
| 			result->low.x = bbox->low.x; | ||||
| 			result->low.y = centroid->y; | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| Datum | ||||
| spg_quad_choose(PG_FUNCTION_ARGS) | ||||
| @@ -196,19 +230,68 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS) | ||||
| 	spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); | ||||
| 	spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); | ||||
| 	Point	   *centroid; | ||||
| 	BOX			infbbox; | ||||
| 	BOX		   *bbox = NULL; | ||||
| 	int			which; | ||||
| 	int			i; | ||||
|  | ||||
| 	Assert(in->hasPrefix); | ||||
| 	centroid = DatumGetPointP(in->prefixDatum); | ||||
|  | ||||
| 	/* | ||||
| 	 * When ordering scan keys are specified, we've to calculate distance for | ||||
| 	 * them.  In order to do that, we need calculate bounding boxes for all | ||||
| 	 * children nodes.  Calculation of those bounding boxes on non-zero level | ||||
| 	 * require knowledge of bounding box of upper node.  So, we save bounding | ||||
| 	 * boxes to traversalValues. | ||||
| 	 */ | ||||
| 	if (in->norderbys > 0) | ||||
| 	{ | ||||
| 		out->distances = (double **) palloc(sizeof(double *) * in->nNodes); | ||||
| 		out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes); | ||||
|  | ||||
| 		if (in->level == 0) | ||||
| 		{ | ||||
| 			double		inf = get_float8_infinity(); | ||||
|  | ||||
| 			infbbox.high.x = inf; | ||||
| 			infbbox.high.y = inf; | ||||
| 			infbbox.low.x = -inf; | ||||
| 			infbbox.low.y = -inf; | ||||
| 			bbox = &infbbox; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			bbox = in->traversalValue; | ||||
| 			Assert(bbox); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (in->allTheSame) | ||||
| 	{ | ||||
| 		/* Report that all nodes should be visited */ | ||||
| 		out->nNodes = in->nNodes; | ||||
| 		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); | ||||
| 		for (i = 0; i < in->nNodes; i++) | ||||
| 		{ | ||||
| 			out->nodeNumbers[i] = i; | ||||
|  | ||||
| 			if (in->norderbys > 0) | ||||
| 			{ | ||||
| 				MemoryContext oldCtx = MemoryContextSwitchTo( | ||||
| 															 in->traversalMemoryContext); | ||||
|  | ||||
| 				/* Use parent quadrant box as traversalValue */ | ||||
| 				BOX		   *quadrant = box_copy(bbox); | ||||
|  | ||||
| 				MemoryContextSwitchTo(oldCtx); | ||||
|  | ||||
| 				out->traversalValues[i] = quadrant; | ||||
| 				out->distances[i] = spg_key_orderbys_distances( | ||||
| 															   BoxPGetDatum(quadrant), false, | ||||
| 															   in->orderbys, in->norderbys); | ||||
| 			} | ||||
| 		} | ||||
| 		PG_RETURN_VOID(); | ||||
| 	} | ||||
|  | ||||
| @@ -286,13 +369,37 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS) | ||||
| 			break;				/* no need to consider remaining conditions */ | ||||
| 	} | ||||
|  | ||||
| 	out->levelAdds = palloc(sizeof(int) * 4); | ||||
| 	for (i = 0; i < 4; ++i) | ||||
| 		out->levelAdds[i] = 1; | ||||
|  | ||||
| 	/* We must descend into the quadrant(s) identified by which */ | ||||
| 	out->nodeNumbers = (int *) palloc(sizeof(int) * 4); | ||||
| 	out->nNodes = 0; | ||||
|  | ||||
| 	for (i = 1; i <= 4; i++) | ||||
| 	{ | ||||
| 		if (which & (1 << i)) | ||||
| 			out->nodeNumbers[out->nNodes++] = i - 1; | ||||
| 		{ | ||||
| 			out->nodeNumbers[out->nNodes] = i - 1; | ||||
|  | ||||
| 			if (in->norderbys > 0) | ||||
| 			{ | ||||
| 				MemoryContext oldCtx = MemoryContextSwitchTo( | ||||
| 															 in->traversalMemoryContext); | ||||
| 				BOX		   *quadrant = getQuadrantArea(bbox, centroid, i); | ||||
|  | ||||
| 				MemoryContextSwitchTo(oldCtx); | ||||
|  | ||||
| 				out->traversalValues[out->nNodes] = quadrant; | ||||
|  | ||||
| 				out->distances[out->nNodes] = spg_key_orderbys_distances( | ||||
| 																		 BoxPGetDatum(quadrant), false, | ||||
| 																		 in->orderbys, in->norderbys); | ||||
| 			} | ||||
|  | ||||
| 			out->nNodes++; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	PG_RETURN_VOID(); | ||||
| @@ -356,5 +463,11 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS) | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	if (res && in->norderbys > 0) | ||||
| 		/* ok, it passes -> let's compute the distances */ | ||||
| 		out->distances = spg_key_orderbys_distances( | ||||
| 													BoxPGetDatum(in->leafDatum), true, | ||||
| 													in->orderbys, in->norderbys); | ||||
|  | ||||
| 	PG_RETURN_BOOL(res); | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -15,17 +15,26 @@ | ||||
|  | ||||
| #include "postgres.h" | ||||
|  | ||||
| #include "access/amvalidate.h" | ||||
| #include "access/htup_details.h" | ||||
| #include "access/reloptions.h" | ||||
| #include "access/spgist_private.h" | ||||
| #include "access/transam.h" | ||||
| #include "access/xact.h" | ||||
| #include "catalog/pg_amop.h" | ||||
| #include "optimizer/paths.h" | ||||
| #include "storage/bufmgr.h" | ||||
| #include "storage/indexfsm.h" | ||||
| #include "storage/lmgr.h" | ||||
| #include "utils/builtins.h" | ||||
| #include "utils/catcache.h" | ||||
| #include "utils/index_selfuncs.h" | ||||
| #include "utils/lsyscache.h" | ||||
| #include "utils/syscache.h" | ||||
|  | ||||
| extern Expr *spgcanorderbyop(IndexOptInfo *index, | ||||
| 				PathKey *pathkey, int pathkeyno, | ||||
| 				Expr *orderby_clause, int *indexcol_p); | ||||
|  | ||||
| /* | ||||
|  * SP-GiST handler function: return IndexAmRoutine with access method parameters | ||||
| @@ -39,7 +48,7 @@ spghandler(PG_FUNCTION_ARGS) | ||||
| 	amroutine->amstrategies = 0; | ||||
| 	amroutine->amsupport = SPGISTNProc; | ||||
| 	amroutine->amcanorder = false; | ||||
| 	amroutine->amcanorderbyop = false; | ||||
| 	amroutine->amcanorderbyop = true; | ||||
| 	amroutine->amcanbackward = false; | ||||
| 	amroutine->amcanunique = false; | ||||
| 	amroutine->amcanmulticol = false; | ||||
| @@ -61,7 +70,7 @@ spghandler(PG_FUNCTION_ARGS) | ||||
| 	amroutine->amcanreturn = spgcanreturn; | ||||
| 	amroutine->amcostestimate = spgcostestimate; | ||||
| 	amroutine->amoptions = spgoptions; | ||||
| 	amroutine->amproperty = NULL; | ||||
| 	amroutine->amproperty = spgproperty; | ||||
| 	amroutine->amvalidate = spgvalidate; | ||||
| 	amroutine->ambeginscan = spgbeginscan; | ||||
| 	amroutine->amrescan = spgrescan; | ||||
| @@ -949,3 +958,82 @@ SpGistPageAddNewItem(SpGistState *state, Page page, Item item, Size size, | ||||
|  | ||||
| 	return offnum; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  *	spgproperty() -- Check boolean properties of indexes. | ||||
|  * | ||||
|  * This is optional for most AMs, but is required for SP-GiST because the core | ||||
|  * property code doesn't support AMPROP_DISTANCE_ORDERABLE. | ||||
|  */ | ||||
| bool | ||||
| spgproperty(Oid index_oid, int attno, | ||||
| 			IndexAMProperty prop, const char *propname, | ||||
| 			bool *res, bool *isnull) | ||||
| { | ||||
| 	Oid			opclass, | ||||
| 				opfamily, | ||||
| 				opcintype; | ||||
| 	CatCList   *catlist; | ||||
| 	int			i; | ||||
|  | ||||
| 	/* Only answer column-level inquiries */ | ||||
| 	if (attno == 0) | ||||
| 		return false; | ||||
|  | ||||
| 	switch (prop) | ||||
| 	{ | ||||
| 		case AMPROP_DISTANCE_ORDERABLE: | ||||
| 			break; | ||||
| 		default: | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Currently, SP-GiST distance-ordered scans require that there be a | ||||
| 	 * distance operator in the opclass with the default types. So we assume | ||||
| 	 * that if such a operator exists, then there's a reason for it. | ||||
| 	 */ | ||||
|  | ||||
| 	/* First we need to know the column's opclass. */ | ||||
| 	opclass = get_index_column_opclass(index_oid, attno); | ||||
| 	if (!OidIsValid(opclass)) | ||||
| 	{ | ||||
| 		*isnull = true; | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	/* Now look up the opclass family and input datatype. */ | ||||
| 	if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype)) | ||||
| 	{ | ||||
| 		*isnull = true; | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	/* And now we can check whether the operator is provided. */ | ||||
| 	catlist = SearchSysCacheList1(AMOPSTRATEGY, | ||||
| 								  ObjectIdGetDatum(opfamily)); | ||||
|  | ||||
| 	*res = false; | ||||
|  | ||||
| 	for (i = 0; i < catlist->n_members; i++) | ||||
| 	{ | ||||
| 		HeapTuple	amoptup = &catlist->members[i]->tuple; | ||||
| 		Form_pg_amop amopform = (Form_pg_amop) GETSTRUCT(amoptup); | ||||
|  | ||||
| 		if (amopform->amoppurpose == AMOP_ORDER && | ||||
| 			(amopform->amoplefttype == opcintype || | ||||
| 			 amopform->amoprighttype == opcintype) && | ||||
| 			opfamily_can_sort_type(amopform->amopsortfamily, | ||||
| 								   get_op_rettype(amopform->amopopr))) | ||||
| 		{ | ||||
| 			*res = true; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ReleaseSysCacheList(catlist); | ||||
|  | ||||
| 	*isnull = false; | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -187,6 +187,7 @@ spgvalidate(Oid opclassoid) | ||||
| 	{ | ||||
| 		HeapTuple	oprtup = &oprlist->members[i]->tuple; | ||||
| 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); | ||||
| 		Oid			op_rettype; | ||||
|  | ||||
| 		/* TODO: Check that only allowed strategy numbers exist */ | ||||
| 		if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63) | ||||
| @@ -200,20 +201,26 @@ spgvalidate(Oid opclassoid) | ||||
| 			result = false; | ||||
| 		} | ||||
|  | ||||
| 		/* spgist doesn't support ORDER BY operators */ | ||||
| 		if (oprform->amoppurpose != AMOP_SEARCH || | ||||
| 			OidIsValid(oprform->amopsortfamily)) | ||||
| 		/* spgist supports ORDER BY operators */ | ||||
| 		if (oprform->amoppurpose != AMOP_SEARCH) | ||||
| 		{ | ||||
| 			ereport(INFO, | ||||
| 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), | ||||
| 					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s", | ||||
| 							opfamilyname, "spgist", | ||||
| 							format_operator(oprform->amopopr)))); | ||||
| 			result = false; | ||||
| 			/* ... and operator result must match the claimed btree opfamily */ | ||||
| 			op_rettype = get_op_rettype(oprform->amopopr); | ||||
| 			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype)) | ||||
| 			{ | ||||
| 				ereport(INFO, | ||||
| 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), | ||||
| 						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s", | ||||
| 								opfamilyname, "spgist", | ||||
| 								format_operator(oprform->amopopr)))); | ||||
| 				result = false; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 			op_rettype = BOOLOID; | ||||
|  | ||||
| 		/* Check operator signature --- same for all spgist strategies */ | ||||
| 		if (!check_amop_signature(oprform->amopopr, BOOLOID, | ||||
| 		if (!check_amop_signature(oprform->amopopr, op_rettype, | ||||
| 								  oprform->amoplefttype, | ||||
| 								  oprform->amoprighttype)) | ||||
| 		{ | ||||
|   | ||||
| @@ -74,9 +74,11 @@ | ||||
| #include "postgres.h" | ||||
|  | ||||
| #include "access/spgist.h" | ||||
| #include "access/spgist_private.h" | ||||
| #include "access/stratnum.h" | ||||
| #include "catalog/pg_type.h" | ||||
| #include "utils/float.h" | ||||
| #include "utils/fmgroids.h" | ||||
| #include "utils/fmgrprotos.h" | ||||
| #include "utils/geo_decls.h" | ||||
|  | ||||
| @@ -367,6 +369,31 @@ overAbove4D(RectBox *rect_box, RangeBox *query) | ||||
| 	return overHigher2D(&rect_box->range_box_y, &query->right); | ||||
| } | ||||
|  | ||||
| /* Lower bound for the distance between point and rect_box */ | ||||
| static double | ||||
| pointToRectBoxDistance(Point *point, RectBox *rect_box) | ||||
| { | ||||
| 	double		dx; | ||||
| 	double		dy; | ||||
|  | ||||
| 	if (point->x < rect_box->range_box_x.left.low) | ||||
| 		dx = rect_box->range_box_x.left.low - point->x; | ||||
| 	else if (point->x > rect_box->range_box_x.right.high) | ||||
| 		dx = point->x - rect_box->range_box_x.right.high; | ||||
| 	else | ||||
| 		dx = 0; | ||||
|  | ||||
| 	if (point->y < rect_box->range_box_y.left.low) | ||||
| 		dy = rect_box->range_box_y.left.low - point->y; | ||||
| 	else if (point->y > rect_box->range_box_y.right.high) | ||||
| 		dy = point->y - rect_box->range_box_y.right.high; | ||||
| 	else | ||||
| 		dy = 0; | ||||
|  | ||||
| 	return HYPOT(dx, dy); | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * SP-GiST config function | ||||
|  */ | ||||
| @@ -534,17 +561,6 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS) | ||||
| 	RangeBox   *centroid, | ||||
| 			  **queries; | ||||
|  | ||||
| 	if (in->allTheSame) | ||||
| 	{ | ||||
| 		/* Report that all nodes should be visited */ | ||||
| 		out->nNodes = in->nNodes; | ||||
| 		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); | ||||
| 		for (i = 0; i < in->nNodes; i++) | ||||
| 			out->nodeNumbers[i] = i; | ||||
|  | ||||
| 		PG_RETURN_VOID(); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * We are saving the traversal value or initialize it an unbounded one, if | ||||
| 	 * we have just begun to walk the tree. | ||||
| @@ -554,6 +570,40 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS) | ||||
| 	else | ||||
| 		rect_box = initRectBox(); | ||||
|  | ||||
| 	if (in->allTheSame) | ||||
| 	{ | ||||
| 		/* Report that all nodes should be visited */ | ||||
| 		out->nNodes = in->nNodes; | ||||
| 		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); | ||||
| 		for (i = 0; i < in->nNodes; i++) | ||||
| 			out->nodeNumbers[i] = i; | ||||
|  | ||||
| 		if (in->norderbys > 0 && in->nNodes > 0) | ||||
| 		{ | ||||
| 			double	   *distances = palloc(sizeof(double) * in->norderbys); | ||||
| 			int			j; | ||||
|  | ||||
| 			for (j = 0; j < in->norderbys; j++) | ||||
| 			{ | ||||
| 				Point	   *pt = DatumGetPointP(in->orderbys[j].sk_argument); | ||||
|  | ||||
| 				distances[j] = pointToRectBoxDistance(pt, rect_box); | ||||
| 			} | ||||
|  | ||||
| 			out->distances = (double **) palloc(sizeof(double *) * in->nNodes); | ||||
| 			out->distances[0] = distances; | ||||
|  | ||||
| 			for (i = 1; i < in->nNodes; i++) | ||||
| 			{ | ||||
| 				out->distances[i] = palloc(sizeof(double) * in->norderbys); | ||||
| 				memcpy(out->distances[i], distances, | ||||
| 					   sizeof(double) * in->norderbys); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		PG_RETURN_VOID(); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * We are casting the prefix and queries to RangeBoxes for ease of the | ||||
| 	 * following operations. | ||||
| @@ -571,6 +621,8 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS) | ||||
| 	out->nNodes = 0; | ||||
| 	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); | ||||
| 	out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes); | ||||
| 	if (in->norderbys > 0) | ||||
| 		out->distances = (double **) palloc(sizeof(double *) * in->nNodes); | ||||
|  | ||||
| 	/* | ||||
| 	 * We switch memory context, because we want to allocate memory for new | ||||
| @@ -648,6 +700,22 @@ spg_box_quad_inner_consistent(PG_FUNCTION_ARGS) | ||||
| 		{ | ||||
| 			out->traversalValues[out->nNodes] = next_rect_box; | ||||
| 			out->nodeNumbers[out->nNodes] = quadrant; | ||||
|  | ||||
| 			if (in->norderbys > 0) | ||||
| 			{ | ||||
| 				double	   *distances = palloc(sizeof(double) * in->norderbys); | ||||
| 				int			j; | ||||
|  | ||||
| 				out->distances[out->nNodes] = distances; | ||||
|  | ||||
| 				for (j = 0; j < in->norderbys; j++) | ||||
| 				{ | ||||
| 					Point	   *pt = DatumGetPointP(in->orderbys[j].sk_argument); | ||||
|  | ||||
| 					distances[j] = pointToRectBoxDistance(pt, next_rect_box); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			out->nNodes++; | ||||
| 		} | ||||
| 		else | ||||
| @@ -763,6 +831,17 @@ spg_box_quad_leaf_consistent(PG_FUNCTION_ARGS) | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	if (flag && in->norderbys > 0) | ||||
| 	{ | ||||
| 		Oid			distfnoid = in->orderbys[0].sk_func.fn_oid; | ||||
|  | ||||
| 		out->distances = spg_key_orderbys_distances(leaf, false, | ||||
| 													in->orderbys, in->norderbys); | ||||
|  | ||||
| 		/* Recheck is necessary when computing distance to polygon */ | ||||
| 		out->recheckDistances = distfnoid == F_DIST_POLYP; | ||||
| 	} | ||||
|  | ||||
| 	PG_RETURN_BOOL(flag); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										68
									
								
								src/backend/utils/cache/lsyscache.c
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										68
									
								
								src/backend/utils/cache/lsyscache.c
									
									
									
									
										vendored
									
									
								
							| @@ -1067,6 +1067,32 @@ get_opclass_input_type(Oid opclass) | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * get_opclass_family_and_input_type | ||||
|  * | ||||
|  *		Returns the OID of the operator family the opclass belongs to, | ||||
|  *				the OID of the datatype the opclass indexes | ||||
|  */ | ||||
| bool | ||||
| get_opclass_opfamily_and_input_type(Oid opclass, Oid *opfamily, Oid *opcintype) | ||||
| { | ||||
| 	HeapTuple	tp; | ||||
| 	Form_pg_opclass cla_tup; | ||||
|  | ||||
| 	tp = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); | ||||
| 	if (!HeapTupleIsValid(tp)) | ||||
| 		return false; | ||||
|  | ||||
| 	cla_tup = (Form_pg_opclass) GETSTRUCT(tp); | ||||
|  | ||||
| 	*opfamily = cla_tup->opcfamily; | ||||
| 	*opcintype = cla_tup->opcintype; | ||||
|  | ||||
| 	ReleaseSysCache(tp); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /*				---------- OPERATOR CACHE ----------					 */ | ||||
|  | ||||
| /* | ||||
| @@ -3106,3 +3132,45 @@ get_range_subtype(Oid rangeOid) | ||||
| 	else | ||||
| 		return InvalidOid; | ||||
| } | ||||
|  | ||||
| /*				---------- PG_INDEX CACHE ----------				 */ | ||||
|  | ||||
| /* | ||||
|  * get_index_column_opclass | ||||
|  * | ||||
|  *		Given the index OID and column number, | ||||
|  *		return opclass of the index column | ||||
|  *			or InvalidOid if the index was not found. | ||||
|  */ | ||||
| Oid | ||||
| get_index_column_opclass(Oid index_oid, int attno) | ||||
| { | ||||
| 	HeapTuple	tuple; | ||||
| 	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY; | ||||
| 	Datum		datum; | ||||
| 	bool		isnull; | ||||
| 	oidvector  *indclass; | ||||
| 	Oid			opclass; | ||||
|  | ||||
| 	/* First we need to know the column's opclass. */ | ||||
|  | ||||
| 	tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); | ||||
| 	if (!HeapTupleIsValid(tuple)) | ||||
| 		return InvalidOid; | ||||
|  | ||||
| 	rd_index = (Form_pg_index) GETSTRUCT(tuple); | ||||
|  | ||||
| 	/* caller is supposed to guarantee this */ | ||||
| 	Assert(attno > 0 && attno <= rd_index->indnatts); | ||||
|  | ||||
| 	datum = SysCacheGetAttr(INDEXRELID, tuple, | ||||
| 							Anum_pg_index_indclass, &isnull); | ||||
| 	Assert(!isnull); | ||||
|  | ||||
| 	indclass = ((oidvector *) DatumGetPointer(datum)); | ||||
| 	opclass = indclass->values[attno - 1]; | ||||
|  | ||||
| 	ReleaseSysCache(tuple); | ||||
|  | ||||
| 	return opclass; | ||||
| } | ||||
|   | ||||
| @@ -174,6 +174,9 @@ extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum, | ||||
| 				uint16 procnum); | ||||
| extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum, | ||||
| 				  uint16 procnum); | ||||
| extern void index_store_float8_orderby_distances(IndexScanDesc scan, | ||||
| 									 Oid *orderByTypes, double *distances, | ||||
| 									 bool recheckOrderBy); | ||||
|  | ||||
| /* | ||||
|  * index access method support routines (in genam.c) | ||||
|   | ||||
| @@ -136,7 +136,10 @@ typedef struct spgPickSplitOut | ||||
| typedef struct spgInnerConsistentIn | ||||
| { | ||||
| 	ScanKey		scankeys;		/* array of operators and comparison values */ | ||||
| 	int			nkeys;			/* length of array */ | ||||
| 	ScanKey		orderbys;		/* array of ordering operators and comparison | ||||
| 								 * values */ | ||||
| 	int			nkeys;			/* length of scankeys array */ | ||||
| 	int			norderbys;		/* length of orderbys array */ | ||||
|  | ||||
| 	Datum		reconstructedValue; /* value reconstructed at parent */ | ||||
| 	void	   *traversalValue; /* opclass-specific traverse value */ | ||||
| @@ -159,6 +162,7 @@ typedef struct spgInnerConsistentOut | ||||
| 	int		   *levelAdds;		/* increment level by this much for each */ | ||||
| 	Datum	   *reconstructedValues;	/* associated reconstructed values */ | ||||
| 	void	  **traversalValues;	/* opclass-specific traverse values */ | ||||
| 	double	  **distances;		/* associated distances */ | ||||
| } spgInnerConsistentOut; | ||||
|  | ||||
| /* | ||||
| @@ -167,7 +171,10 @@ typedef struct spgInnerConsistentOut | ||||
| typedef struct spgLeafConsistentIn | ||||
| { | ||||
| 	ScanKey		scankeys;		/* array of operators and comparison values */ | ||||
| 	int			nkeys;			/* length of array */ | ||||
| 	ScanKey		orderbys;		/* array of ordering operators and comparison | ||||
| 								 * values */ | ||||
| 	int			nkeys;			/* length of scankeys array */ | ||||
| 	int			norderbys;		/* length of orderbys array */ | ||||
|  | ||||
| 	Datum		reconstructedValue; /* value reconstructed at parent */ | ||||
| 	void	   *traversalValue; /* opclass-specific traverse value */ | ||||
| @@ -181,6 +188,8 @@ typedef struct spgLeafConsistentOut | ||||
| { | ||||
| 	Datum		leafValue;		/* reconstructed original data, if any */ | ||||
| 	bool		recheck;		/* set true if operator must be rechecked */ | ||||
| 	bool		recheckDistances;	/* set true if distances must be rechecked */ | ||||
| 	double	   *distances;		/* associated distances */ | ||||
| } spgLeafConsistentOut; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
| #include "access/spgist.h" | ||||
| #include "nodes/tidbitmap.h" | ||||
| #include "storage/buf.h" | ||||
| #include "utils/geo_decls.h" | ||||
| #include "utils/relcache.h" | ||||
|  | ||||
|  | ||||
| @@ -130,14 +131,35 @@ typedef struct SpGistState | ||||
| 	bool		isBuild;		/* true if doing index build */ | ||||
| } SpGistState; | ||||
|  | ||||
| typedef struct SpGistSearchItem | ||||
| { | ||||
| 	pairingheap_node phNode;	/* pairing heap node */ | ||||
| 	Datum		value;			/* value reconstructed from parent or | ||||
| 								 * leafValue if heaptuple */ | ||||
| 	void	   *traversalValue; /* opclass-specific traverse value */ | ||||
| 	int			level;			/* level of items on this page */ | ||||
| 	ItemPointerData heapPtr;	/* heap info, if heap tuple */ | ||||
| 	bool		isNull;			/* SearchItem is NULL item */ | ||||
| 	bool		isLeaf;			/* SearchItem is heap item */ | ||||
| 	bool		recheck;		/* qual recheck is needed */ | ||||
| 	bool		recheckDistances;	/* distance recheck is needed */ | ||||
|  | ||||
| 	/* array with numberOfOrderBys entries */ | ||||
| 	double		distances[FLEXIBLE_ARRAY_MEMBER]; | ||||
| }			SpGistSearchItem; | ||||
|  | ||||
| #define SizeOfSpGistSearchItem(n_distances) \ | ||||
| 	(offsetof(SpGistSearchItem, distances) + sizeof(double) * (n_distances)) | ||||
|  | ||||
| /* | ||||
|  * Private state of an index scan | ||||
|  */ | ||||
| typedef struct SpGistScanOpaqueData | ||||
| { | ||||
| 	SpGistState state;			/* see above */ | ||||
| 	pairingheap *scanQueue;		/* queue of to be visited items */ | ||||
| 	MemoryContext tempCxt;		/* short-lived memory context */ | ||||
| 	MemoryContext traversalCxt; /* memory context for traversalValues */ | ||||
| 	MemoryContext traversalCxt; /* single scan lifetime memory context */ | ||||
|  | ||||
| 	/* Control flags showing whether to search nulls and/or non-nulls */ | ||||
| 	bool		searchNulls;	/* scan matches (all) null entries */ | ||||
| @@ -146,9 +168,18 @@ typedef struct SpGistScanOpaqueData | ||||
| 	/* Index quals to be passed to opclass (null-related quals removed) */ | ||||
| 	int			numberOfKeys;	/* number of index qualifier conditions */ | ||||
| 	ScanKey		keyData;		/* array of index qualifier descriptors */ | ||||
| 	int			numberOfOrderBys;	/* number of ordering operators */ | ||||
| 	ScanKey		orderByData;	/* array of ordering op descriptors */ | ||||
| 	Oid		   *orderByTypes;	/* array of ordering op return types */ | ||||
| 	Oid			indexCollation; /* collation of index column */ | ||||
|  | ||||
| 	/* Stack of yet-to-be-visited pages */ | ||||
| 	List	   *scanStack;		/* List of ScanStackEntrys */ | ||||
| 	/* Opclass defined functions: */ | ||||
| 	FmgrInfo	innerConsistentFn; | ||||
| 	FmgrInfo	leafConsistentFn; | ||||
|  | ||||
| 	/* Pre-allocated workspace arrays: */ | ||||
| 	double	   *zeroDistances; | ||||
| 	double	   *infDistances; | ||||
|  | ||||
| 	/* These fields are only used in amgetbitmap scans: */ | ||||
| 	TIDBitmap  *tbm;			/* bitmap being filled */ | ||||
| @@ -161,7 +192,10 @@ typedef struct SpGistScanOpaqueData | ||||
| 	int			iPtr;			/* index for scanning through same */ | ||||
| 	ItemPointerData heapPtrs[MaxIndexTuplesPerPage];	/* TIDs from cur page */ | ||||
| 	bool		recheck[MaxIndexTuplesPerPage]; /* their recheck flags */ | ||||
| 	bool		recheckDistances[MaxIndexTuplesPerPage];	/* distance recheck | ||||
| 															 * flags */ | ||||
| 	HeapTuple	reconTups[MaxIndexTuplesPerPage];	/* reconstructed tuples */ | ||||
| 	double	   *distances[MaxIndexTuplesPerPage];	/* distances (for recheck) */ | ||||
|  | ||||
| 	/* | ||||
| 	 * Note: using MaxIndexTuplesPerPage above is a bit hokey since | ||||
| @@ -410,6 +444,9 @@ extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page, | ||||
| 					 Item item, Size size, | ||||
| 					 OffsetNumber *startOffset, | ||||
| 					 bool errorOK); | ||||
| extern bool spgproperty(Oid index_oid, int attno, | ||||
| 			IndexAMProperty prop, const char *propname, | ||||
| 			bool *res, bool *isnull); | ||||
|  | ||||
| /* spgdoinsert.c */ | ||||
| extern void spgUpdateNodeLink(SpGistInnerTuple tup, int nodeN, | ||||
| @@ -421,4 +458,9 @@ extern void spgPageIndexMultiDelete(SpGistState *state, Page page, | ||||
| extern bool spgdoinsert(Relation index, SpGistState *state, | ||||
| 			ItemPointer heapPtr, Datum datum, bool isnull); | ||||
|  | ||||
| /* spgproc.c */ | ||||
| extern double *spg_key_orderbys_distances(Datum key, bool isLeaf, | ||||
| 						   ScanKey orderbys, int norderbys); | ||||
| extern BOX *box_copy(BOX *orig); | ||||
|  | ||||
| #endif							/* SPGIST_PRIVATE_H */ | ||||
|   | ||||
| @@ -53,6 +53,6 @@ | ||||
|  */ | ||||
|  | ||||
| /*							yyyymmddN */ | ||||
| #define CATALOG_VERSION_NO	201809181 | ||||
| #define CATALOG_VERSION_NO	201809191 | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1401,6 +1401,10 @@ | ||||
| { amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point', | ||||
|   amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)', | ||||
|   amopmethod => 'spgist' }, | ||||
| { amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point', | ||||
|   amoprighttype => 'point', amopstrategy => '15', amopopr => '<->(point,point)', | ||||
|   amopmethod => 'spgist', amoppurpose => 'o', | ||||
|   amopsortfamily => 'btree/float_ops' }, | ||||
|  | ||||
| # SP-GiST kd_point_ops | ||||
| { amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point', | ||||
| @@ -1421,6 +1425,10 @@ | ||||
| { amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point', | ||||
|   amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)', | ||||
|   amopmethod => 'spgist' }, | ||||
| { amopfamily => 'spgist/kd_point_ops', amoplefttype => 'point', | ||||
|   amoprighttype => 'point', amopstrategy => '15', amopopr => '<->(point,point)', | ||||
|   amopmethod => 'spgist', amoppurpose => 'o', | ||||
|   amopsortfamily => 'btree/float_ops' }, | ||||
|  | ||||
| # SP-GiST text_ops | ||||
| { amopfamily => 'spgist/text_ops', amoplefttype => 'text', | ||||
| @@ -1590,6 +1598,10 @@ | ||||
| { amopfamily => 'spgist/poly_ops', amoplefttype => 'polygon', | ||||
|   amoprighttype => 'polygon', amopstrategy => '12', | ||||
|   amopopr => '|&>(polygon,polygon)', amopmethod => 'spgist' }, | ||||
| { amopfamily => 'spgist/poly_ops', amoplefttype => 'polygon', | ||||
|   amoprighttype => 'point', amopstrategy => '15', | ||||
|   amopopr => '<->(polygon,point)', amoppurpose => 'o', amopmethod => 'spgist', | ||||
|   amopsortfamily => 'btree/float_ops' }, | ||||
|  | ||||
| # GiST inet_ops | ||||
| { amopfamily => 'gist/network_ops', amoplefttype => 'inet', | ||||
|   | ||||
| @@ -95,6 +95,8 @@ extern char *get_constraint_name(Oid conoid); | ||||
| extern char *get_language_name(Oid langoid, bool missing_ok); | ||||
| extern Oid	get_opclass_family(Oid opclass); | ||||
| extern Oid	get_opclass_input_type(Oid opclass); | ||||
| extern bool get_opclass_opfamily_and_input_type(Oid opclass, | ||||
| 									Oid *opfamily, Oid *opcintype); | ||||
| extern RegProcedure get_opcode(Oid opno); | ||||
| extern char *get_opname(Oid opno); | ||||
| extern Oid	get_op_rettype(Oid opno); | ||||
| @@ -176,6 +178,7 @@ extern void free_attstatsslot(AttStatsSlot *sslot); | ||||
| extern char *get_namespace_name(Oid nspid); | ||||
| extern char *get_namespace_name_or_temp(Oid nspid); | ||||
| extern Oid	get_range_subtype(Oid rangeOid); | ||||
| extern Oid	get_index_column_opclass(Oid index_oid, int attno); | ||||
|  | ||||
| #define type_is_array(typid)  (get_element_type(typid) != InvalidOid) | ||||
| /* type_is_array_domain accepts both plain arrays and domains over arrays */ | ||||
|   | ||||
| @@ -83,7 +83,8 @@ select prop, | ||||
|        pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree, | ||||
|        pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash, | ||||
|        pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist, | ||||
|        pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist, | ||||
|        pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist_radix, | ||||
|        pg_index_column_has_property('sp_quad_ind'::regclass, 1, prop) as spgist_quad, | ||||
|        pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin, | ||||
|        pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin | ||||
|   from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', | ||||
| @@ -92,18 +93,18 @@ select prop, | ||||
|                     'bogus']::text[]) | ||||
|          with ordinality as u(prop,ord) | ||||
|  order by ord; | ||||
|         prop        | btree | hash | gist | spgist | gin | brin  | ||||
| --------------------+-------+------+------+--------+-----+------ | ||||
|  asc                | t     | f    | f    | f      | f   | f | ||||
|  desc               | f     | f    | f    | f      | f   | f | ||||
|  nulls_first        | f     | f    | f    | f      | f   | f | ||||
|  nulls_last         | t     | f    | f    | f      | f   | f | ||||
|  orderable          | t     | f    | f    | f      | f   | f | ||||
|  distance_orderable | f     | f    | t    | f      | f   | f | ||||
|  returnable         | t     | f    | f    | t      | f   | f | ||||
|  search_array       | t     | f    | f    | f      | f   | f | ||||
|  search_nulls       | t     | f    | t    | t      | f   | t | ||||
|  bogus              |       |      |      |        |     |  | ||||
|         prop        | btree | hash | gist | spgist_radix | spgist_quad | gin | brin  | ||||
| --------------------+-------+------+------+--------------+-------------+-----+------ | ||||
|  asc                | t     | f    | f    | f            | f           | f   | f | ||||
|  desc               | f     | f    | f    | f            | f           | f   | f | ||||
|  nulls_first        | f     | f    | f    | f            | f           | f   | f | ||||
|  nulls_last         | t     | f    | f    | f            | f           | f   | f | ||||
|  orderable          | t     | f    | f    | f            | f           | f   | f | ||||
|  distance_orderable | f     | f    | t    | f            | t           | f   | f | ||||
|  returnable         | t     | f    | f    | t            | t           | f   | f | ||||
|  search_array       | t     | f    | f    | f            | f           | f   | f | ||||
|  search_nulls       | t     | f    | t    | t            | t           | f   | t | ||||
|  bogus              |       |      |      |              |             |     |  | ||||
| (10 rows) | ||||
|  | ||||
| select prop, | ||||
|   | ||||
| @@ -294,6 +294,15 @@ SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; | ||||
|      1 | ||||
| (1 row) | ||||
|  | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_seq1 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl; | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_seq2 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_seq3 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM quad_point_tbl WHERE p IS NOT NULL; | ||||
| SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; | ||||
|  count  | ||||
| ------- | ||||
| @@ -888,6 +897,71 @@ SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; | ||||
|      1 | ||||
| (1 row) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl; | ||||
|                         QUERY PLAN                          | ||||
| ----------------------------------------------------------- | ||||
|  WindowAgg | ||||
|    ->  Index Only Scan using sp_quad_ind on quad_point_tbl | ||||
|          Order By: (p <-> '(0,0)'::point) | ||||
| (3 rows) | ||||
|  | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_idx1 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl; | ||||
| SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN quad_point_tbl_ord_idx1 idx | ||||
| ON seq.n = idx.n | ||||
| AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  n | dist | p | n | dist | p  | ||||
| ---+------+---+---+------+--- | ||||
| (0 rows) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
|                         QUERY PLAN                          | ||||
| ----------------------------------------------------------- | ||||
|  WindowAgg | ||||
|    ->  Index Only Scan using sp_quad_ind on quad_point_tbl | ||||
|          Index Cond: (p <@ '(1000,1000),(200,200)'::box) | ||||
|          Order By: (p <-> '(0,0)'::point) | ||||
| (4 rows) | ||||
|  | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_idx2 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN quad_point_tbl_ord_idx2 idx | ||||
| ON seq.n = idx.n | ||||
| AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  n | dist | p | n | dist | p  | ||||
| ---+------+---+---+------+--- | ||||
| (0 rows) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM quad_point_tbl WHERE p IS NOT NULL; | ||||
|                         QUERY PLAN                          | ||||
| ----------------------------------------------------------- | ||||
|  WindowAgg | ||||
|    ->  Index Only Scan using sp_quad_ind on quad_point_tbl | ||||
|          Index Cond: (p IS NOT NULL) | ||||
|          Order By: (p <-> '(333,400)'::point) | ||||
| (4 rows) | ||||
|  | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_idx3 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM quad_point_tbl WHERE p IS NOT NULL; | ||||
| SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN quad_point_tbl_ord_idx3 idx | ||||
| ON seq.n = idx.n | ||||
| AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  n | dist | p | n | dist | p  | ||||
| ---+------+---+---+------+--- | ||||
| (0 rows) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
|                        QUERY PLAN                         | ||||
| @@ -993,6 +1067,71 @@ SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)'; | ||||
|      1 | ||||
| (1 row) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM kd_point_tbl; | ||||
|                       QUERY PLAN                        | ||||
| ------------------------------------------------------- | ||||
|  WindowAgg | ||||
|    ->  Index Only Scan using sp_kd_ind on kd_point_tbl | ||||
|          Order By: (p <-> '(0,0)'::point) | ||||
| (3 rows) | ||||
|  | ||||
| CREATE TEMP TABLE kd_point_tbl_ord_idx1 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM kd_point_tbl; | ||||
| SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN kd_point_tbl_ord_idx1 idx | ||||
| ON seq.n = idx.n AND | ||||
| (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  n | dist | p | n | dist | p  | ||||
| ---+------+---+---+------+--- | ||||
| (0 rows) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
|                        QUERY PLAN                         | ||||
| --------------------------------------------------------- | ||||
|  WindowAgg | ||||
|    ->  Index Only Scan using sp_kd_ind on kd_point_tbl | ||||
|          Index Cond: (p <@ '(1000,1000),(200,200)'::box) | ||||
|          Order By: (p <-> '(0,0)'::point) | ||||
| (4 rows) | ||||
|  | ||||
| CREATE TEMP TABLE kd_point_tbl_ord_idx2 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN kd_point_tbl_ord_idx2 idx | ||||
| ON seq.n = idx.n AND | ||||
| (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  n | dist | p | n | dist | p  | ||||
| ---+------+---+---+------+--- | ||||
| (0 rows) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM kd_point_tbl WHERE p IS NOT NULL; | ||||
|                       QUERY PLAN                        | ||||
| ------------------------------------------------------- | ||||
|  WindowAgg | ||||
|    ->  Index Only Scan using sp_kd_ind on kd_point_tbl | ||||
|          Index Cond: (p IS NOT NULL) | ||||
|          Order By: (p <-> '(333,400)'::point) | ||||
| (4 rows) | ||||
|  | ||||
| CREATE TEMP TABLE kd_point_tbl_ord_idx3 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM kd_point_tbl WHERE p IS NOT NULL; | ||||
| SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx | ||||
| ON seq.n = idx.n AND | ||||
| (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  n | dist | p | n | dist | p  | ||||
| ---+------+---+---+------+--- | ||||
| (0 rows) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; | ||||
|                          QUERY PLAN                          | ||||
|   | ||||
| @@ -1911,6 +1911,7 @@ ORDER BY 1, 2, 3; | ||||
|        4000 |           12 | <= | ||||
|        4000 |           12 | |&> | ||||
|        4000 |           14 | >= | ||||
|        4000 |           15 | <-> | ||||
|        4000 |           15 | > | ||||
|        4000 |           16 | @> | ||||
|        4000 |           18 | = | ||||
| @@ -1924,7 +1925,7 @@ ORDER BY 1, 2, 3; | ||||
|        4000 |           26 | >> | ||||
|        4000 |           27 | >>= | ||||
|        4000 |           28 | ^@ | ||||
| (122 rows) | ||||
| (123 rows) | ||||
|  | ||||
| -- Check that all opclass search operators have selectivity estimators. | ||||
| -- This is not absolutely required, but it seems a reasonable thing | ||||
|   | ||||
| @@ -462,6 +462,54 @@ SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(2 | ||||
|   1000 | ||||
| (1 row) | ||||
|  | ||||
| -- test ORDER BY distance | ||||
| SET enable_indexscan = ON; | ||||
| SET enable_bitmapscan = OFF; | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id | ||||
| FROM quad_poly_tbl; | ||||
|                         QUERY PLAN                          | ||||
| ----------------------------------------------------------- | ||||
|  WindowAgg | ||||
|    ->  Index Scan using quad_poly_tbl_idx on quad_poly_tbl | ||||
|          Order By: (p <-> '(123,456)'::point) | ||||
| (3 rows) | ||||
|  | ||||
| CREATE TEMP TABLE quad_poly_tbl_ord_idx1 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id | ||||
| FROM quad_poly_tbl; | ||||
| SELECT * | ||||
| FROM quad_poly_tbl_ord_seq1 seq FULL JOIN quad_poly_tbl_ord_idx1 idx | ||||
| 	ON seq.n = idx.n AND seq.id = idx.id AND | ||||
| 		(seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL) | ||||
| WHERE seq.id IS NULL OR idx.id IS NULL; | ||||
|  n | dist | id | n | dist | id  | ||||
| ---+------+----+---+------+---- | ||||
| (0 rows) | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id | ||||
| FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; | ||||
|                                    QUERY PLAN                                     | ||||
| --------------------------------------------------------------------------------- | ||||
|  WindowAgg | ||||
|    ->  Index Scan using quad_poly_tbl_idx on quad_poly_tbl | ||||
|          Index Cond: (p <@ '((300,300),(400,600),(600,500),(700,200))'::polygon) | ||||
|          Order By: (p <-> '(123,456)'::point) | ||||
| (4 rows) | ||||
|  | ||||
| CREATE TEMP TABLE quad_poly_tbl_ord_idx2 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id | ||||
| FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; | ||||
| SELECT * | ||||
| FROM quad_poly_tbl_ord_seq2 seq FULL JOIN quad_poly_tbl_ord_idx2 idx | ||||
| 	ON seq.n = idx.n AND seq.id = idx.id AND | ||||
| 		(seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL) | ||||
| WHERE seq.id IS NULL OR idx.id IS NULL; | ||||
|  n | dist | id | n | dist | id  | ||||
| ---+------+----+---+------+---- | ||||
| (0 rows) | ||||
|  | ||||
| RESET enable_seqscan; | ||||
| RESET enable_indexscan; | ||||
| RESET enable_bitmapscan; | ||||
|   | ||||
| @@ -40,7 +40,8 @@ select prop, | ||||
|        pg_index_column_has_property('onek_hundred'::regclass, 1, prop) as btree, | ||||
|        pg_index_column_has_property('hash_i4_index'::regclass, 1, prop) as hash, | ||||
|        pg_index_column_has_property('gcircleind'::regclass, 1, prop) as gist, | ||||
|        pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist, | ||||
|        pg_index_column_has_property('sp_radix_ind'::regclass, 1, prop) as spgist_radix, | ||||
|        pg_index_column_has_property('sp_quad_ind'::regclass, 1, prop) as spgist_quad, | ||||
|        pg_index_column_has_property('botharrayidx'::regclass, 1, prop) as gin, | ||||
|        pg_index_column_has_property('brinidx'::regclass, 1, prop) as brin | ||||
|   from unnest(array['asc', 'desc', 'nulls_first', 'nulls_last', | ||||
|   | ||||
| @@ -198,6 +198,18 @@ SELECT count(*) FROM quad_point_tbl WHERE p >^ '(5000, 4000)'; | ||||
|  | ||||
| SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; | ||||
|  | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_seq1 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl; | ||||
|  | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_seq2 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
|  | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_seq3 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM quad_point_tbl WHERE p IS NOT NULL; | ||||
|  | ||||
| SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; | ||||
|  | ||||
| SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcde'; | ||||
| @@ -363,6 +375,39 @@ EXPLAIN (COSTS OFF) | ||||
| SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; | ||||
| SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl; | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_idx1 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl; | ||||
| SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN quad_point_tbl_ord_idx1 idx | ||||
| ON seq.n = idx.n | ||||
| AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_idx2 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN quad_point_tbl_ord_idx2 idx | ||||
| ON seq.n = idx.n | ||||
| AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM quad_point_tbl WHERE p IS NOT NULL; | ||||
| CREATE TEMP TABLE quad_point_tbl_ord_idx3 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM quad_point_tbl WHERE p IS NOT NULL; | ||||
| SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN quad_point_tbl_ord_idx3 idx | ||||
| ON seq.n = idx.n | ||||
| AND (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| @@ -391,6 +436,39 @@ EXPLAIN (COSTS OFF) | ||||
| SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)'; | ||||
| SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)'; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM kd_point_tbl; | ||||
| CREATE TEMP TABLE kd_point_tbl_ord_idx1 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM kd_point_tbl; | ||||
| SELECT * FROM quad_point_tbl_ord_seq1 seq FULL JOIN kd_point_tbl_ord_idx1 idx | ||||
| ON seq.n = idx.n AND | ||||
| (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| CREATE TEMP TABLE kd_point_tbl_ord_idx2 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '0,0') n, p <-> '0,0' dist, p | ||||
| FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)'; | ||||
| SELECT * FROM quad_point_tbl_ord_seq2 seq FULL JOIN kd_point_tbl_ord_idx2 idx | ||||
| ON seq.n = idx.n AND | ||||
| (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM kd_point_tbl WHERE p IS NOT NULL; | ||||
| CREATE TEMP TABLE kd_point_tbl_ord_idx3 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> '333,400') n, p <-> '333,400' dist, p | ||||
| FROM kd_point_tbl WHERE p IS NOT NULL; | ||||
| SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx | ||||
| ON seq.n = idx.n AND | ||||
| (seq.dist = idx.dist AND seq.p ~= idx.p OR seq.p IS NULL AND idx.p IS NULL) | ||||
| WHERE seq.n IS NULL OR idx.n IS NULL; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; | ||||
| SELECT count(*) FROM radix_text_tbl WHERE t = 'P0123456789abcdef'; | ||||
|   | ||||
| @@ -206,6 +206,39 @@ EXPLAIN (COSTS OFF) | ||||
| SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))'; | ||||
| SELECT count(*) FROM quad_poly_tbl WHERE p ~= polygon '((200, 300),(210, 310),(230, 290))'; | ||||
|  | ||||
| -- test ORDER BY distance | ||||
| SET enable_indexscan = ON; | ||||
| SET enable_bitmapscan = OFF; | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id | ||||
| FROM quad_poly_tbl; | ||||
|  | ||||
| CREATE TEMP TABLE quad_poly_tbl_ord_idx1 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id | ||||
| FROM quad_poly_tbl; | ||||
|  | ||||
| SELECT * | ||||
| FROM quad_poly_tbl_ord_seq1 seq FULL JOIN quad_poly_tbl_ord_idx1 idx | ||||
| 	ON seq.n = idx.n AND seq.id = idx.id AND | ||||
| 		(seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL) | ||||
| WHERE seq.id IS NULL OR idx.id IS NULL; | ||||
|  | ||||
|  | ||||
| EXPLAIN (COSTS OFF) | ||||
| SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id | ||||
| FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; | ||||
|  | ||||
| CREATE TEMP TABLE quad_poly_tbl_ord_idx2 AS | ||||
| SELECT rank() OVER (ORDER BY p <-> point '123,456') n, p <-> point '123,456' dist, id | ||||
| FROM quad_poly_tbl WHERE p <@ polygon '((300,300),(400,600),(600,500),(700,200))'; | ||||
|  | ||||
| SELECT * | ||||
| FROM quad_poly_tbl_ord_seq2 seq FULL JOIN quad_poly_tbl_ord_idx2 idx | ||||
| 	ON seq.n = idx.n AND seq.id = idx.id AND | ||||
| 		(seq.dist = idx.dist OR seq.dist IS NULL AND idx.dist IS NULL) | ||||
| WHERE seq.id IS NULL OR idx.id IS NULL; | ||||
|  | ||||
| RESET enable_seqscan; | ||||
| RESET enable_indexscan; | ||||
| RESET enable_bitmapscan; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user