PostGIS  3.4.0dev-r@@SVN_REVISION@@
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"
31 #include "lwgeom_geos_prepared.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 */
78 static HTAB* PrepGeomHash = NULL;
79 
80 #define PREPARED_BACKEND_HASH_SIZE 32
81 
82 typedef struct
83 {
84  MemoryContext context;
85  const GEOSPreparedGeometry* prepared_geom;
86  const GEOSGeometry* geom;
87 }
89 
90 /* Memory context hash table function prototypes */
91 uint32 mcxt_ptr_hasha(const void *key, Size keysize);
92 static void CreatePrepGeomHash(void);
93 static void AddPrepGeomHashEntry(PrepGeomHashEntry pghe);
94 static PrepGeomHashEntry *GetPrepGeomHashEntry(MemoryContext mcxt);
95 static void DeletePrepGeomHashEntry(MemoryContext mcxt);
96 
97 
98 static 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 */
120  DeletePrepGeomHashEntry(context);
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 */
128 uint32
129 mcxt_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 
138 static 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 
150 static void
152 {
153  bool found;
154  void **key;
155  PrepGeomHashEntry *he;
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 
174 static PrepGeomHashEntry*
175 GetPrepGeomHashEntry(MemoryContext mcxt)
176 {
177  void **key;
178  PrepGeomHashEntry *he;
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 
190 static void
191 DeletePrepGeomHashEntry(MemoryContext mcxt)
192 {
193  void **key;
194  PrepGeomHashEntry *he;
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 
223 static int
224 PrepGeomCacheBuilder(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  {
240  PrepGeomHashEntry pghe;
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", 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 
303 static int
304 PrepGeomCacheCleaner(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 
338 static 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 
348 static GeomCacheMethods PrepGeomCacheMethods =
349 {
350  PREP_CACHE_ENTRY,
354 };
355 
356 
369 GetPrepGeomCache(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 GeomCache * PrepGeomCacheAllocator()
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 PrepGeomHashEntry * GetPrepGeomHashEntry(MemoryContext mcxt)
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 ...
const GEOSPreparedGeometry * prepared_geom
MemoryContext context_statement
const GEOSGeometry * geom
MemoryContext context_callback
const GEOSGeometry * geom
const GEOSPreparedGeometry * prepared_geom