PostGIS  2.5.7dev-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
99 #if POSTGIS_PGSQL_VERSION < 96
100 PreparedCacheDelete(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 */
125  DeletePrepGeomHashEntry(context);
126 }
127 
128 #if POSTGIS_PGSQL_VERSION < 96
129 static void
130 PreparedCacheInit(MemoryContext context)
131 {
132  /*
133  * Do nothing as the cache is initialised when the transform()
134  * function is first called
135  */
136 }
137 
138 static void
139 PreparedCacheReset(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 
147 static bool
148 PreparedCacheIsEmpty(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 
157 static void
158 PreparedCacheStats(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
168 static void
169 PreparedCacheCheck(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 */
179 static 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 */
203 uint32
204 mcxt_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 
213 static 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 
225 static void
227 {
228  bool found;
229  void **key;
230  PrepGeomHashEntry *he;
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 
249 static PrepGeomHashEntry*
250 GetPrepGeomHashEntry(MemoryContext mcxt)
251 {
252  void **key;
253  PrepGeomHashEntry *he;
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 
265 static void
266 DeletePrepGeomHashEntry(MemoryContext mcxt)
267 {
268  void **key;
269  PrepGeomHashEntry *he;
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 
298 static int
299 PrepGeomCacheBuilder(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  {
315  PrepGeomHashEntry pghe;
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  /*
354  * Avoid creating a PreparedPoint around a Point or a MultiPoint.
355  * Consider changing this behavior in the future if supported GEOS
356  * versions correctly handle prepared points and multipoints and
357  * provide a performance benefit.
358  * See https://trac.osgeo.org/postgis/ticket/3437
359  */
360  if (lwgeom_get_type(lwgeom) == POINTTYPE || lwgeom_get_type(lwgeom) == MULTIPOINTTYPE)
361  return LW_FAILURE;
362 
363  prepcache->geom = LWGEOM2GEOS( lwgeom , 0);
364  if ( ! prepcache->geom ) return LW_FAILURE;
365  prepcache->prepared_geom = GEOSPrepare( prepcache->geom );
366  if ( ! prepcache->prepared_geom ) return LW_FAILURE;
367  prepcache->gcache.argnum = cache->argnum;
368 
369  /*
370  * In order to find the objects we need to destroy, we keep
371  * extra references in a global hash object.
372  */
373  pghe = GetPrepGeomHashEntry(prepcache->context_callback);
374  if ( ! pghe )
375  {
376  lwpgerror("PrepGeomCacheBuilder failed to find hash entry for context %p", prepcache->context_callback);
377  return LW_FAILURE;
378  }
379 
380  pghe->geom = prepcache->geom;
381  pghe->prepared_geom = prepcache->prepared_geom;
382 
383  return LW_SUCCESS;
384 }
385 
396 static int
397 PrepGeomCacheCleaner(GeomCache *cache)
398 {
399  PrepGeomHashEntry* pghe = 0;
400  PrepGeomCache* prepcache = (PrepGeomCache*)cache;
401 
402  if ( ! prepcache )
403  return LW_FAILURE;
404 
405  /*
406  * Clear out the references to the soon-to-be-freed GEOS objects
407  * from the callback hash entry
408  */
409  pghe = GetPrepGeomHashEntry(prepcache->context_callback);
410  if ( ! pghe )
411  {
412  lwpgerror("PrepGeomCacheCleaner failed to find hash entry for context %p", prepcache->context_callback);
413  return LW_FAILURE;
414  }
415  pghe->geom = 0;
416  pghe->prepared_geom = 0;
417 
418  /*
419  * Free the GEOS objects and free the index tree
420  */
421  POSTGIS_DEBUGF(3, "PrepGeomCacheFreeer: freeing %p argnum %d", prepcache, prepcache->gcache.argnum);
422  GEOSPreparedGeom_destroy( prepcache->prepared_geom );
423  GEOSGeom_destroy( (GEOSGeometry *)prepcache->geom );
424  prepcache->gcache.argnum = 0;
425  prepcache->prepared_geom = 0;
426  prepcache->geom = 0;
427 
428  return LW_SUCCESS;
429 }
430 
431 static GeomCache*
433 {
434  PrepGeomCache* prepcache = palloc(sizeof(PrepGeomCache));
435  memset(prepcache, 0, sizeof(PrepGeomCache));
436  prepcache->context_statement = CurrentMemoryContext;
437  prepcache->gcache.type = PREP_CACHE_ENTRY;
438  return (GeomCache*)prepcache;
439 }
440 
441 static GeomCacheMethods PrepGeomCacheMethods =
442 {
443  PREP_CACHE_ENTRY,
447 };
448 
449 
462 GetPrepGeomCache(FunctionCallInfo fcinfo, GSERIALIZED *g1, GSERIALIZED *g2)
463 {
464  return (PrepGeomCache*)GetGeomCache(fcinfo, &PrepGeomCacheMethods, g1, g2);
465 }
466 
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, uint8_t autofix)
#define LW_FALSE
Definition: liblwgeom.h:77
#define LW_FAILURE
Definition: liblwgeom.h:79
#define LW_SUCCESS
Definition: liblwgeom.h:80
#define MULTIPOINTTYPE
Definition: liblwgeom.h:88
uint32_t lwgeom_get_type(const LWGEOM *geom)
Return LWTYPE number.
Definition: lwgeom.c:923
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:85
static GeomCache * PrepGeomCacheAllocator()
static void CreatePrepGeomHash(void)
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 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)
const GEOSPreparedGeometry * prepared_geom
MemoryContext context_statement
const GEOSGeometry * geom
MemoryContext context_callback
const GEOSGeometry * geom
const GEOSPreparedGeometry * prepared_geom
#define HASH_FIND(hh, head, keyptr, keylen, out)
Definition: uthash.h:132