PostGIS 3.0.6dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
lwgeom_geos_prepared.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 * Copyright (C) 2012 Sandro Santilli <strk@kbt.io>
22 * Copyright (C) 2008 Paul Ramsey <pramsey@cleverelephant.ca>
23 * Copyright (C) 2007 Refractions Research Inc.
24 *
25 **********************************************************************/
26
27
28#include <assert.h>
29
30#include "../postgis_config.h"
32#include "lwgeom_cache.h"
33
34/***********************************************************************
35**
36** PreparedGeometry implementations that cache intermediate indexed versions
37** of geometry in a special MemoryContext for re-used by future function
38** invocations.
39**
40** By creating a memory context to hold the GEOS PreparedGeometry and Geometry
41** and making it a child of the fmgr memory context, we can get the memory held
42** by the GEOS objects released when the memory context delete callback is
43** invoked by the parent context.
44**
45** Working parts:
46**
47** PrepGeomCache, the actual struct that holds the keys we compare
48** to determine if our cache is stale, and references to the GEOS
49** objects used in computations.
50**
51** PrepGeomHash, a global hash table that uses a MemoryContext as
52** key and returns a structure holding references to the GEOS
53** objects used in computations.
54**
55** PreparedCacheContextMethods, a set of callback functions that
56** get hooked into a MemoryContext that is in turn used as a
57** key in the PrepGeomHash.
58**
59** All this is to allow us to clean up external malloc'ed objects
60** (the GEOS Geometry and PreparedGeometry) before the structure
61** that references them (PrepGeomCache) is pfree'd by PgSQL. The
62** methods in the PreparedCacheContext are called just before the
63** function context is freed, allowing us to look up the references
64** in the PrepGeomHash and free them before the function context
65** is freed.
66**
67**/
68
69/*
70** Backend prepared hash table
71**
72** The memory context call-backs use a MemoryContext as the parameter
73** so we need to map that over to actual references to GEOS objects to
74** delete.
75**
76** This hash table stores a key/value pair of MemoryContext/Geom* objects.
77*/
78static HTAB* PrepGeomHash = NULL;
79
80#define PREPARED_BACKEND_HASH_SIZE 32
81
82typedef struct
83{
84 MemoryContext context;
85 const GEOSPreparedGeometry* prepared_geom;
86 const GEOSGeometry* geom;
87}
89
90/* Memory context hash table function prototypes */
91uint32 mcxt_ptr_hasha(const void *key, Size keysize);
92static void CreatePrepGeomHash(void);
94static PrepGeomHashEntry *GetPrepGeomHashEntry(MemoryContext mcxt);
95static void DeletePrepGeomHashEntry(MemoryContext mcxt);
96
97
98static void
99#if POSTGIS_PGSQL_VERSION < 96
100PreparedCacheDelete(MemoryContext context)
101{
102#else
104{
105 MemoryContext context = (MemoryContext)ptr;
106#endif
107
108 PrepGeomHashEntry* pghe;
109
110 /* Lookup the hash entry pointer in the global hash table so we can free it */
111 pghe = GetPrepGeomHashEntry(context);
112
113 if (!pghe)
114 elog(ERROR, "PreparedCacheDelete: Trying to delete non-existent hash entry object with MemoryContext key (%p)", (void *)context);
115
116 POSTGIS_DEBUGF(3, "deleting geom object (%p) and prepared geom object (%p) with MemoryContext key (%p)", pghe->geom, pghe->prepared_geom, context);
117
118 /* Free them */
119 if ( pghe->prepared_geom )
120 GEOSPreparedGeom_destroy( pghe->prepared_geom );
121 if ( pghe->geom )
122 GEOSGeom_destroy( (GEOSGeometry *)pghe->geom );
123
124 /* Remove the hash entry as it is no longer needed */
126}
127
128#if POSTGIS_PGSQL_VERSION < 96
129static void
130PreparedCacheInit(MemoryContext context)
131{
132 /*
133 * Do nothing as the cache is initialised when the transform()
134 * function is first called
135 */
136}
137
138static void
139PreparedCacheReset(MemoryContext context)
140{
141 /*
142 * Do nothing, but we must supply a function since this call is mandatory according to tgl
143 * (see postgis-devel archives July 2007)
144 */
145}
146
147static bool
148PreparedCacheIsEmpty(MemoryContext context)
149{
150 /*
151 * Always return false since this call is mandatory according to tgl
152 * (see postgis-devel archives July 2007)
153 */
154 return LW_FALSE;
155}
156
157static void
158PreparedCacheStats(MemoryContext context, int level)
159{
160 /*
161 * Simple stats display function - we must supply a function since this call is mandatory according to tgl
162 * (see postgis-devel archives July 2007)
163 fprintf(stderr, "%s: Prepared context\n", context->name);
164 */
165}
166
167#ifdef MEMORY_CONTEXT_CHECKING
168static void
169PreparedCacheCheck(MemoryContext context)
170{
171 /*
172 * Do nothing - stub required for when PostgreSQL is compiled
173 * with MEMORY_CONTEXT_CHECKING defined
174 */
175}
176#endif
177
178/* Memory context definition must match the current version of PostgreSQL */
179static MemoryContextMethods PreparedCacheContextMethods =
180{
181 NULL,
182 NULL,
183 NULL,
184 PreparedCacheInit,
185 PreparedCacheReset,
187 NULL,
188 PreparedCacheIsEmpty,
189 PreparedCacheStats
190#ifdef MEMORY_CONTEXT_CHECKING
191 , PreparedCacheCheck
192#endif
193};
194
195#endif /* POSTGIS_PGSQL_VERSION < 96 */
196
197
198
199/* TODO: put this in common are for both transform and prepared
200** mcxt_ptr_hash
201** Build a key from a pointer and a size value.
202*/
203uint32
204mcxt_ptr_hasha(const void *key, Size keysize)
205{
206 uint32 hashval;
207
208 hashval = DatumGetUInt32(hash_any(key, keysize));
209
210 return hashval;
211}
212
213static void
215{
216 HASHCTL ctl;
217
218 ctl.keysize = sizeof(MemoryContext);
219 ctl.entrysize = sizeof(PrepGeomHashEntry);
220 ctl.hash = mcxt_ptr_hasha;
221
222 PrepGeomHash = hash_create("PostGIS Prepared Geometry Backend MemoryContext Hash", PREPARED_BACKEND_HASH_SIZE, &ctl, (HASH_ELEM | HASH_FUNCTION));
223}
224
225static void
227{
228 bool found;
229 void **key;
231
232 /* The hash key is the MemoryContext pointer */
233 key = (void *)&(pghe.context);
234
235 he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_ENTER, &found);
236 if (!found)
237 {
238 /* Insert the entry into the new hash element */
239 he->context = pghe.context;
240 he->geom = pghe.geom;
241 he->prepared_geom = pghe.prepared_geom;
242 }
243 else
244 {
245 elog(ERROR, "AddPrepGeomHashEntry: This memory context is already in use! (%p)", (void *)pghe.context);
246 }
247}
248
249static PrepGeomHashEntry*
250GetPrepGeomHashEntry(MemoryContext mcxt)
251{
252 void **key;
254
255 /* The hash key is the MemoryContext pointer */
256 key = (void *)&mcxt;
257
258 /* Return the projection object from the hash */
259 he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_FIND, NULL);
260
261 return he;
262}
263
264
265static void
266DeletePrepGeomHashEntry(MemoryContext mcxt)
267{
268 void **key;
270
271 /* The hash key is the MemoryContext pointer */
272 key = (void *)&mcxt;
273
274 /* Delete the projection object from the hash */
275 he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_REMOVE, NULL);
276
277 if (!he)
278 {
279 elog(ERROR, "DeletePrepGeomHashEntry: There was an error removing the geometry object from this MemoryContext (%p)", (void *)mcxt);
280 }
281
282 he->prepared_geom = NULL;
283 he->geom = NULL;
284}
285
298static int
299PrepGeomCacheBuilder(const LWGEOM *lwgeom, GeomCache *cache)
300{
301 PrepGeomCache* prepcache = (PrepGeomCache*)cache;
302 PrepGeomHashEntry* pghe;
303
304 /*
305 * First time through? allocate the global hash.
306 */
307 if (!PrepGeomHash)
309
310 /*
311 * No callback entry for this statement context yet? Set it up
312 */
313 if ( ! prepcache->context_callback )
314 {
316#if POSTGIS_PGSQL_VERSION < 96
317 prepcache->context_callback = MemoryContextCreate(T_AllocSetContext, 8192,
318 &PreparedCacheContextMethods,
319 prepcache->context_statement,
320 "PostGIS Prepared Geometry Context");
321
322#else
323 MemoryContextCallback *callback;
324 prepcache->context_callback = AllocSetContextCreate(prepcache->context_statement,
325 "PostGIS Prepared Geometry Context",
326 ALLOCSET_SMALL_SIZES);
327
328 /* PgSQL comments suggest allocating callback in the context */
329 /* being managed, so that the callback object gets cleaned along with */
330 /* the context */
331 callback = MemoryContextAlloc(prepcache->context_callback, sizeof(MemoryContextCallback));
332 callback->arg = (void*)(prepcache->context_callback);
333 callback->func = PreparedCacheDelete;
334 MemoryContextRegisterResetCallback(prepcache->context_callback, callback);
335#endif
336
337 pghe.context = prepcache->context_callback;
338 pghe.geom = 0;
339 pghe.prepared_geom = 0;
340 AddPrepGeomHashEntry( pghe );
341 }
342
343 /*
344 * Hum, we shouldn't be asked to build a new cache on top of
345 * an existing one. Error.
346 */
347 if ( prepcache->gcache.argnum || prepcache->geom || prepcache->prepared_geom )
348 {
349 lwpgerror("PrepGeomCacheBuilder asked to build new prepcache where one already exists.");
350 return LW_FAILURE;
351 }
352
353 prepcache->geom = LWGEOM2GEOS( lwgeom , 0);
354 if ( ! prepcache->geom ) return LW_FAILURE;
355 prepcache->prepared_geom = GEOSPrepare( prepcache->geom );
356 if ( ! prepcache->prepared_geom ) return LW_FAILURE;
357 prepcache->gcache.argnum = cache->argnum;
358
359 /*
360 * In order to find the objects we need to destroy, we keep
361 * extra references in a global hash object.
362 */
363 pghe = GetPrepGeomHashEntry(prepcache->context_callback);
364 if ( ! pghe )
365 {
366 lwpgerror("PrepGeomCacheBuilder failed to find hash entry for context %p", prepcache->context_callback);
367 return LW_FAILURE;
368 }
369
370 pghe->geom = prepcache->geom;
371 pghe->prepared_geom = prepcache->prepared_geom;
372
373 return LW_SUCCESS;
374}
375
386static int
387PrepGeomCacheCleaner(GeomCache *cache)
388{
389 PrepGeomHashEntry* pghe = 0;
390 PrepGeomCache* prepcache = (PrepGeomCache*)cache;
391
392 if ( ! prepcache )
393 return LW_FAILURE;
394
395 /*
396 * Clear out the references to the soon-to-be-freed GEOS objects
397 * from the callback hash entry
398 */
399 pghe = GetPrepGeomHashEntry(prepcache->context_callback);
400 if ( ! pghe )
401 {
402 lwpgerror("PrepGeomCacheCleaner failed to find hash entry for context %p", prepcache->context_callback);
403 return LW_FAILURE;
404 }
405 pghe->geom = 0;
406 pghe->prepared_geom = 0;
407
408 /*
409 * Free the GEOS objects and free the index tree
410 */
411 POSTGIS_DEBUGF(3, "PrepGeomCacheFreeer: freeing %p argnum %d", prepcache, prepcache->gcache.argnum);
412 GEOSPreparedGeom_destroy( prepcache->prepared_geom );
413 GEOSGeom_destroy( (GEOSGeometry *)prepcache->geom );
414 prepcache->gcache.argnum = 0;
415 prepcache->prepared_geom = 0;
416 prepcache->geom = 0;
417
418 return LW_SUCCESS;
419}
420
421static GeomCache*
423{
424 PrepGeomCache* prepcache = palloc(sizeof(PrepGeomCache));
425 memset(prepcache, 0, sizeof(PrepGeomCache));
426 prepcache->context_statement = CurrentMemoryContext;
427 prepcache->gcache.type = PREP_CACHE_ENTRY;
428 return (GeomCache*)prepcache;
429}
430
431static GeomCacheMethods PrepGeomCacheMethods =
432{
433 PREP_CACHE_ENTRY,
437};
438
439
452GetPrepGeomCache(FunctionCallInfo fcinfo, GSERIALIZED *g1, GSERIALIZED *g2)
453{
454 return (PrepGeomCache*)GetGeomCache(fcinfo, &PrepGeomCacheMethods, g1, g2);
455}
456
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
#define LW_FALSE
Definition liblwgeom.h:108
#define LW_FAILURE
Definition liblwgeom.h:110
#define LW_SUCCESS
Definition liblwgeom.h:111
static void CreatePrepGeomHash(void)
static HTAB * PrepGeomHash
static void AddPrepGeomHashEntry(PrepGeomHashEntry pghe)
uint32 mcxt_ptr_hasha(const void *key, Size keysize)
static GeomCacheMethods PrepGeomCacheMethods
static int PrepGeomCacheBuilder(const LWGEOM *lwgeom, GeomCache *cache)
Given a generic GeomCache, and a geometry to prepare, prepare a PrepGeomCache and stick it into the G...
static int PrepGeomCacheCleaner(GeomCache *cache)
This function is passed into the generic GetGeomCache function in the case of a cache miss,...
static void PreparedCacheDelete(void *ptr)
static void DeletePrepGeomHashEntry(MemoryContext mcxt)
#define PREPARED_BACKEND_HASH_SIZE
static GeomCache * PrepGeomCacheAllocator()
PrepGeomCache * GetPrepGeomCache(FunctionCallInfo fcinfo, GSERIALIZED *g1, GSERIALIZED *g2)
Given a couple potential geometries and a function call context, return a prepared structure for one ...
static PrepGeomHashEntry * GetPrepGeomHashEntry(MemoryContext mcxt)
const GEOSPreparedGeometry * prepared_geom
MemoryContext context_statement
const GEOSGeometry * geom
MemoryContext context_callback
const GEOSGeometry * geom
const GEOSPreparedGeometry * prepared_geom