PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
gserialized_supportfn.c
Go to the documentation of this file.
1/**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************/
20
21
22#include "../postgis_config.h"
23
24/* PostgreSQL */
25#include "postgres.h"
26#include "funcapi.h"
27#include "access/htup_details.h"
28#include "access/stratnum.h"
29#include "catalog/namespace.h"
30#include "catalog/pg_opfamily.h"
31#include "catalog/pg_type_d.h"
32#include "catalog/pg_am_d.h"
33#include "nodes/supportnodes.h"
34#include "nodes/nodeFuncs.h"
35#include "nodes/makefuncs.h"
36#include "optimizer/optimizer.h"
37#include "parser/parse_func.h"
38#include "utils/array.h"
39#include "utils/builtins.h"
40#include "utils/lsyscache.h"
41#include "utils/numeric.h"
42#include "utils/syscache.h"
43
44/* PostGIS */
45#include "liblwgeom.h"
46#include "lwgeom_pg.h"
47
48/* Local prototypes */
49Datum postgis_index_supportfn(PG_FUNCTION_ARGS);
50
51/* From gserialized_estimate.c */
52float8 gserialized_joinsel_internal(PlannerInfo *root, List *args, JoinType jointype, int mode);
53float8 gserialized_sel_internal(PlannerInfo *root, List *args, int varRelid, int mode);
54
75
76static const int16 GeometryStrategies[] = {
77 [ST_INTERSECTS_IDX] = RTOverlapStrategyNumber,
78 [ST_DWITHIN_IDX] = RTOverlapStrategyNumber,
79 [ST_CONTAINS_IDX] = RTContainsStrategyNumber,
80 [ST_WITHIN_IDX] = RTContainedByStrategyNumber,
81 [ST_TOUCHES_IDX] = RTOverlapStrategyNumber,
82 [ST_3DINTERSECTS_IDX] = RTOverlapStrategyNumber,
83 [ST_CONTAINSPROPERLY_IDX] = RTContainsStrategyNumber,
84 [ST_COVEREDBY_IDX] = RTContainedByStrategyNumber,
85 [ST_OVERLAPS_IDX] = RTOverlapStrategyNumber,
86 [ST_COVERS_IDX] = RTContainsStrategyNumber,
87 [ST_CROSSES_IDX] = RTOverlapStrategyNumber,
88 [ST_DFULLYWITHIN_IDX] = RTContainedByStrategyNumber,
89 [ST_3DDWITHIN_IDX] = RTOverlapStrategyNumber,
90 [ST_3DDFULLYWITHIN_IDX] = RTOverlapStrategyNumber,
91 [ST_LINECROSSINGDIRECTION_IDX] = RTOverlapStrategyNumber,
92 [ST_ORDERINGEQUALS_IDX] = RTSameStrategyNumber,
93 [ST_EQUALS_IDX] = RTSameStrategyNumber
94};
95
96/* We use InvalidStrategy for the functions that don't currently exist for geography */
97static const int16 GeographyStrategies[] = {
98 [ST_INTERSECTS_IDX] = RTOverlapStrategyNumber,
99 [ST_DWITHIN_IDX] = RTOverlapStrategyNumber,
100 [ST_CONTAINS_IDX] = InvalidStrategy,
101 [ST_WITHIN_IDX] = InvalidStrategy,
102 [ST_TOUCHES_IDX] = InvalidStrategy,
103 [ST_3DINTERSECTS_IDX] = InvalidStrategy,
104 [ST_CONTAINSPROPERLY_IDX] = InvalidStrategy,
105 [ST_COVEREDBY_IDX] = RTOverlapStrategyNumber, /* This is different than geometry */
106 [ST_OVERLAPS_IDX] = InvalidStrategy,
107 [ST_COVERS_IDX] = RTOverlapStrategyNumber, /* This is different than geometry */
108 [ST_CROSSES_IDX] = InvalidStrategy,
109 [ST_DFULLYWITHIN_IDX] = InvalidStrategy,
110 [ST_3DDWITHIN_IDX] = InvalidStrategy,
111 [ST_3DDFULLYWITHIN_IDX] = InvalidStrategy,
112 [ST_LINECROSSINGDIRECTION_IDX] = InvalidStrategy,
113 [ST_ORDERINGEQUALS_IDX] = InvalidStrategy,
114 [ST_EQUALS_IDX] = InvalidStrategy
115};
116
117static int16
118get_strategy_by_type(Oid first_type, uint16_t index)
119{
120 if (first_type == postgis_oid(GEOMETRYOID))
121 {
122 return GeometryStrategies[index];
123 }
124
125 if (first_type == postgis_oid(GEOGRAPHYOID))
126 {
127 return GeographyStrategies[index];
128 }
129
130 return InvalidStrategy;
131}
132
133/*
134* Depending on the function, we will deploy different
135* index enhancement strategies. Containment functions
136* can use a more strict index strategy than overlapping
137* functions. For within-distance functions, we need
138* to construct expanded boxes, on the non-indexed
139* function argument. We store the metadata to drive
140* these choices in the IndexableFunctions array.
141*/
142typedef struct
143{
144 const char *fn_name;
145 uint16_t index; /* Position of the strategy in the arrays */
146 uint8_t nargs; /* Expected number of function arguments */
147 uint8_t expand_arg; /* Radius argument for "within distance" search */
148 uint8_t dims; /* Dimensionality of function */
150
151/*
152* Metadata currently scanned from start to back,
153* so most common functions first. Could be sorted
154* and searched with binary search.
155*/
157 {"st_intersects", ST_INTERSECTS_IDX, 2, 0, 2},
158 {"st_dwithin", ST_DWITHIN_IDX, 3, 3, 2},
159 {"st_contains", ST_CONTAINS_IDX, 2, 0, 2},
160 {"st_within", ST_WITHIN_IDX, 2, 0, 2},
161 {"st_touches", ST_TOUCHES_IDX, 2, 0, 2},
162 {"st_3dintersects", ST_3DINTERSECTS_IDX, 2, 0, 3},
163 {"st_containsproperly", ST_CONTAINSPROPERLY_IDX, 2, 0, 2},
164 {"st_coveredby", ST_COVEREDBY_IDX, 2, 0, 2},
165 {"st_overlaps", ST_OVERLAPS_IDX, 2, 0, 2},
166 {"st_covers", ST_COVERS_IDX, 2, 0, 2},
167 {"st_crosses", ST_CROSSES_IDX, 2, 0, 2},
168 {"st_dfullywithin", ST_DFULLYWITHIN_IDX, 3, 3, 2},
169 {"st_3ddwithin", ST_3DDWITHIN_IDX, 3, 3, 3},
170 {"st_3ddfullywithin", ST_3DDFULLYWITHIN_IDX, 3, 3, 3},
171 {"st_linecrossingdirection", ST_LINECROSSINGDIRECTION_IDX, 2, 0, 2},
172 {"st_orderingequals", ST_ORDERINGEQUALS_IDX, 2, 0, 2},
173 {"st_equals", ST_EQUALS_IDX, 2, 0, 2},
174 {NULL, 0, 0, 0, 0}
175};
176
177
178/*
179* We need to look up whether an opfamily supports
180* 2D or 3D filtering, so we can avoid accidentally
181* combining 3D filters with 2D functions.
182*/
183typedef struct
184{
185 const char *opfamilyname;
186 uint8_t dims;
188
189static const OpFamilyDim OpFamilyDims[] = {
190 {"gist_geometry_ops_2d", 2},
191 {"gist_geometry_ops_nd", 3},
192 {"brin_geometry_inclusion_ops_2d", 2},
193 {"brin_geometry_inclusion_ops_3d", 3},
194 {"brin_geometry_inclusion_ops_4d", 3},
195 {"spgist_geometry_ops_2d", 2},
196 {"spgist_geometry_ops_3d", 3},
197 {"spgist_geometry_ops_nd", 3},
198 {NULL, 0}
199};
200
201
202/*
203* Is the function calling the support function
204* one of those we will enhance with index ops? If
205* so, copy the metadata for the function into
206* idxfn and return true. If false... how did the
207* support function get added, anyways?
208*/
209static bool
211{
213 const char *fn_name = get_func_name(funcid);
214
215 do
216 {
217 if(strcmp(idxfns->fn_name, fn_name) == 0)
218 {
219 *idxfn = *idxfns;
220 return true;
221 }
222 idxfns++;
223 }
224 while (idxfns->fn_name);
225
226 return false;
227}
228
229static uint8_t
230opFamilyDim(const char* opfamily)
231{
232 const OpFamilyDim *idxdims = OpFamilyDims;
233 do
234 {
235 if(strcmp(idxdims->opfamilyname, opfamily) == 0)
236 {
237 return idxdims->dims;
238 }
239 idxdims++;
240 }
241 while (idxdims->opfamilyname);
242 return 0;
243}
244
245/*
246* We only add spatial index enhancements for
247* indexes that support spatial searches (range
248* based searches like the && operator), so only
249* implementations based on GIST, SPGIST and BRIN.
250*/
251static Oid
252opFamilyAmOid(Oid opfamilyoid, uint8_t* dims)
253{
254 Form_pg_opfamily familyform;
255 char *opfamilyname;
256 Oid opfamilyam;
257 HeapTuple familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid));
258 if (!HeapTupleIsValid(familytup))
259 elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid);
260 familyform = (Form_pg_opfamily) GETSTRUCT(familytup);
261 opfamilyam = familyform->opfmethod;
262 opfamilyname = NameStr(familyform->opfname);
263 elog(DEBUG3, "%s: found opfamily %s [%u]", __func__, opfamilyname, opfamilyam);
264 if (dims) {
265 *dims = opFamilyDim(opfamilyname);
266 }
267 ReleaseSysCache(familytup);
268 return opfamilyam;
269}
270
271/*
272* To apply the "expand for radius search" pattern
273* we need access to the expand function, so lookup
274* the function Oid using the function name and
275* type number.
276*/
277static Oid
278expandFunctionOid(Oid geotype, Oid callingfunc)
279{
280 const Oid radiustype = FLOAT8OID; /* Should always be FLOAT8OID */
281 const Oid expandfn_args[2] = {geotype, radiustype};
282 const bool noError = true;
283 /* Expand function must be in same namespace as the caller */
284 char *nspname = get_namespace_name(get_func_namespace(callingfunc));
285 List *expandfn_name = list_make2(makeString(nspname), makeString("st_expand"));
286 Oid expandfn_oid = LookupFuncName(expandfn_name, 2, expandfn_args, noError);
287 if (expandfn_oid == InvalidOid)
288 {
289 /*
290 * This is ugly, but we first lookup the geometry variant of expand
291 * and if we fail, we look up the geography variant. The alternative
292 * is re-naming the geography variant to match the geometry
293 * one, which would not be the end of the world.
294 */
295 expandfn_name = list_make2(makeString(nspname), makeString("_st_expand"));
296 expandfn_oid = LookupFuncName(expandfn_name, 2, expandfn_args, noError);
297 if (expandfn_oid == InvalidOid)
298 elog(ERROR, "%s: unable to lookup 'st_expand(Oid[%u], Oid[%u])'", __func__, geotype, radiustype);
299 }
300 return expandfn_oid;
301}
302
303/*
304* For functions that we want enhanced with spatial
305* index lookups, add this support function to the
306* SQL function definition, for example:
307*
308* CREATE OR REPLACE FUNCTION ST_Intersects(g1 geometry, g2 geometry)
309* RETURNS boolean
310* AS 'MODULE_PATHNAME','ST_Intersects'
311* SUPPORT postgis_index_supportfn
312* LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
313* COST 100;
314*
315* The function must also have an entry above in the
316* IndexableFunctions array so that we know what
317* index search strategy we want to apply.
318*/
320Datum postgis_index_supportfn(PG_FUNCTION_ARGS)
321{
322 Node *rawreq = (Node *) PG_GETARG_POINTER(0);
323 Node *ret = NULL;
324
325 /* The support function need the cache to be populated to know what the type Oids are.
326 * Otherwise it will need look them up dynamically, which only works in the schema where Postgis
327 * is installed is part of the search path (Trac #4739)
328 */
329 postgis_initialize_cache();
330
331 if (IsA(rawreq, SupportRequestSelectivity))
332 {
333 SupportRequestSelectivity *req = (SupportRequestSelectivity *) rawreq;
334
335 if (req->is_join)
336 {
337 req->selectivity = gserialized_joinsel_internal(req->root, req->args, req->jointype, 2);
338 }
339 else
340 {
341 req->selectivity = gserialized_sel_internal(req->root, req->args, req->varRelid, 2);
342 }
343 POSTGIS_DEBUGF(2, "%s: got selectivity %g", __func__, req->selectivity);
344 PG_RETURN_POINTER(req);
345 }
346
347 /*
348 * This support function is strictly for adding spatial index
349 * support.
350 */
351 if (IsA(rawreq, SupportRequestIndexCondition))
352 {
353 SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq;
354
355 if (is_funcclause(req->node)) /* ST_Something() */
356 {
357 FuncExpr *clause = (FuncExpr *) req->node;
358 Oid funcid = clause->funcid;
359 IndexableFunction idxfn = {NULL, 0, 0, 0, 0};
360 Oid opfamilyoid = req->opfamily; /* OPERATOR FAMILY of the index */
361
362 if (needsSpatialIndex(funcid, &idxfn))
363 {
364 int nargs = list_length(clause->args);
365 Node *leftarg, *rightarg;
366 Oid leftdatatype, rightdatatype, oproid;
367 bool swapped = false;
368
369 /*
370 * Only add an operator condition for GIST, SPGIST, BRIN indexes.
371 * Effectively this means only these opclasses will get automatic
372 * indexing when used with one of the indexable functions
373 * gist_geometry_ops_2d, gist_geometry_ops_nd,
374 * spgist_geometry_ops_2d, spgist_geometry_ops_nd
375 */
376 uint8_t opfamilydims;
377 Oid opfamilyam = opFamilyAmOid(opfamilyoid, &opfamilydims);
378 if (opfamilyam != GIST_AM_OID &&
379 opfamilyam != SPGIST_AM_OID &&
380 opfamilyam != BRIN_AM_OID)
381 {
382 PG_RETURN_POINTER((Node *)NULL);
383 }
384
385 /*
386 * We can only do something with index matches on the first
387 * or second argument.
388 */
389 if (req->indexarg > 1)
390 PG_RETURN_POINTER((Node *)NULL);
391
392 /*
393 * Avoid using a 3D operator (@@) with a
394 * non-3D function (like ST_Within)
395 * https://trac.osgeo.org/postgis/ticket/5025
396 */
397 if (opfamilydims == 3 && idxfn.dims != 3)
398 PG_RETURN_POINTER((Node *)NULL);
399
400 /*
401 * Make sure we have enough arguments.
402 */
403 if (nargs < 2 || nargs < idxfn.expand_arg)
404 elog(ERROR, "%s: associated with function with %d arguments", __func__, nargs);
405
406 /*
407 * Extract "leftarg" as the arg matching
408 * the index and "rightarg" as the other, even if
409 * they were in the opposite order in the call.
410 * NOTE: The functions we deal with here treat
411 * their first two arguments symmetrically
412 * enough that we needn't distinguish between
413 * the two cases beyond this. Could be more
414 * complications in the future.
415 */
416 if (req->indexarg == 0)
417 {
418 leftarg = linitial(clause->args);
419 rightarg = lsecond(clause->args);
420 }
421 else
422 {
423 rightarg = linitial(clause->args);
424 leftarg = lsecond(clause->args);
425 swapped = true;
426 }
427 /*
428 * Need the argument types (which should always be geometry/geography) as
429 * this support function is only ever bound to functions
430 * using those types.
431 */
432 leftdatatype = exprType(leftarg);
433 rightdatatype = exprType(rightarg);
434
435 /*
436 * Given the index operator family and the arguments and the
437 * desired strategy number we can now lookup the operator
438 * we want (usually && or &&&).
439 */
440 oproid = get_opfamily_member(opfamilyoid,
441 leftdatatype,
442 rightdatatype,
443 get_strategy_by_type(leftdatatype, idxfn.index));
444 if (!OidIsValid(oproid))
445 elog(ERROR,
446 "no spatial operator found for '%s': opfamily %u type %d",
447 idxfn.fn_name,
448 opfamilyoid,
449 leftdatatype);
450
451 /*
452 * For the ST_DWithin variants we need to build a more complex return.
453 * We want to expand the non-indexed side of the call by the
454 * radius and then apply the operator.
455 * st_dwithin(g1, g2, radius) yields this, if g1 is the indexarg:
456 * g1 && st_expand(g2, radius)
457 */
458 if (idxfn.expand_arg)
459 {
460 Expr *expr;
461 Node *radiusarg = (Node *) list_nth(clause->args, idxfn.expand_arg-1);
462 Oid expandfn_oid = expandFunctionOid(rightdatatype, clause->funcid);
463
464 FuncExpr *expandexpr = makeFuncExpr(expandfn_oid, rightdatatype,
465 list_make2(rightarg, radiusarg),
466 InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
467
468 /*
469 * The comparison expression has to be a pseudo constant,
470 * (not volatile or dependent on the target index table)
471 */
472#if POSTGIS_PGSQL_VERSION >= 140
473 if (!is_pseudo_constant_for_index(req->root, (Node*)expandexpr, req->index))
474#else
475 if (!is_pseudo_constant_for_index((Node*)expandexpr, req->index))
476#endif
477 PG_RETURN_POINTER((Node*)NULL);
478
479 /* OK, we can make an index expression */
480 expr = make_opclause(oproid, BOOLOID, false,
481 (Expr *) leftarg, (Expr *) expandexpr,
482 InvalidOid, InvalidOid);
483
484 ret = (Node *)(list_make1(expr));
485 }
486 /*
487 * For the ST_Intersects variants we just need to return
488 * an index OpExpr with the original arguments on each
489 * side.
490 * st_intersects(g1, g2) yields: g1 && g2
491 */
492 else
493 {
494 Expr *expr;
495 /*
496 * The comparison expression has to be a pseudoconstant
497 * (not volatile or dependent on the target index's table)
498 */
499#if POSTGIS_PGSQL_VERSION >= 140
500 if (!is_pseudo_constant_for_index(req->root, rightarg, req->index))
501#else
502 if (!is_pseudo_constant_for_index(rightarg, req->index))
503#endif
504 PG_RETURN_POINTER((Node*)NULL);
505
506 /*
507 * Arguments were swapped to put the index value on the
508 * left, so we need the commuted operator for
509 * the OpExpr
510 */
511 if (swapped)
512 {
513 oproid = get_commutator(oproid);
514 if (!OidIsValid(oproid))
515 PG_RETURN_POINTER((Node *)NULL);
516 }
517
518 expr = make_opclause(oproid, BOOLOID, false,
519 (Expr *) leftarg, (Expr *) rightarg,
520 InvalidOid, InvalidOid);
521
522 ret = (Node *)(list_make1(expr));
523 }
524
525 /*
526 * Set the lossy field on the SupportRequestIndexCondition parameter
527 * to indicate that the index alone is not sufficient to evaluate
528 * the condition. The function must also still be applied.
529 */
530 req->lossy = true;
531
532 PG_RETURN_POINTER(ret);
533 }
534 else
535 {
536 elog(WARNING, "support function '%s' called from unsupported spatial function", __func__);
537 }
538 }
539 }
540
541 PG_RETURN_POINTER(ret);
542}
static const IndexableFunction IndexableFunctions[]
static const int16 GeometryStrategies[]
@ ST_LINECROSSINGDIRECTION_IDX
@ ST_3DDFULLYWITHIN_IDX
@ ST_TOUCHES_IDX
@ ST_3DINTERSECTS_IDX
@ ST_COVERS_IDX
@ ST_DWITHIN_IDX
@ ST_OVERLAPS_IDX
@ ST_DFULLYWITHIN_IDX
@ ST_EQUALS_IDX
@ ST_CONTAINS_IDX
@ ST_INTERSECTS_IDX
@ ST_3DDWITHIN_IDX
@ ST_ORDERINGEQUALS_IDX
@ ST_CONTAINSPROPERLY_IDX
@ ST_CROSSES_IDX
@ ST_WITHIN_IDX
@ ST_COVEREDBY_IDX
static bool needsSpatialIndex(Oid funcid, IndexableFunction *idxfn)
Datum postgis_index_supportfn(PG_FUNCTION_ARGS)
static Oid expandFunctionOid(Oid geotype, Oid callingfunc)
PG_FUNCTION_INFO_V1(postgis_index_supportfn)
static const OpFamilyDim OpFamilyDims[]
static Oid opFamilyAmOid(Oid opfamilyoid, uint8_t *dims)
float8 gserialized_sel_internal(PlannerInfo *root, List *args, int varRelid, int mode)
This function should return an estimation of the number of rows returned by a query involving an over...
float8 gserialized_joinsel_internal(PlannerInfo *root, List *args, JoinType jointype, int mode)
static uint8_t opFamilyDim(const char *opfamily)
static const int16 GeographyStrategies[]
static int16 get_strategy_by_type(Oid first_type, uint16_t index)
This library is the generic geometry handling section of PostGIS.
const char * opfamilyname