PostGIS  3.4.0dev-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 /* 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 */
49 Datum postgis_index_supportfn(PG_FUNCTION_ARGS);
50 
51 /* From gserialized_estimate.c */
52 float8 gserialized_joinsel_internal(PlannerInfo *root, List *args, JoinType jointype, int mode);
53 float8 gserialized_sel_internal(PlannerInfo *root, List *args, int varRelid, int mode);
54 
56 {
73  ST_EQUALS_IDX = 16
74 };
75 
76 static 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] = RTOverlapStrategyNumber,
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 */
97 static 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 
117 static int16
118 get_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 */
142 typedef 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 */
183 typedef struct
184 {
185  const char *opfamilyname;
186  uint8_t dims;
187 } OpFamilyDim;
188 
189 static 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 */
209 static bool
211 {
212  const IndexableFunction *idxfns = IndexableFunctions;
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 
229 static uint8_t
230 opFamilyDim(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 */
251 static Oid
252 opFamilyAmOid(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 */
277 static Oid
278 expandFunctionOid(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 defintion, 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 */
320 Datum 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 commutated 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.
args
Definition: ovdump.py:45
const char * opfamilyname