PostGIS  3.3.9dev-r@@SVN_REVISION@@
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 */
51 Datum postgis_index_supportfn(PG_FUNCTION_ARGS);
52 
53 /* From gserialized_estimate.c */
54 float8 gserialized_joinsel_internal(PlannerInfo *root, List *args, JoinType jointype, int mode);
55 float8 gserialized_sel_internal(PlannerInfo *root, List *args, int varRelid, int mode);
56 
58 {
75  ST_EQUALS_IDX = 16
76 };
77 
78 static 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 */
99 static 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 
119 static int16
120 get_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 */
144 typedef 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  uint8_t dims; /* Dimensionality of function */
152 
153 /*
154 * Metadata currently scanned from start to back,
155 * so most common functions first. Could be sorted
156 * and searched with binary search.
157 */
159  {"st_intersects", ST_INTERSECTS_IDX, 2, 0, 2},
160  {"st_dwithin", ST_DWITHIN_IDX, 3, 3, 2},
161  {"st_contains", ST_CONTAINS_IDX, 2, 0, 2},
162  {"st_within", ST_WITHIN_IDX, 2, 0, 2},
163  {"st_touches", ST_TOUCHES_IDX, 2, 0, 2},
164  {"st_3dintersects", ST_3DINTERSECTS_IDX, 2, 0, 3},
165  {"st_containsproperly", ST_CONTAINSPROPERLY_IDX, 2, 0, 2},
166  {"st_coveredby", ST_COVEREDBY_IDX, 2, 0, 2},
167  {"st_overlaps", ST_OVERLAPS_IDX, 2, 0, 2},
168  {"st_covers", ST_COVERS_IDX, 2, 0, 2},
169  {"st_crosses", ST_CROSSES_IDX, 2, 0, 2},
170  {"st_dfullywithin", ST_DFULLYWITHIN_IDX, 3, 3, 2},
171  {"st_3ddwithin", ST_3DDWITHIN_IDX, 3, 3, 3},
172  {"st_3ddfullywithin", ST_3DDFULLYWITHIN_IDX, 3, 3, 3},
173  {"st_linecrossingdirection", ST_LINECROSSINGDIRECTION_IDX, 2, 0, 2},
174  {"st_orderingequals", ST_ORDERINGEQUALS_IDX, 2, 0, 2},
175  {"st_equals", ST_EQUALS_IDX, 2, 0, 2},
176  {NULL, 0, 0, 0, 0}
177 };
178 
179 
180 /*
181 * We need to look up whether an opfamily supports
182 * 2D or 3D filtering, so we can avoid accidentally
183 * combining 3D filters with 2D functions.
184 */
185 typedef struct
186 {
187  const char *opfamilyname;
188  uint8_t dims;
189 } OpFamilyDim;
190 
191 static const OpFamilyDim OpFamilyDims[] = {
192  {"gist_geometry_ops_2d", 2},
193  {"gist_geometry_ops_nd", 3},
194  {"brin_geometry_inclusion_ops_2d", 2},
195  {"brin_geometry_inclusion_ops_3d", 3},
196  {"brin_geometry_inclusion_ops_4d", 3},
197  {"spgist_geometry_ops_2d", 2},
198  {"spgist_geometry_ops_3d", 3},
199  {"spgist_geometry_ops_nd", 3},
200  {NULL, 0}
201 };
202 
203 
204 /*
205 * Is the function calling the support function
206 * one of those we will enhance with index ops? If
207 * so, copy the metadata for the function into
208 * idxfn and return true. If false... how did the
209 * support function get added, anyways?
210 */
211 static bool
213 {
214  const IndexableFunction *idxfns = IndexableFunctions;
215  const char *fn_name = get_func_name(funcid);
216 
217  do
218  {
219  if(strcmp(idxfns->fn_name, fn_name) == 0)
220  {
221  *idxfn = *idxfns;
222  return true;
223  }
224  idxfns++;
225  }
226  while (idxfns->fn_name);
227 
228  return false;
229 }
230 
231 static uint8_t
232 opFamilyDim(const char* opfamily)
233 {
234  const OpFamilyDim *idxdims = OpFamilyDims;
235  do
236  {
237  if(strcmp(idxdims->opfamilyname, opfamily) == 0)
238  {
239  return idxdims->dims;
240  }
241  idxdims++;
242  }
243  while (idxdims->opfamilyname);
244  return 0;
245 }
246 
247 /*
248 * We only add spatial index enhancements for
249 * indexes that support spatial searches (range
250 * based searches like the && operator), so only
251 * implementations based on GIST, SPGIST and BRIN.
252 */
253 static Oid
254 opFamilyAmOid(Oid opfamilyoid, uint8_t* dims)
255 {
256  Form_pg_opfamily familyform;
257  char *opfamilyname;
258  Oid opfamilyam;
259  HeapTuple familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid));
260  if (!HeapTupleIsValid(familytup))
261  elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid);
262  familyform = (Form_pg_opfamily) GETSTRUCT(familytup);
263  opfamilyam = familyform->opfmethod;
264  opfamilyname = NameStr(familyform->opfname);
265  elog(DEBUG3, "%s: found opfamily %s [%u]", __func__, opfamilyname, opfamilyam);
266  if (dims) {
267  *dims = opFamilyDim(opfamilyname);
268  }
269  ReleaseSysCache(familytup);
270  return opfamilyam;
271 }
272 
273 /*
274 * To apply the "expand for radius search" pattern
275 * we need access to the expand function, so lookup
276 * the function Oid using the function name and
277 * type number.
278 */
279 static Oid
280 expandFunctionOid(Oid geotype, Oid callingfunc)
281 {
282  const Oid radiustype = FLOAT8OID; /* Should always be FLOAT8OID */
283  const Oid expandfn_args[2] = {geotype, radiustype};
284  const bool noError = true;
285  /* Expand function must be in same namespace as the caller */
286  char *nspname = get_namespace_name(get_func_namespace(callingfunc));
287  List *expandfn_name = list_make2(makeString(nspname), makeString("st_expand"));
288  Oid expandfn_oid = LookupFuncName(expandfn_name, 2, expandfn_args, noError);
289  if (expandfn_oid == InvalidOid)
290  {
291  /*
292  * This is ugly, but we first lookup the geometry variant of expand
293  * and if we fail, we look up the geography variant. The alternative
294  * is re-naming the geography variant to match the geometry
295  * one, which would not be the end of the world.
296  */
297  expandfn_name = list_make2(makeString(nspname), makeString("_st_expand"));
298  expandfn_oid = LookupFuncName(expandfn_name, 2, expandfn_args, noError);
299  if (expandfn_oid == InvalidOid)
300  elog(ERROR, "%s: unable to lookup 'st_expand(Oid[%u], Oid[%u])'", __func__, geotype, radiustype);
301  }
302  return expandfn_oid;
303 }
304 
305 /*
306 * For functions that we want enhanced with spatial
307 * index lookups, add this support function to the
308 * SQL function defintion, for example:
309 *
310 * CREATE OR REPLACE FUNCTION ST_Intersects(g1 geometry, g2 geometry)
311 * RETURNS boolean
312 * AS 'MODULE_PATHNAME','ST_Intersects'
313 * SUPPORT postgis_index_supportfn
314 * LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
315 * COST 100;
316 *
317 * The function must also have an entry above in the
318 * IndexableFunctions array so that we know what
319 * index search strategy we want to apply.
320 */
322 Datum postgis_index_supportfn(PG_FUNCTION_ARGS)
323 {
324  Node *rawreq = (Node *) PG_GETARG_POINTER(0);
325  Node *ret = NULL;
326 
327  /* The support function need the cache to be populated to know what the type Oids are.
328  * Otherwise it will need look them up dynamically, which only works in the schema where Postgis
329  * is installed is part of the search path (Trac #4739)
330  */
331  postgis_initialize_cache();
332 
333  if (IsA(rawreq, SupportRequestSelectivity))
334  {
335  SupportRequestSelectivity *req = (SupportRequestSelectivity *) rawreq;
336 
337  if (req->is_join)
338  {
339  req->selectivity = gserialized_joinsel_internal(req->root, req->args, req->jointype, 2);
340  }
341  else
342  {
343  req->selectivity = gserialized_sel_internal(req->root, req->args, req->varRelid, 2);
344  }
345  POSTGIS_DEBUGF(2, "%s: got selectivity %g", __func__, req->selectivity);
346  PG_RETURN_POINTER(req);
347  }
348 
349  /*
350  * This support function is strictly for adding spatial index
351  * support.
352  */
353  if (IsA(rawreq, SupportRequestIndexCondition))
354  {
355  SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq;
356 
357  if (is_funcclause(req->node)) /* ST_Something() */
358  {
359  FuncExpr *clause = (FuncExpr *) req->node;
360  Oid funcid = clause->funcid;
361  IndexableFunction idxfn = {NULL, 0, 0, 0, 0};
362  Oid opfamilyoid = req->opfamily; /* OPERATOR FAMILY of the index */
363 
364  if (needsSpatialIndex(funcid, &idxfn))
365  {
366  int nargs = list_length(clause->args);
367  Node *leftarg, *rightarg;
368  Oid leftdatatype, rightdatatype, oproid;
369  bool swapped = false;
370 
371  /*
372  * Only add an operator condition for GIST, SPGIST, BRIN indexes.
373  * Effectively this means only these opclasses will get automatic
374  * indexing when used with one of the indexable functions
375  * gist_geometry_ops_2d, gist_geometry_ops_nd,
376  * spgist_geometry_ops_2d, spgist_geometry_ops_nd
377  */
378  uint8_t opfamilydims;
379  Oid opfamilyam = opFamilyAmOid(opfamilyoid, &opfamilydims);
380  if (opfamilyam != GIST_AM_OID &&
381  opfamilyam != SPGIST_AM_OID &&
382  opfamilyam != BRIN_AM_OID)
383  {
384  PG_RETURN_POINTER((Node *)NULL);
385  }
386 
387  /*
388  * We can only do something with index matches on the first
389  * or second argument.
390  */
391  if (req->indexarg > 1)
392  PG_RETURN_POINTER((Node *)NULL);
393 
394  /*
395  * Avoid using a 3D operator (@@) with a
396  * non-3D function (like ST_Within)
397  * https://trac.osgeo.org/postgis/ticket/5025
398  */
399  if (opfamilydims == 3 && idxfn.dims != 3)
400  PG_RETURN_POINTER((Node *)NULL);
401 
402  /*
403  * Make sure we have enough arguments.
404  */
405  if (nargs < 2 || nargs < idxfn.expand_arg)
406  elog(ERROR, "%s: associated with function with %d arguments", __func__, nargs);
407 
408  /*
409  * Extract "leftarg" as the arg matching
410  * the index and "rightarg" as the other, even if
411  * they were in the opposite order in the call.
412  * NOTE: The functions we deal with here treat
413  * their first two arguments symmetrically
414  * enough that we needn't distinguish between
415  * the two cases beyond this. Could be more
416  * complications in the future.
417  */
418  if (req->indexarg == 0)
419  {
420  leftarg = linitial(clause->args);
421  rightarg = lsecond(clause->args);
422  }
423  else
424  {
425  rightarg = linitial(clause->args);
426  leftarg = lsecond(clause->args);
427  swapped = true;
428  }
429  /*
430  * Need the argument types (which should always be geometry/geography) as
431  * this support function is only ever bound to functions
432  * using those types.
433  */
434  leftdatatype = exprType(leftarg);
435  rightdatatype = exprType(rightarg);
436 
437  /*
438  * Given the index operator family and the arguments and the
439  * desired strategy number we can now lookup the operator
440  * we want (usually && or &&&).
441  */
442  oproid = get_opfamily_member(opfamilyoid,
443  leftdatatype,
444  rightdatatype,
445  get_strategy_by_type(leftdatatype, idxfn.index));
446  if (!OidIsValid(oproid))
447  elog(ERROR,
448  "no spatial operator found for '%s': opfamily %u type %d",
449  idxfn.fn_name,
450  opfamilyoid,
451  leftdatatype);
452 
453  /*
454  * For the ST_DWithin variants we need to build a more complex return.
455  * We want to expand the non-indexed side of the call by the
456  * radius and then apply the operator.
457  * st_dwithin(g1, g2, radius) yields this, if g1 is the indexarg:
458  * g1 && st_expand(g2, radius)
459  */
460  if (idxfn.expand_arg)
461  {
462  Expr *expr;
463  Node *radiusarg = (Node *) list_nth(clause->args, idxfn.expand_arg-1);
464  Oid expandfn_oid = expandFunctionOid(rightdatatype, clause->funcid);
465 
466  FuncExpr *expandexpr = makeFuncExpr(expandfn_oid, rightdatatype,
467  list_make2(rightarg, radiusarg),
468  InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
469 
470  /*
471  * The comparison expression has to be a pseudo constant,
472  * (not volatile or dependent on the target index table)
473  */
474 #if POSTGIS_PGSQL_VERSION >= 140
475  if (!is_pseudo_constant_for_index(req->root, (Node*)expandexpr, req->index))
476 #else
477  if (!is_pseudo_constant_for_index((Node*)expandexpr, req->index))
478 #endif
479  PG_RETURN_POINTER((Node*)NULL);
480 
481  /* OK, we can make an index expression */
482  expr = make_opclause(oproid, BOOLOID, false,
483  (Expr *) leftarg, (Expr *) expandexpr,
484  InvalidOid, InvalidOid);
485 
486  ret = (Node *)(list_make1(expr));
487  }
488  /*
489  * For the ST_Intersects variants we just need to return
490  * an index OpExpr with the original arguments on each
491  * side.
492  * st_intersects(g1, g2) yields: g1 && g2
493  */
494  else
495  {
496  Expr *expr;
497  /*
498  * The comparison expression has to be a pseudoconstant
499  * (not volatile or dependent on the target index's table)
500  */
501 #if POSTGIS_PGSQL_VERSION >= 140
502  if (!is_pseudo_constant_for_index(req->root, rightarg, req->index))
503 #else
504  if (!is_pseudo_constant_for_index(rightarg, req->index))
505 #endif
506  PG_RETURN_POINTER((Node*)NULL);
507 
508  /*
509  * Arguments were swapped to put the index value on the
510  * left, so we need the commutated operator for
511  * the OpExpr
512  */
513  if (swapped)
514  {
515  oproid = get_commutator(oproid);
516  if (!OidIsValid(oproid))
517  PG_RETURN_POINTER((Node *)NULL);
518  }
519 
520  expr = make_opclause(oproid, BOOLOID, false,
521  (Expr *) leftarg, (Expr *) rightarg,
522  InvalidOid, InvalidOid);
523 
524  ret = (Node *)(list_make1(expr));
525  }
526 
527  /*
528  * Set the lossy field on the SupportRequestIndexCondition parameter
529  * to indicate that the index alone is not sufficient to evaluate
530  * the condition. The function must also still be applied.
531  */
532  req->lossy = true;
533 
534  PG_RETURN_POINTER(ret);
535  }
536  else
537  {
538  elog(WARNING, "support function '%s' called from unsupported spatial function", __func__);
539  }
540  }
541  }
542 
543  PG_RETURN_POINTER(ret);
544 }
545 
546 #endif /* POSTGIS_PGSQL_VERSION >= 120 */
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.
args
Definition: ovdump.py:45
const char * opfamilyname