[postgis-tickets] r17299 - Oops, add file to svn
Paul Ramsey
pramsey at cleverelephant.ca
Wed Mar 6 11:36:12 PST 2019
Author: pramsey
Date: 2019-03-06 11:36:12 -0800 (Wed, 06 Mar 2019)
New Revision: 17299
Added:
trunk/postgis/gserialized_supportfn.c
Log:
Oops, add file to svn
References #4341
Added: trunk/postgis/gserialized_supportfn.c
===================================================================
--- trunk/postgis/gserialized_supportfn.c (rev 0)
+++ trunk/postgis/gserialized_supportfn.c 2019-03-06 19:36:12 UTC (rev 17299)
@@ -0,0 +1,372 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * PostGIS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * PostGIS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************/
+
+
+#include "../postgis_config.h"
+
+#if POSTGIS_PGSQL_VERSION >= 120
+
+/* PostgreSQL */
+#include "postgres.h"
+#include "funcapi.h"
+#include "access/htup_details.h"
+#include "access/stratnum.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_type_d.h"
+#include "catalog/pg_am_d.h"
+#include "nodes/supportnodes.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/parse_func.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/numeric.h"
+#include "utils/syscache.h"
+
+/* PostGIS */
+#include "liblwgeom.h"
+
+/* Prototypes */
+Datum postgis_index_supportfn(PG_FUNCTION_ARGS);
+
+
+/*
+* Depending on the function, we will deploy different
+* index enhancement strategies. Containment functions
+* can use a more strict index strategy than overlapping
+* functions. For within-distance functions, we need
+* to construct expanded boxes, on the non-indexed
+* function argument. We store the metadata to drive
+* these choices in the IndexableFunctions array.
+*/
+typedef struct
+{
+ const char *fn_name;
+ int strategy_number; /* Index strategy to add */
+ int nargs; /* Expected number of function arguments */
+ int expand_arg; /* Radius argument for "within distance" search */
+} IndexableFunction;
+
+/*
+* Metadata currently scanned from start to back,
+* so most common functions first. Could be sorted
+* and searched with binary search.
+*/
+const IndexableFunction IndexableFunctions[] = {
+ {"st_intersects", RTOverlapStrategyNumber, 2, 0},
+ {"st_dwithin", RTOverlapStrategyNumber, 3, 3},
+ {"st_contains", RTContainsStrategyNumber, 2, 0},
+ {"st_within", RTContainedByStrategyNumber, 2, 0},
+ {"st_touches", RTOverlapStrategyNumber, 2, 0},
+ {"st_3dintersects", RTOverlapStrategyNumber, 2, 0},
+ {"st_containsproperly", RTOverlapStrategyNumber, 2, 0},
+ {"st_coveredby", RTContainedByStrategyNumber, 2, 0},
+ {"st_overlaps", RTOverlapStrategyNumber, 2, 0},
+ {"st_covers", RTContainsStrategyNumber, 2, 0},
+ {"st_crosses", RTOverlapStrategyNumber, 2, 0},
+ {"st_dfullywithin", RTOverlapStrategyNumber, 3, 3},
+ {"st_3dintersects", RTOverlapStrategyNumber, 3, 3},
+ {"st_3ddwithin", RTOverlapStrategyNumber, 3, 3},
+ {"st_3ddfullywithin", RTOverlapStrategyNumber, 3, 3},
+ {"st_linecrossingdirection", RTOverlapStrategyNumber, 2, 0},
+ {NULL, 0, 0, 0}
+};
+
+/*
+* Is the function calling the support function
+* one of those we will enhance with index ops? If
+* so, copy the metadata for the function into
+* idxfn and return true. If false... how did the
+* support function get added, anyways?
+*/
+static bool
+needsSpatialIndex(Oid funcid, IndexableFunction *idxfn)
+{
+ const IndexableFunction *idxfns = IndexableFunctions;
+ const char *fn_name = get_func_name(funcid);
+
+ do
+ {
+ if(strcmp(idxfns->fn_name, fn_name) == 0)
+ {
+ *idxfn = *idxfns;
+ return true;
+ }
+ idxfns++;
+ }
+ while (idxfns->fn_name);
+
+ return false;
+}
+
+/*
+* We only add spatial index enhancements for
+* indexes that support spatial searches (range
+* based searches like the && operator), so only
+* implementations based on GIST, SPGIST and BRIN.
+*/
+static Oid
+opFamilyAmOid(Oid opfamilyoid)
+{
+ Form_pg_opfamily familyform;
+ // char *opfamilyname;
+ Oid opfamilyam;
+ HeapTuple familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid));
+ if (!HeapTupleIsValid(familytup))
+ elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid);
+ familyform = (Form_pg_opfamily) GETSTRUCT(familytup);
+ opfamilyam = familyform->opfmethod;
+ // opfamilyname = NameStr(familyform->opfname);
+ // elog(NOTICE, "%s: found opfamily %s [%u]", __func__, opfamilyname, opfamilyam);
+ ReleaseSysCache(familytup);
+ return opfamilyam;
+}
+
+/*
+* To apply the "expand for radius search" pattern
+* we need access to the expand function, so lookup
+* the function Oid using the function name and
+* type number.
+*/
+static Oid
+expandFunctionOid(Oid geotype, Oid callingfunc)
+{
+ const Oid radiustype = FLOAT8OID; /* Should always be FLOAT8OID */
+ const Oid expandfn_args[2] = {geotype, radiustype};
+ const bool noError = true;
+ /* Expand function must be in same namespace as the caller */
+ char *nspname = get_namespace_name(get_func_namespace(callingfunc));
+ List *expandfn_name = list_make2(makeString(nspname), makeString("st_expand"));
+ Oid expandfn_oid = LookupFuncName(expandfn_name, 2, expandfn_args, noError);
+ if (expandfn_oid == InvalidOid)
+ {
+ /*
+ * This is ugly, but we first lookup the geometry variant of expand
+ * and if we fail, we look up the geography variant. The alternative
+ * is re-naming the geography variant to match the geometry
+ * one, which would not be the end of the world.
+ */
+ expandfn_name = list_make2(makeString(nspname), makeString("_st_expand"));
+ expandfn_oid = LookupFuncName(expandfn_name, 2, expandfn_args, noError);
+ if (expandfn_oid == InvalidOid)
+ elog(ERROR, "%s: unable to lookup 'st_expand(Oid[%u], Oid[%u])'", __func__, geotype, radiustype);
+ }
+ return expandfn_oid;
+}
+
+/*
+* For functions that we want enhanced with spatial
+* index lookups, add this support function to the
+* SQL function defintion, for example:
+*
+* CREATE OR REPLACE FUNCTION ST_Intersects(g1 geometry, g2 geometry)
+* RETURNS boolean
+* AS 'MODULE_PATHNAME','ST_Intersects'
+* SUPPORT postgis_index_supportfn
+* LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+* COST 100;
+*
+* The function must also have an entry above in the
+* IndexableFunctions array so that we know what
+* index search strategy we want to apply.
+*/
+PG_FUNCTION_INFO_V1(postgis_index_supportfn);
+Datum postgis_index_supportfn(PG_FUNCTION_ARGS)
+{
+ Node *rawreq = (Node *) PG_GETARG_POINTER(0);
+ Node *ret = NULL;
+
+ /*
+ * This support function is strictly for adding spatial index
+ * support.
+ */
+ if (IsA(rawreq, SupportRequestIndexCondition))
+ {
+ SupportRequestIndexCondition *req = (SupportRequestIndexCondition *) rawreq;
+
+ if (is_funcclause(req->node)) /* ST_Something() */
+ {
+ FuncExpr *clause = (FuncExpr *) req->node;
+ Oid funcid = clause->funcid;
+ IndexableFunction idxfn = {NULL, 0, 0, 0};
+ Oid opfamilyoid = req->opfamily; /* OPERATOR FAMILY of the index */
+
+ if (needsSpatialIndex(funcid, &idxfn))
+ {
+ int nargs = list_length(clause->args);
+ Node *leftarg, *rightarg;
+ Oid leftdatatype, rightdatatype, oproid;
+ bool swapped = false;
+
+ /*
+ * Only add an operator condition for GIST, SPGIST, BRIN indexes.
+ * Effectively this means only these opclasses will get automatic
+ * indexing when used with one of the indexable functions
+ * gist_geometry_ops_2d, gist_geometry_ops_nd,
+ * spgist_geometry_ops_2d, spgist_geometry_ops_nd
+ */
+ Oid opfamilyam = opFamilyAmOid(opfamilyoid);
+ if (opfamilyam != GIST_AM_OID &&
+ opfamilyam != SPGIST_AM_OID &&
+ opfamilyam != BRIN_AM_OID)
+ {
+ PG_RETURN_POINTER((Node *)NULL);
+ }
+
+ /*
+ * We can only do something with index matches on the first
+ * or second argument.
+ */
+ if (req->indexarg > 1)
+ PG_RETURN_POINTER((Node *)NULL);
+
+ /*
+ * Make sure we have enough arguments.
+ */
+ if (nargs < 2 || nargs < idxfn.expand_arg)
+ elog(ERROR, "%s: associated with function with %d arguments", __func__, nargs);
+
+ /*
+ * Extract "leftarg" as the arg matching
+ * the index and "rightarg" as the other, even if
+ * they were in the opposite order in the call.
+ * NOTE: The functions we deal with here treat
+ * their first two arguments symmetrically
+ * enough that we needn't distinguish between
+ * the two cases beyond this. Could be more
+ * complications in the future.
+ */
+ if (req->indexarg == 0)
+ {
+ leftarg = linitial(clause->args);
+ rightarg = lsecond(clause->args);
+ }
+ else
+ {
+ rightarg = linitial(clause->args);
+ leftarg = lsecond(clause->args);
+ swapped = true;
+ }
+ /*
+ * Need the argument types (which should always be geometry/geography) as
+ * this support function is only ever bound to functions
+ * using those types.
+ */
+ leftdatatype = exprType(leftarg);
+ rightdatatype = exprType(rightarg);
+
+ /*
+ * Given the index operator family and the arguments and the
+ * desired strategy number we can now lookup the operator
+ * we want (usually && or &&&).
+ */
+ oproid = get_opfamily_member(opfamilyoid, leftdatatype, rightdatatype, idxfn.strategy_number);
+ if (!OidIsValid(oproid))
+ elog(ERROR, "no spatial operator found for opfamily %u strategy %d", opfamilyoid, idxfn.strategy_number);
+
+ /*
+ * For the ST_DWithin variants we need to build a more complex return.
+ * We want to expand the non-indexed side of the call by the
+ * radius and then apply the operator.
+ * st_dwithin(g1, g2, radius) yields this, if g1 is the indexarg:
+ * g1 && st_expand(g2, radius)
+ */
+ if (idxfn.expand_arg)
+ {
+ Expr *expr;
+ Node *radiusarg = (Node *) list_nth(clause->args, idxfn.expand_arg-1);
+ Oid expandfn_oid = expandFunctionOid(rightdatatype, clause->funcid);
+
+ FuncExpr *expandexpr = makeFuncExpr(expandfn_oid, rightdatatype,
+ list_make2(rightarg, radiusarg),
+ InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+
+ /*
+ * The comparison expression has to be a pseudo constant,
+ * (not volatile or dependent on the target index table)
+ */
+ if (!is_pseudo_constant_for_index((Node*)expandexpr, req->index))
+ PG_RETURN_POINTER((Node*)NULL);
+
+ /* OK, we can make an index expression */
+ expr = make_opclause(oproid, BOOLOID, false,
+ (Expr *) leftarg, (Expr *) expandexpr,
+ InvalidOid, InvalidOid);
+
+ ret = (Node *)(list_make1(expr));
+ }
+ /*
+ * For the ST_Intersects variants we just need to return
+ * an index OpExpr with the original arguments on each
+ * side.
+ * st_intersects(g1, g2) yields: g1 && g2
+ */
+ else
+ {
+ Expr *expr;
+ /*
+ * The comparison expression has to be a pseudoconstant
+ * (not volatile or dependent on the target index's table)
+ */
+ if (!is_pseudo_constant_for_index(rightarg, req->index))
+ PG_RETURN_POINTER((Node*)NULL);
+
+ /*
+ * Arguments were swapped to put the index value on the
+ * left, so we need the commutated operator for
+ * the OpExpr
+ */
+ if (swapped)
+ {
+ oproid = get_commutator(oproid);
+ if (!OidIsValid(oproid))
+ PG_RETURN_POINTER((Node *)NULL);
+ }
+
+ expr = make_opclause(oproid, BOOLOID, false,
+ (Expr *) leftarg, (Expr *) rightarg,
+ InvalidOid, InvalidOid);
+
+ ret = (Node *)(list_make1(expr));
+ }
+
+ /*
+ * Set the lossy field on the SupportRequestIndexCondition parameter
+ * to indicate that the index alone is not sufficient to evaluate
+ * the condition. The function must also still be applied.
+ */
+ req->lossy = true;
+
+ PG_RETURN_POINTER(ret);
+ }
+ else
+ {
+ elog(WARNING, "support function '%s' called from unsupported spatial function", __func__);
+ }
+ }
+ }
+
+ PG_RETURN_POINTER(ret);
+}
+
+#endif /* POSTGIS_PGSQL_VERSION >= 120 */
More information about the postgis-tickets
mailing list