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