#include "lwgeom_geos.h"
#include "lwgeom_pg.h"
#include "lwgeom_transform.h"
+#include "lwgeom_accum.h"
/* Local prototypes */
Datum PGISDirectFunctionCall1(PGFunction func, Datum arg1);
Datum PGISDirectFunctionCall2(PGFunction func, Datum arg1, Datum arg2);
Datum pgis_geometry_accum_transfn(PG_FUNCTION_ARGS);
-Datum pgis_geometry_union_finalfn(PG_FUNCTION_ARGS);
Datum pgis_geometry_collect_finalfn(PG_FUNCTION_ARGS);
Datum pgis_geometry_polygonize_finalfn(PG_FUNCTION_ARGS);
Datum pgis_geometry_makeline_finalfn(PG_FUNCTION_ARGS);
Datum LWGEOM_makeline_garray(PG_FUNCTION_ARGS);
-/** @file
-** Versions of PostgreSQL < 8.4 perform array accumulation internally using
-** pass by value, which is very slow working with large/many geometries.
-** Hence PostGIS currently implements its own aggregate for building
-** geometry arrays using pass by reference, which is significantly faster and
-** similar to the method used in PostgreSQL 8.4.
-**
-** Hence we can revert this to the original aggregate functions from 1.3 at
-** whatever point PostgreSQL 8.4 becomes the minimum version we support :)
-*/
-
-
-/**
-** To pass the internal ArrayBuildState pointer between the
-** transfn and finalfn we need to wrap it into a custom type first,
-** the pgis_abs type in our case. The extra "data" member can optionally
-** be used to pass an additional constant argument to a finalizer function.
-*/
-
-typedef struct
-{
- ArrayBuildState *a;
- Datum data;
-}
-pgis_abs;
-
-
/**
-** The transfer function hooks into the PostgreSQL accumArrayResult()
-** function (present since 8.0) to build an array in a side memory
-** context.
+** The transfer function builds a List of LWGEOM* allocated
+** in the aggregate memory context. The pgis_accum_finalfn
+** converts that List into a Pg Array.
*/
PG_FUNCTION_INFO_V1(pgis_geometry_accum_transfn);
Datum
pgis_geometry_accum_transfn(PG_FUNCTION_ARGS)
{
- Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
- MemoryContext aggcontext;
- ArrayBuildState *state;
- pgis_abs *p;
- Datum elem;
+ MemoryContext aggcontext, old;
+ CollectionBuildState *state;
+ LWGEOM *geom = NULL;
+ GSERIALIZED *gser = NULL;
+ Datum argType = get_fn_expr_argtype(fcinfo->flinfo, 1);
- if (arg1_typeid == InvalidOid)
+ if (argType == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not determine input data type")));
if ( PG_ARGISNULL(0) )
{
- MemoryContext old = MemoryContextSwitchTo(aggcontext);
- p = (pgis_abs*) palloc(sizeof(pgis_abs));
- p->a = NULL;
- p->data = (Datum) NULL;
-
- if (PG_NARGS() == 3)
- {
- Datum argument = PG_GETARG_DATUM(2);
- Oid dataOid = get_fn_expr_argtype(fcinfo->flinfo, 2);
+ int n = ((PG_NARGS()-2) <= CollectionBuildStateDataSize) ? (PG_NARGS()-2) : CollectionBuildStateDataSize;
- p->data = datumCopy(argument, get_typbyval(dataOid), get_typlen(dataOid));
+ state = MemoryContextAlloc(aggcontext, sizeof(CollectionBuildState));
+ state->geoms = NULL;
+ state->geomOid = argType;
+ for (int i = 0; i < n; i++)
+ {
+ Datum argument = PG_GETARG_DATUM(i+2);
+ Oid dataOid = get_fn_expr_argtype(fcinfo->flinfo, i+2);
+ state->data[i] = datumCopy(argument, get_typbyval(dataOid), get_typlen(dataOid));
}
- MemoryContextSwitchTo(old);
}
else
{
- p = (pgis_abs*) PG_GETARG_POINTER(0);
+ state = (CollectionBuildState*) PG_GETARG_POINTER(0);
}
- state = p->a;
- elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
- state = accumArrayResult(state,
- elem,
- PG_ARGISNULL(1),
- arg1_typeid,
- aggcontext);
- p->a = state;
-
- PG_RETURN_POINTER(p);
-}
+ if (!PG_ARGISNULL(1))
+ gser = PG_GETARG_GSERIALIZED_P(1);
+ /* Take a copy of the geometry into the aggregate context */
+ old = MemoryContextSwitchTo(aggcontext);
+ if (gser)
+ geom = lwgeom_clone_deep(lwgeom_from_gserialized(gser));
+
+ /* Initialize or append to list as necessary */
+ if (state->geoms)
+ state->geoms = lappend(state->geoms, geom);
+ else
+ state->geoms = list_make1(geom);
-Datum pgis_accum_finalfn(pgis_abs *p, MemoryContext mctx, FunctionCallInfo fcinfo);
+ MemoryContextSwitchTo(old);
+
+ PG_RETURN_POINTER(state);
+}
+
+
+Datum pgis_accum_finalfn(CollectionBuildState *state, MemoryContext mctx, FunctionCallInfo fcinfo);
/**
-** The final function rescues the built array from the side memory context
-** using the PostgreSQL built-in function makeMdArrayResult
+** The final function reads the List of LWGEOM* from the aggregate
+** memory context and constructs an Array using construct_md_array()
*/
Datum
-pgis_accum_finalfn(pgis_abs *p, MemoryContext mctx, __attribute__((__unused__)) FunctionCallInfo fcinfo)
+pgis_accum_finalfn(CollectionBuildState *state, MemoryContext mctx, __attribute__((__unused__)) FunctionCallInfo fcinfo)
{
+ ListCell *l;
+ size_t nelems = 0;
+ Datum *elems;
+ bool *nulls;
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
+ size_t i = 0;
+ ArrayType *arr;
int dims[1];
- int lbs[1];
- ArrayBuildState *state;
- Datum result;
+ int lbs[1] = {1};
/* cannot be called directly because of internal-type argument */
Assert(fcinfo->context &&
IsA(fcinfo->context, WindowAggState))
);
- state = p->a;
- dims[0] = state->nelems;
- lbs[0] = 1;
- result = makeMdArrayResult(state, 1, dims, lbs, mctx, false);
- return result;
+ /* Retrieve geometry type metadata */
+ get_typlenbyvalalign(state->geomOid, &elmlen, &elmbyval, &elmalign);
+ nelems = list_length(state->geoms);
+
+ /* Build up an array, because that's what we pass to all the */
+ /* specific final functions */
+ elems = palloc(nelems * sizeof(Datum));
+ nulls = palloc(nelems * sizeof(bool));
+
+ foreach (l, state->geoms)
+ {
+ LWGEOM *geom = (LWGEOM*)(lfirst(l));
+ Datum elem = (Datum)0;
+ bool isNull = true;
+ if (geom)
+ {
+ GSERIALIZED *gser = geometry_serialize(geom);
+ elem = PointerGetDatum(gser);
+ isNull = false;
+ }
+ elems[i] = elem;
+ nulls[i] = isNull;
+ i++;
+
+ if (i >= nelems)
+ break;
+ }
+
+ /* Turn element array into PgSQL array */
+ dims[0] = nelems;
+ arr = construct_md_array(elems, nulls, 1, dims, lbs, state->geomOid,
+ elmlen, elmbyval, elmalign);
+
+ return PointerGetDatum(arr);
}
/**
Datum
pgis_geometry_collect_finalfn(PG_FUNCTION_ARGS)
{
- pgis_abs *p;
+ CollectionBuildState *p;
Datum result = 0;
Datum geometry_array = 0;
if (PG_ARGISNULL(0))
PG_RETURN_NULL(); /* returns null iff no input values */
- p = (pgis_abs*) PG_GETARG_POINTER(0);
+ p = (CollectionBuildState*) PG_GETARG_POINTER(0);
geometry_array = pgis_accum_finalfn(p, CurrentMemoryContext, fcinfo);
result = PGISDirectFunctionCall1( LWGEOM_collect_garray, geometry_array );
Datum
pgis_geometry_polygonize_finalfn(PG_FUNCTION_ARGS)
{
- pgis_abs *p;
+ CollectionBuildState *p;
Datum result = 0;
Datum geometry_array = 0;
if (PG_ARGISNULL(0))
PG_RETURN_NULL(); /* returns null iff no input values */
- p = (pgis_abs*) PG_GETARG_POINTER(0);
+ p = (CollectionBuildState*) PG_GETARG_POINTER(0);
geometry_array = pgis_accum_finalfn(p, CurrentMemoryContext, fcinfo);
result = PGISDirectFunctionCall1( polygonize_garray, geometry_array );
Datum
pgis_geometry_makeline_finalfn(PG_FUNCTION_ARGS)
{
- pgis_abs *p;
+ CollectionBuildState *p;
Datum result = 0;
Datum geometry_array = 0;
if (PG_ARGISNULL(0))
PG_RETURN_NULL(); /* returns null iff no input values */
- p = (pgis_abs*) PG_GETARG_POINTER(0);
+ p = (CollectionBuildState*) PG_GETARG_POINTER(0);
geometry_array = pgis_accum_finalfn(p, CurrentMemoryContext, fcinfo);
result = PGISDirectFunctionCall1( LWGEOM_makeline_garray, geometry_array );
Datum
pgis_geometry_clusterintersecting_finalfn(PG_FUNCTION_ARGS)
{
- pgis_abs *p;
+ CollectionBuildState *p;
Datum result = 0;
Datum geometry_array = 0;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
- p = (pgis_abs*) PG_GETARG_POINTER(0);
+ p = (CollectionBuildState*) PG_GETARG_POINTER(0);
geometry_array = pgis_accum_finalfn(p, CurrentMemoryContext, fcinfo);
result = PGISDirectFunctionCall1( clusterintersecting_garray, geometry_array );
if (!result)
Datum
pgis_geometry_clusterwithin_finalfn(PG_FUNCTION_ARGS)
{
- pgis_abs *p;
+ CollectionBuildState *p;
Datum result = 0;
Datum geometry_array = 0;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
- p = (pgis_abs*) PG_GETARG_POINTER(0);
+ p = (CollectionBuildState*) PG_GETARG_POINTER(0);
- if (!p->data)
+ if (!p->data[0])
{
elog(ERROR, "Tolerance not defined");
PG_RETURN_NULL();
}
geometry_array = pgis_accum_finalfn(p, CurrentMemoryContext, fcinfo);
- result = PGISDirectFunctionCall2( cluster_within_distance_garray, geometry_array, p->data);
+ result = PGISDirectFunctionCall2( cluster_within_distance_garray, geometry_array, p->data[0]);
if (!result)
PG_RETURN_NULL();
--- /dev/null
+/**********************************************************************
+ *
+ * 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/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (c) 2019, Paul Ramsey <pramsey@cleverelephant.ca>
+ *
+ **********************************************************************/
+
+#ifndef _LWGEOM_ACCUM_H
+#define _LWGEOM_ACCUM_H 1
+
+/**
+** To pass the internal state of our collection between the
+** transfn and finalfn we need to wrap it into a custom type first,
+** the CollectionBuildState type in our case. The extra "data" member
+** can optionally be used to pass additional constant
+** arguments to a finalizer function.
+*/
+#define CollectionBuildStateDataSize 2
+typedef struct CollectionBuildState
+{
+ List *geoms; /* collected geometries */
+ Datum data[CollectionBuildStateDataSize];
+ Oid geomOid;
+} CollectionBuildState;
+
+
+#endif /* _LWGEOM_ACCUM_H */
#include "../postgis_config.h"
+#include "float.h" /* for DBL_DIG */
+
/* PostgreSQL */
#include "postgres.h"
#include "funcapi.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/numeric.h"
-
#include "access/htup_details.h"
-
/* PostGIS */
#include "lwgeom_functions_analytic.h" /* for point_in_polygon */
#include "lwgeom_geos.h"
#include "liblwgeom.h"
#include "lwgeom_rtree.h"
#include "lwgeom_geos_prepared.h"
-#include "float.h" /* for DBL_DIG */
+#include "lwgeom_accum.h"
/* Return NULL on GEOS error
Datum ST_DelaunayTriangles(PG_FUNCTION_ARGS);
Datum pgis_union_geometry_array(PG_FUNCTION_ARGS);
+Datum pgis_geometry_union_finalfn(PG_FUNCTION_ARGS);
/*
** Prototypes end
PG_RETURN_POINTER(gser_out);
}
-typedef struct UnionBuildState
-{
- MemoryContext mcontext; /* where all the temp stuff is kept */
- GEOSGeometry **geoms; /* collected GEOS geometries*/
- int empty_type;
- uint32_t alen; /* allocated length of above arrays */
- uint32_t ngeoms; /* number of valid entries in above arrays */
- int32_t srid;
- bool is3d;
-} UnionBuildState;
-PG_FUNCTION_INFO_V1(pgis_geometry_union_transfn);
-Datum pgis_geometry_union_transfn(PG_FUNCTION_ARGS)
+PG_FUNCTION_INFO_V1(pgis_geometry_union_finalfn);
+Datum pgis_geometry_union_finalfn(PG_FUNCTION_ARGS)
{
- MemoryContext aggcontext;
- MemoryContext old;
- UnionBuildState *state;
- GSERIALIZED *gser_in;
- uint32_t curgeom;
- GEOSGeometry *g;
-
- if (!AggCheckCallContext(fcinfo, &aggcontext))
- {
- /* cannot be called directly because of dummy-type argument */
- elog(ERROR, "%s called in non-aggregate context", __func__);
- aggcontext = NULL; /* keep compiler quiet */
- }
-
- if (!PG_ARGISNULL(0))
- {
- state = (UnionBuildState *)PG_GETARG_POINTER(0);
- }
- else
- {
- old = MemoryContextSwitchTo(aggcontext);
- state = (UnionBuildState *)palloc(sizeof(UnionBuildState));
-
- state->mcontext = aggcontext;
- state->alen = 10;
- state->ngeoms = 0;
- state->geoms = palloc(sizeof(GEOSGeometry *) * state->alen);
- state->is3d = false;
- state->srid = 0;
- state->empty_type = 0;
+ CollectionBuildState *state;
+ ListCell *l;
+ LWGEOM **geoms;
+ GSERIALIZED *gser_out;
+ size_t ngeoms = 0;
+ int empty_type = 0;
+ bool first = true;
+ int32_t srid = SRID_UNKNOWN;
+ int has_z = LW_FALSE;
- initGEOS(lwpgnotice, lwgeom_geos_error);
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL(); /* returns null iff no input values */
- MemoryContextSwitchTo(old);
- };
+ state = (CollectionBuildState *)PG_GETARG_POINTER(0);
+ geoms = palloc(list_length(state->geoms) * sizeof(LWGEOM*));
- /* do we have geometry to push? */
- if (!PG_ARGISNULL(1))
+ /* Read contents of list into an array of only non-null values */
+ foreach (l, state->geoms)
{
- old = MemoryContextSwitchTo(state->mcontext);
- gser_in = PG_GETARG_GSERIALIZED_P_COPY(1);
- MemoryContextSwitchTo(old);
-
- if (state->ngeoms > 0)
- {
- if (state->srid != gserialized_get_srid(gser_in))
- for (curgeom = 0; curgeom < state->ngeoms; curgeom++)
- GEOSGeom_destroy(state->geoms[curgeom]);
-
- gserialized_error_if_srid_mismatch_reference(gser_in, state->srid, __func__);
- }
-
- if (!gserialized_is_empty(gser_in))
+ LWGEOM *geom = (LWGEOM*)(lfirst(l));
+ if (geom)
{
- if (state->ngeoms == 0)
- {
- state->srid = gserialized_get_srid(gser_in);
- state->is3d = gserialized_has_z(gser_in);
- }
-
- old = MemoryContextSwitchTo(state->mcontext);
- g = POSTGIS2GEOS(gser_in);
- MemoryContextSwitchTo(old);
-
- if (!g)
+ if (!lwgeom_is_empty(geom))
{
- for (curgeom = 0; curgeom < state->ngeoms; curgeom++)
- GEOSGeom_destroy(state->geoms[curgeom]);
- HANDLE_GEOS_ERROR("One of the geometries in the set could not be converted to GEOS");
+ geoms[ngeoms++] = geom;
+ if (first)
+ {
+ srid = lwgeom_get_srid(geom);
+ has_z = lwgeom_has_z(geom);
+ first = false;
+ }
}
-
- curgeom = state->ngeoms;
- state->ngeoms++;
-
- if (state->ngeoms > state->alen)
+ else
{
- old = MemoryContextSwitchTo(state->mcontext);
- state->alen *= 2;
- state->geoms = repalloc(state->geoms, sizeof(GEOSGeometry *) * state->alen);
- MemoryContextSwitchTo(old);
+ int type = lwgeom_get_type(geom);
+ empty_type = type > empty_type ? type : empty_type;
}
-
- state->geoms[curgeom] = g;
- }
- else
- {
- int gser_type = gserialized_get_type(gser_in);
- if (gser_type > state->empty_type)
- state->empty_type = gser_type;
}
}
- PG_RETURN_POINTER(state);
-}
-
-PG_FUNCTION_INFO_V1(pgis_geometry_union_finalfn);
-Datum pgis_geometry_union_finalfn(PG_FUNCTION_ARGS)
-{
- UnionBuildState *state;
- GSERIALIZED *gser_out = NULL;
- GEOSGeometry *g = NULL;
- GEOSGeometry *g_union = NULL;
-
- if (PG_ARGISNULL(0))
- PG_RETURN_NULL(); /* returns null iff no input values */
-
- state = (UnionBuildState *)PG_GETARG_POINTER(0);
-
/*
- ** Take our GEOS geometries and turn them into a GEOS collection,
+ ** Take our array of LWGEOM* and turn it into a GEOS collection,
** then pass that into cascaded union.
*/
- if (state->ngeoms > 0)
+ if (ngeoms > 0)
{
- g = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, state->geoms, state->ngeoms);
+ GEOSGeometry *g = NULL;
+ GEOSGeometry *g_union = NULL;
+ LWCOLLECTION* col = lwcollection_construct(COLLECTIONTYPE, srid, NULL, ngeoms, geoms);
+
+ initGEOS(lwpgnotice, lwgeom_geos_error);
+ g = LWGEOM2GEOS((LWGEOM*)col, LW_FALSE);
if (!g)
HANDLE_GEOS_ERROR("Could not create GEOS COLLECTION from geometry array");
if (!g_union)
HANDLE_GEOS_ERROR("GEOSUnaryUnion");
- GEOSSetSRID(g_union, state->srid);
- gser_out = GEOS2POSTGIS(g_union, state->is3d);
+ GEOSSetSRID(g_union, srid);
+ gser_out = GEOS2POSTGIS(g_union, has_z);
GEOSGeom_destroy(g_union);
}
/* No real geometries in our array, any empties? */
else
{
/* If it was only empties, we'll return the largest type number */
- if (state->empty_type > 0)
+ if (empty_type > 0)
PG_RETURN_POINTER(
- geometry_serialize(lwgeom_construct_empty(state->empty_type, state->srid, state->is3d, 0)));
+ geometry_serialize(lwgeom_construct_empty(empty_type, srid, has_z, 0)));
/* Nothing but NULL, returns NULL */
else
PG_RETURN_POINTER(gser_out);
}
+
+
/**
* @example ST_UnaryUnion {@link #geomunion} SELECT ST_UnaryUnion(
* 'POLYGON((0 0, 10 0, 0 10, 10 10, 0 0))'
AS 'MODULE_PATHNAME'
LANGUAGE 'c' _PARALLEL;
--- Availability: 3.0.0
-CREATE OR REPLACE FUNCTION pgis_geometry_union_transfn(internal, geometry)
- RETURNS internal
- AS 'MODULE_PATHNAME'
- LANGUAGE 'c' _PARALLEL;
-
-- Availability: 1.4.0
-- Changed: 2.5.0 use 'internal' transfer type
CREATE OR REPLACE FUNCTION pgis_geometry_union_finalfn(internal)
-- we don't want to force drop of this agg since its often used in views
-- parallel handling dealt with in postgis_after_upgrade.sql
-- Changed: 2.5.0 use 'internal' stype
--- Changed: 3.0.0 transfn now converts to GEOS
CREATE AGGREGATE ST_Union (geometry) (
- sfunc = pgis_geometry_union_transfn,
+ sfunc = pgis_geometry_accum_transfn,
stype = internal,
#if POSTGIS_PGSQL_VERSION >= 96
parallel = safe,
DROP FUNCTION IF EXISTS st_combine_bbox(box2d, geometry);
DROP FUNCTION IF EXISTS st_distance_sphere(geometry, geometry);
+-- dev function 3.0 cycle
+DROP FUNCTION IF EXISTS pgis_geometry_union_transfn(internal, geometry);
-- pgis_abs type was increased from 8 bytes in 2.1 to 16 bytes in 2.2
-- See #3460
-- This signature was superseeded
DROP FUNCTION IF EXISTS st_buffer(geometry, double precision); -- Does not conflict
-
-- FUNCTION ST_CurveToLine changed to add defaults in 2.5
-- These signatures were superseeded
DROP FUNCTION IF EXISTS ST_CurveToLine(geometry, integer); -- Does not conflict
exit(1);
}
+ $query = "insert into upgrade_test(g1,g2) values ";
+ $query .= "('POINT(0 0)', 'LINESTRING(0 0, 1 1)'), ";
+ $query .= "('POINT(1 0)', 'LINESTRING(0 1, 1 1)');";
+ my $ret = sql($query);
+ unless ( $ret =~ /^INSERT/ ) {
+ `dropdb $DB`;
+ print "\nSomething went wrong populating upgrade_test table: $ret.\n";
+ exit(1);
+ }
+
+ my $query = "create view upgrade_view_test as ";
+ $query .= "select st_union(g1) from upgrade_test;";
+ my $ret = sql($query);
+ unless ( $ret =~ /^CREATE/ ) {
+ `dropdb $DB`;
+ print "\nSomething went wrong creating upgrade_view_test view: $ret.\n";
+ exit(1);
+ }
+
if ( $OPT_WITH_RASTER )
{
$query = "insert into upgrade_test(r) ";
{
# TODO: allow passing the "upgrade-cleanup" script via commandline
+ my $ret = sql("drop view upgrade_view_test;");
+ unless ( $ret =~ /^DROP/ ) {
+ `dropdb $DB`;
+ print "\nSomething went wrong dropping spatial view: $ret.\n";
+ exit(1);
+ }
+
my $ret = sql("drop table upgrade_test;");
unless ( $ret =~ /^DROP/ ) {
`dropdb $DB`;