Regina Obe
6906e4079c
for ST_AsGeoJSON and ST_AsGML References #5139 for PostGIS 3.3.0 Using same solution from #3952
471 lines
12 KiB
C
471 lines
12 KiB
C
/**********************************************************************
|
|
*
|
|
* PostGIS - Spatial Types for PostgreSQL
|
|
* http://postgis.net
|
|
*
|
|
* Copyright (C) 2012 Sandro Santilli <strk@kbt.io>
|
|
*
|
|
* This is free software; you can redistribute and/or modify it under
|
|
* the terms of the GNU General Public Licence. See the COPYING file.
|
|
*
|
|
**********************************************************************/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "catalog/pg_type.h" /* for CSTRINGOID */
|
|
#include "executor/spi.h"
|
|
#include "fmgr.h"
|
|
#include "utils/memutils.h"
|
|
|
|
#include "../postgis_config.h"
|
|
|
|
/* Include for VARATT_EXTERNAL_GET_POINTER */
|
|
#if POSTGIS_PGSQL_VERSION < 130
|
|
#include "access/tuptoaster.h"
|
|
#else
|
|
#include "access/detoast.h"
|
|
#endif
|
|
|
|
#include "lwgeom_cache.h"
|
|
|
|
|
|
/*
|
|
* Generic statement caching infrastructure. We cache
|
|
* the following kinds of objects:
|
|
*
|
|
* geometries-with-trees
|
|
* PreparedGeometry, RTree, CIRC_TREE, RECT_TREE
|
|
*
|
|
* Each GenericCache* has a type, and after that
|
|
* some data. Similar to generic LWGEOM*. Test that
|
|
* the type number is what you expect before casting
|
|
* and de-referencing struct members.
|
|
*/
|
|
typedef struct {
|
|
int type;
|
|
char data[1];
|
|
} GenericCache;
|
|
|
|
/*
|
|
* Although there are only two basic kinds of
|
|
* cache entries, the actual trees stored in the
|
|
* geometries-with-trees pattern are quite diverse,
|
|
* and they might be used in combination, so we have
|
|
* one slot for each tree type.
|
|
*/
|
|
typedef struct {
|
|
GenericCache* entry[NUM_CACHE_ENTRIES];
|
|
} GenericCacheCollection;
|
|
|
|
MemoryContext
|
|
PostgisCacheContext(FunctionCallInfo fcinfo)
|
|
{
|
|
if (!fcinfo->flinfo)
|
|
elog(ERROR, "%s: Could not find upper context", __func__);
|
|
return fcinfo->flinfo->fn_mcxt;
|
|
}
|
|
|
|
/**
|
|
* Get the generic collection off the statement, allocate a
|
|
* new one if we don't have one already.
|
|
*/
|
|
static GenericCacheCollection *
|
|
GetGenericCacheCollection(FunctionCallInfo fcinfo)
|
|
{
|
|
GenericCacheCollection *internal_cache;
|
|
if (!fcinfo->flinfo)
|
|
elog(ERROR, "%s: Could not find upper context", __func__);
|
|
|
|
internal_cache = fcinfo->flinfo->fn_extra;
|
|
|
|
if (!internal_cache)
|
|
{
|
|
internal_cache = MemoryContextAllocZero(PostgisCacheContext(fcinfo), sizeof(GenericCacheCollection));
|
|
fcinfo->flinfo->fn_extra = internal_cache;
|
|
}
|
|
return internal_cache;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get an appropriate (based on the entry type number)
|
|
* GeomCache entry from the generic cache if one exists.
|
|
* Returns a cache pointer if there is a cache hit and we have an
|
|
* index built and ready to use. Returns NULL otherwise.
|
|
*/
|
|
GeomCache *
|
|
GetGeomCache(FunctionCallInfo fcinfo,
|
|
const GeomCacheMethods *cache_methods,
|
|
SHARED_GSERIALIZED *g1,
|
|
SHARED_GSERIALIZED *g2)
|
|
{
|
|
GeomCache* cache;
|
|
int cache_hit = 0;
|
|
MemoryContext old_context;
|
|
const GSERIALIZED *geom;
|
|
GenericCacheCollection* generic_cache = GetGenericCacheCollection(fcinfo);
|
|
uint32_t entry_number = cache_methods->entry_number;
|
|
|
|
Assert(entry_number < NUM_CACHE_ENTRIES);
|
|
|
|
cache = (GeomCache*)(generic_cache->entry[entry_number]);
|
|
if ( ! cache )
|
|
{
|
|
old_context = MemoryContextSwitchTo(PostgisCacheContext(fcinfo));
|
|
/* Allocate in the upper context */
|
|
cache = cache_methods->GeomCacheAllocator();
|
|
MemoryContextSwitchTo(old_context);
|
|
/* Store the pointer in GenericCache */
|
|
cache->type = entry_number;
|
|
generic_cache->entry[entry_number] = (GenericCache*)cache;
|
|
}
|
|
|
|
/* Cache hit on the first argument */
|
|
if (g1 && cache->geom1 && cache->argnum != 2 && shared_gserialized_equal(g1, cache->geom1))
|
|
{
|
|
cache_hit = 1;
|
|
geom = shared_gserialized_get(cache->geom1);
|
|
}
|
|
/* Cache hit on second argument */
|
|
else if (g2 && cache->geom2 && cache->argnum != 1 && shared_gserialized_equal(g2, cache->geom2))
|
|
{
|
|
cache_hit = 2;
|
|
geom = shared_gserialized_get(cache->geom2);
|
|
}
|
|
/* No cache hit. If we have a tree, free it. */
|
|
else
|
|
{
|
|
cache_hit = 0;
|
|
if ( cache->argnum )
|
|
{
|
|
cache_methods->GeomIndexFreer(cache);
|
|
cache->argnum = 0;
|
|
}
|
|
}
|
|
|
|
/* Cache hit, but no tree built yet, build it! */
|
|
if ( cache_hit && ! cache->argnum )
|
|
{
|
|
int rv;
|
|
LWGEOM *lwgeom;
|
|
|
|
/* Save the tree and supporting geometry in the cache */
|
|
/* memory context */
|
|
old_context = MemoryContextSwitchTo(PostgisCacheContext(fcinfo));
|
|
lwgeom = lwgeom_from_gserialized(geom);
|
|
cache->argnum = 0;
|
|
|
|
/* Can't build a tree on a NULL or empty */
|
|
if ((!lwgeom) || lwgeom_is_empty(lwgeom))
|
|
{
|
|
MemoryContextSwitchTo(old_context);
|
|
return NULL;
|
|
}
|
|
rv = cache_methods->GeomIndexBuilder(lwgeom, cache);
|
|
MemoryContextSwitchTo(old_context);
|
|
|
|
/* Something went awry in the tree build phase */
|
|
if ( ! rv )
|
|
return NULL;
|
|
|
|
/* Only set an argnum if everything completely successfully */
|
|
cache->argnum = cache_hit;
|
|
}
|
|
|
|
/* We have a hit and a calculated tree, we're done */
|
|
if ( cache_hit && cache->argnum )
|
|
return cache;
|
|
|
|
/* Argument one didn't match, so copy the new value in. */
|
|
if ( g1 && cache_hit != 1 )
|
|
{
|
|
if (cache->geom1)
|
|
shared_gserialized_unref(fcinfo, cache->geom1);
|
|
cache->geom1 = shared_gserialized_ref(fcinfo, g1);
|
|
}
|
|
|
|
/* Argument two didn't match, so copy the new value in. */
|
|
if ( g2 && cache_hit != 2 )
|
|
{
|
|
if (cache->geom2)
|
|
shared_gserialized_unref(fcinfo, cache->geom2);
|
|
cache->geom2 = shared_gserialized_ref(fcinfo, g2);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
inline static ToastCache*
|
|
ToastCacheGet(FunctionCallInfo fcinfo)
|
|
{
|
|
const uint32_t entry_number = TOAST_CACHE_ENTRY;
|
|
GenericCacheCollection* generic_cache = GetGenericCacheCollection(fcinfo);
|
|
ToastCache* cache = (ToastCache*)(generic_cache->entry[entry_number]);
|
|
if (!cache)
|
|
{
|
|
cache = MemoryContextAllocZero(PostgisCacheContext(fcinfo), sizeof(ToastCache));
|
|
cache->type = entry_number;
|
|
generic_cache->entry[entry_number] = (GenericCache*)cache;
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
SHARED_GSERIALIZED *
|
|
ToastCacheGetGeometry(FunctionCallInfo fcinfo, uint32_t argnum)
|
|
{
|
|
Assert(argnum < ToastCacheSize);
|
|
ToastCache* cache = ToastCacheGet(fcinfo);
|
|
ToastCacheArgument* arg = &(cache->arg[argnum]);
|
|
|
|
Datum datum = PG_GETARG_DATUM(argnum);
|
|
struct varlena *attr = (struct varlena *) DatumGetPointer(datum);
|
|
|
|
/*
|
|
* In general, you have to detoast the whole object to
|
|
* determine if it's different from the cached object, but
|
|
* for toasted objects, the va_valueid and va_toastrelid are
|
|
* a unique key. Only objects toasted to disk have this
|
|
* property, but fortunately those are also the objects
|
|
* that are costliest to de-TOAST, which is why this
|
|
* cache is a performance win.
|
|
* https://www.postgresql.org/message-id/8196.1585870220@sss.pgh.pa.us
|
|
*/
|
|
if (!VARATT_IS_EXTERNAL_ONDISK(attr))
|
|
return shared_gserialized_new_nocache(datum);
|
|
|
|
/* Retrieve the unique keys for this object */
|
|
struct varatt_external ve;
|
|
VARATT_EXTERNAL_GET_POINTER(ve, attr);
|
|
Oid valueid = ve.va_valueid;
|
|
Oid toastrelid = ve.va_toastrelid;
|
|
|
|
/* We've seen this object before? */
|
|
if (arg->valueid == valueid && arg->toastrelid == toastrelid)
|
|
{
|
|
return arg->geom;
|
|
}
|
|
/* New object, clear our old copies and see if it */
|
|
/* shows up a second time before taking a copy */
|
|
else
|
|
{
|
|
if (arg->geom)
|
|
shared_gserialized_unref(fcinfo, arg->geom);
|
|
arg->valueid = valueid;
|
|
arg->toastrelid = toastrelid;
|
|
arg->geom = shared_gserialized_new_cached(fcinfo, datum);
|
|
return arg->geom;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Retrieve an SRS from a given SRID
|
|
* Require valid spatial_ref_sys table entry
|
|
*
|
|
* Could return SRS as short one (i.e EPSG:4326)
|
|
* or as long one: (i.e urn:ogc:def:crs:EPSG::4326)
|
|
*/
|
|
static char *
|
|
getSRSbySRID(FunctionCallInfo fcinfo, int32_t srid, bool short_crs)
|
|
{
|
|
static const uint16_t max_query_size = 512;
|
|
char query[512];
|
|
char *srs, *srscopy;
|
|
int size, err;
|
|
postgis_initialize_cache();
|
|
|
|
if (SPI_OK_CONNECT != SPI_connect())
|
|
{
|
|
elog(NOTICE, "%s: could not connect to SPI manager", __func__);
|
|
SPI_finish();
|
|
return NULL;
|
|
}
|
|
|
|
if (short_crs)
|
|
snprintf(query,
|
|
max_query_size,
|
|
"SELECT auth_name||':'||auth_srid \
|
|
FROM %s WHERE srid='%d'",
|
|
postgis_spatial_ref_sys(),
|
|
srid);
|
|
else
|
|
snprintf(query,
|
|
max_query_size,
|
|
"SELECT 'urn:ogc:def:crs:'||auth_name||'::'||auth_srid \
|
|
FROM %s WHERE srid='%d'",
|
|
postgis_spatial_ref_sys(),
|
|
srid);
|
|
|
|
err = SPI_execute(query, true, 1);
|
|
if (err < 0)
|
|
{
|
|
elog(NOTICE, "%s: error executing query %d", __func__, err);
|
|
SPI_finish();
|
|
return NULL;
|
|
}
|
|
|
|
/* no entry in spatial_ref_sys */
|
|
if (SPI_processed <= 0)
|
|
{
|
|
SPI_finish();
|
|
return NULL;
|
|
}
|
|
|
|
/* get result */
|
|
srs = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
|
|
|
|
/* NULL result */
|
|
if (!srs)
|
|
{
|
|
SPI_finish();
|
|
return NULL;
|
|
}
|
|
|
|
size = strlen(srs) + 1;
|
|
|
|
srscopy = MemoryContextAllocZero(PostgisCacheContext(fcinfo), size);
|
|
memcpy(srscopy, srs, size);
|
|
|
|
/* disconnect from SPI */
|
|
SPI_finish();
|
|
|
|
return srscopy;
|
|
}
|
|
|
|
static inline SRSDescCache *
|
|
SRSDescCacheGet(FunctionCallInfo fcinfo)
|
|
{
|
|
const uint32_t entry_number = SRSDESC_CACHE_ENTRY;
|
|
GenericCacheCollection *generic_cache = GetGenericCacheCollection(fcinfo);
|
|
SRSDescCache *cache = (SRSDescCache *)(generic_cache->entry[entry_number]);
|
|
if (!cache)
|
|
{
|
|
cache = MemoryContextAllocZero(PostgisCacheContext(fcinfo), sizeof(SRSDescCache));
|
|
cache->type = entry_number;
|
|
generic_cache->entry[entry_number] = (GenericCache *)cache;
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
const char *
|
|
GetSRSCacheBySRID(FunctionCallInfo fcinfo, int32_t srid, bool short_crs)
|
|
{
|
|
SRSDescCache *cache = SRSDescCacheGet(fcinfo);
|
|
SRSDescCacheArgument *arg = &(cache->arg[0]);
|
|
|
|
if (arg->srid != srid || arg->short_mode != short_crs || !arg->srs)
|
|
{
|
|
arg->srid = srid;
|
|
arg->short_mode = short_crs;
|
|
if (arg->srs)
|
|
pfree(arg->srs);
|
|
arg->srs = getSRSbySRID(fcinfo, srid, short_crs);
|
|
}
|
|
return arg->srs;
|
|
}
|
|
|
|
/*
|
|
* Retrieve an SRID from a given SRS
|
|
* Require valid spatial_ref_sys table entry
|
|
*
|
|
*/
|
|
static int32_t
|
|
getSRIDbySRS(FunctionCallInfo fcinfo, const char *srs)
|
|
{
|
|
static const int16_t max_query_size = 512;
|
|
char query[512];
|
|
Oid argtypes[] = {CSTRINGOID};
|
|
Datum values[] = {CStringGetDatum(srs)};
|
|
int32_t srid, err;
|
|
|
|
postgis_initialize_cache();
|
|
snprintf(query,
|
|
max_query_size,
|
|
"SELECT srid "
|
|
"FROM %s, "
|
|
"regexp_matches($1::text, E'([a-z]+):([0-9]+)', 'gi') AS re "
|
|
"WHERE re[1] ILIKE auth_name AND int4(re[2]) = auth_srid",
|
|
postgis_spatial_ref_sys());
|
|
|
|
if (!srs)
|
|
return 0;
|
|
|
|
if (SPI_OK_CONNECT != SPI_connect())
|
|
{
|
|
elog(NOTICE, "getSRIDbySRS: could not connect to SPI manager");
|
|
return 0;
|
|
}
|
|
|
|
err = SPI_execute_with_args(query, 1, argtypes, values, NULL, true, 1);
|
|
if (err < 0)
|
|
{
|
|
elog(NOTICE, "getSRIDbySRS: error executing query %d", err);
|
|
SPI_finish();
|
|
return 0;
|
|
}
|
|
|
|
/* no entry in spatial_ref_sys */
|
|
if (SPI_processed <= 0)
|
|
{
|
|
snprintf(query,
|
|
max_query_size,
|
|
"SELECT srid "
|
|
"FROM %s, "
|
|
"regexp_matches($1::text, E'urn:ogc:def:crs:([a-z]+):.*:([0-9]+)', 'gi') AS re "
|
|
"WHERE re[1] ILIKE auth_name AND int4(re[2]) = auth_srid",
|
|
postgis_spatial_ref_sys());
|
|
|
|
err = SPI_execute_with_args(query, 1, argtypes, values, NULL, true, 1);
|
|
if (err < 0)
|
|
{
|
|
elog(NOTICE, "getSRIDbySRS: error executing query %d", err);
|
|
SPI_finish();
|
|
return 0;
|
|
}
|
|
|
|
if (SPI_processed <= 0)
|
|
{
|
|
SPI_finish();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
srid = atoi(SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1));
|
|
SPI_finish();
|
|
|
|
return srid;
|
|
}
|
|
|
|
static inline SRIDCache *
|
|
SRIDCacheGet(FunctionCallInfo fcinfo)
|
|
{
|
|
const uint32_t entry_number = SRID_CACHE_ENTRY;
|
|
GenericCacheCollection *generic_cache = GetGenericCacheCollection(fcinfo);
|
|
SRIDCache *cache = (SRIDCache *)(generic_cache->entry[entry_number]);
|
|
if (!cache)
|
|
{
|
|
cache = MemoryContextAllocZero(PostgisCacheContext(fcinfo), sizeof(SRIDCache));
|
|
cache->type = entry_number;
|
|
generic_cache->entry[entry_number] = (GenericCache *)cache;
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
int32_t
|
|
GetSRIDCacheBySRS(FunctionCallInfo fcinfo, const char *srs)
|
|
{
|
|
SRIDCache *cache = SRIDCacheGet(fcinfo);
|
|
SRIDCacheArgument *arg = &(cache->arg[0]);
|
|
|
|
if (!arg->srid || strcmp(srs, arg->srs) != 0)
|
|
{
|
|
size_t size = strlen(srs) + 1;
|
|
arg->srid = getSRIDbySRS(fcinfo, srs);
|
|
arg->srs = MemoryContextAlloc(PostgisCacheContext(fcinfo), size);
|
|
memcpy(arg->srs, srs, size);
|
|
}
|
|
|
|
return arg->srid;
|
|
}
|