PostGIS  3.0.6dev-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 
57 enum 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 
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 } 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 */
157 static 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 */
185 static bool
186 needsSpatialIndex(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 */
211 static Oid
212 opFamilyAmOid(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 */
234 static Oid
235 expandFunctionOid(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 */
276 PG_FUNCTION_INFO_V1(postgis_index_supportfn);
277 Datum 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.
args
Definition: ovdump.py:45