PostGIS 3.7.0dev-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 reused 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
100{
101 MemoryContext context = (MemoryContext)ptr;
102
103 PrepGeomHashEntry* pghe;
104
105 /* Lookup the hash entry pointer in the global hash table so we can free it */
106 pghe = GetPrepGeomHashEntry(context);
107
108 if (!pghe)
109 elog(ERROR, "PreparedCacheDelete: Trying to delete non-existent hash entry object with MemoryContext key (%p)", (void *)context);
110
111 POSTGIS_DEBUGF(3, "deleting geom object (%p) and prepared geom object (%p) with MemoryContext key (%p)", pghe->geom, pghe->prepared_geom, context);
112
113 /* Free them */
114 if ( pghe->prepared_geom )
115 GEOSPreparedGeom_destroy( pghe->prepared_geom );
116 if ( pghe->geom )
117 GEOSGeom_destroy( (GEOSGeometry *)pghe->geom );
118
119 /* Remove the hash entry as it is no longer needed */
121}
122
123
124/* TODO: put this in common are for both transform and prepared
125** mcxt_ptr_hash
126** Build a key from a pointer and a size value.
127*/
128uint32
129mcxt_ptr_hasha(const void *key, Size keysize)
130{
131 uint32 hashval;
132
133 hashval = DatumGetUInt32(hash_any(key, keysize));
134
135 return hashval;
136}
137
138static void
140{
141 HASHCTL ctl;
142
143 ctl.keysize = sizeof(MemoryContext);
144 ctl.entrysize = sizeof(PrepGeomHashEntry);
145 ctl.hash = mcxt_ptr_hasha;
146
147 PrepGeomHash = hash_create("PostGIS Prepared Geometry Backend MemoryContext Hash", PREPARED_BACKEND_HASH_SIZE, &ctl, (HASH_ELEM | HASH_FUNCTION));
148}
149
150static void
152{
153 bool found;
154 void **key;
156
157 /* The hash key is the MemoryContext pointer */
158 key = (void *)&(pghe.context);
159
160 he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_ENTER, &found);
161 if (!found)
162 {
163 /* Insert the entry into the new hash element */
164 he->context = pghe.context;
165 he->geom = pghe.geom;
166 he->prepared_geom = pghe.prepared_geom;
167 }
168 else
169 {
170 elog(ERROR, "AddPrepGeomHashEntry: This memory context is already in use! (%p)", (void *)pghe.context);
171 }
172}
173
174static PrepGeomHashEntry*
175GetPrepGeomHashEntry(MemoryContext mcxt)
176{
177 void **key;
179
180 /* The hash key is the MemoryContext pointer */
181 key = (void *)&mcxt;
182
183 /* Return the projection object from the hash */
184 he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_FIND, NULL);
185
186 return he;
187}
188
189
190static void
191DeletePrepGeomHashEntry(MemoryContext mcxt)
192{
193 void **key;
195
196 /* The hash key is the MemoryContext pointer */
197 key = (void *)&mcxt;
198
199 /* Delete the projection object from the hash */
200 he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_REMOVE, NULL);
201
202 if (!he)
203 {
204 elog(ERROR, "DeletePrepGeomHashEntry: There was an error removing the geometry object from this MemoryContext (%p)", (void *)mcxt);
205 }
206
207 he->prepared_geom = NULL;
208 he->geom = NULL;
209}
210
223static int
224PrepGeomCacheBuilder(const LWGEOM *lwgeom, GeomCache *cache)
225{
226 PrepGeomCache* prepcache = (PrepGeomCache*)cache;
227 PrepGeomHashEntry* pghe;
228
229 /*
230 * First time through? allocate the global hash.
231 */
232 if (!PrepGeomHash)
234
235 /*
236 * No callback entry for this statement context yet? Set it up
237 */
238 if ( ! prepcache->context_callback )
239 {
241 MemoryContextCallback *callback;
242 prepcache->context_callback = AllocSetContextCreate(prepcache->context_statement,
243 "PostGIS Prepared Geometry Context",
244 ALLOCSET_SMALL_SIZES);
245
246 /* PgSQL comments suggest allocating callback in the context */
247 /* being managed, so that the callback object gets cleaned along with */
248 /* the context */
249 callback = MemoryContextAlloc(prepcache->context_callback, sizeof(MemoryContextCallback));
250 callback->arg = (void*)(prepcache->context_callback);
251 callback->func = PreparedCacheDelete;
252 MemoryContextRegisterResetCallback(prepcache->context_callback, callback);
253
254 pghe.context = prepcache->context_callback;
255 pghe.geom = 0;
256 pghe.prepared_geom = 0;
257 AddPrepGeomHashEntry( pghe );
258 }
259
260 /*
261 * Hum, we shouldn't be asked to build a new cache on top of
262 * an existing one. Error.
263 */
264 if ( prepcache->gcache.argnum || prepcache->geom || prepcache->prepared_geom )
265 {
266 lwpgerror("PrepGeomCacheBuilder asked to build new prepcache where one already exists.");
267 return LW_FAILURE;
268 }
269
270 prepcache->geom = LWGEOM2GEOS( lwgeom , 0);
271 if ( ! prepcache->geom ) return LW_FAILURE;
272 prepcache->prepared_geom = GEOSPrepare( prepcache->geom );
273 if ( ! prepcache->prepared_geom ) return LW_FAILURE;
274 prepcache->gcache.argnum = cache->argnum;
275
276 /*
277 * In order to find the objects we need to destroy, we keep
278 * extra references in a global hash object.
279 */
280 pghe = GetPrepGeomHashEntry(prepcache->context_callback);
281 if ( ! pghe )
282 {
283 lwpgerror("PrepGeomCacheBuilder failed to find hash entry for context %p", (void *)prepcache->context_callback);
284 return LW_FAILURE;
285 }
286
287 pghe->geom = prepcache->geom;
288 pghe->prepared_geom = prepcache->prepared_geom;
289
290 return LW_SUCCESS;
291}
292
303static int
304PrepGeomCacheCleaner(GeomCache *cache)
305{
306 PrepGeomHashEntry* pghe = 0;
307 PrepGeomCache* prepcache = (PrepGeomCache*)cache;
308
309 if ( ! prepcache )
310 return LW_FAILURE;
311
312 /*
313 * Clear out the references to the soon-to-be-freed GEOS objects
314 * from the callback hash entry
315 */
316 pghe = GetPrepGeomHashEntry(prepcache->context_callback);
317 if ( ! pghe )
318 {
319 lwpgerror("PrepGeomCacheCleaner failed to find hash entry for context %p", prepcache->context_callback);
320 return LW_FAILURE;
321 }
322 pghe->geom = 0;
323 pghe->prepared_geom = 0;
324
325 /*
326 * Free the GEOS objects and free the index tree
327 */
328 POSTGIS_DEBUGF(3, "PrepGeomCacheFreeer: freeing %p argnum %d", prepcache, prepcache->gcache.argnum);
329 GEOSPreparedGeom_destroy( prepcache->prepared_geom );
330 GEOSGeom_destroy( (GEOSGeometry *)prepcache->geom );
331 prepcache->gcache.argnum = 0;
332 prepcache->prepared_geom = 0;
333 prepcache->geom = 0;
334
335 return LW_SUCCESS;
336}
337
338static GeomCache*
340{
341 PrepGeomCache* prepcache = palloc(sizeof(PrepGeomCache));
342 memset(prepcache, 0, sizeof(PrepGeomCache));
343 prepcache->context_statement = CurrentMemoryContext;
344 prepcache->gcache.type = PREP_CACHE_ENTRY;
345 return (GeomCache*)prepcache;
346}
347
348static GeomCacheMethods PrepGeomCacheMethods =
349{
350 PREP_CACHE_ENTRY,
354};
355
356
369GetPrepGeomCache(FunctionCallInfo fcinfo, SHARED_GSERIALIZED *g1, SHARED_GSERIALIZED *g2)
370{
371 return (PrepGeomCache*)GetGeomCache(fcinfo, &PrepGeomCacheMethods, g1, g2);
372}
373
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
#define LW_FAILURE
Definition liblwgeom.h:96
#define LW_SUCCESS
Definition liblwgeom.h:97
static void CreatePrepGeomHash(void)
PrepGeomCache * GetPrepGeomCache(FunctionCallInfo fcinfo, SHARED_GSERIALIZED *g1, SHARED_GSERIALIZED *g2)
Given a couple potential geometries and a function call context, return a prepared structure for one ...
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()
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