PostGIS  2.2.8dev-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  * Copyright (C) 2012 Sandro Santilli <strk@keybit.net>
7  * Copyright (C) 2008 Paul Ramsey <pramsey@cleverelephant.ca>
8  * Copyright (C) 2007 Refractions Research Inc.
9  *
10  * This is free software; you can redistribute and/or modify it under
11  * the terms of the GNU General Public Licence. See the COPYING file.
12  *
13  **********************************************************************/
14 
15 #include <assert.h>
16 
17 #include "../postgis_config.h"
18 #include "lwgeom_geos_prepared.h"
19 #include "lwgeom_cache.h"
20 
21 /***********************************************************************
22 **
23 ** PreparedGeometry implementations that cache intermediate indexed versions
24 ** of geometry in a special MemoryContext for re-used by future function
25 ** invocations.
26 **
27 ** By creating a memory context to hold the GEOS PreparedGeometry and Geometry
28 ** and making it a child of the fmgr memory context, we can get the memory held
29 ** by the GEOS objects released when the memory context delete callback is
30 ** invoked by the parent context.
31 **
32 ** Working parts:
33 **
34 ** PrepGeomCache, the actual struct that holds the keys we compare
35 ** to determine if our cache is stale, and references to the GEOS
36 ** objects used in computations.
37 **
38 ** PrepGeomHash, a global hash table that uses a MemoryContext as
39 ** key and returns a structure holding references to the GEOS
40 ** objects used in computations.
41 **
42 ** PreparedCacheContextMethods, a set of callback functions that
43 ** get hooked into a MemoryContext that is in turn used as a
44 ** key in the PrepGeomHash.
45 **
46 ** All this is to allow us to clean up external malloc'ed objects
47 ** (the GEOS Geometry and PreparedGeometry) before the structure
48 ** that references them (PrepGeomCache) is pfree'd by PgSQL. The
49 ** methods in the PreparedCacheContext are called just before the
50 ** function context is freed, allowing us to look up the references
51 ** in the PrepGeomHash and free them before the function context
52 ** is freed.
53 **
54 **/
55 
56 /*
57 ** Backend prepared hash table
58 **
59 ** The memory context call-backs use a MemoryContext as the parameter
60 ** so we need to map that over to actual references to GEOS objects to
61 ** delete.
62 **
63 ** This hash table stores a key/value pair of MemoryContext/Geom* objects.
64 */
65 static HTAB* PrepGeomHash = NULL;
66 
67 #define PREPARED_BACKEND_HASH_SIZE 32
68 
69 typedef struct
70 {
71  MemoryContext context;
72  const GEOSPreparedGeometry* prepared_geom;
73  const GEOSGeometry* geom;
74 }
76 
77 /* Memory context hash table function prototypes */
78 uint32 mcxt_ptr_hasha(const void *key, Size keysize);
79 static void CreatePrepGeomHash(void);
80 static void AddPrepGeomHashEntry(PrepGeomHashEntry pghe);
81 static PrepGeomHashEntry *GetPrepGeomHashEntry(MemoryContext mcxt);
82 static void DeletePrepGeomHashEntry(MemoryContext mcxt);
83 
84 /* Memory context cache function prototypes */
85 static void PreparedCacheInit(MemoryContext context);
86 static void PreparedCacheReset(MemoryContext context);
87 static void PreparedCacheDelete(MemoryContext context);
88 static bool PreparedCacheIsEmpty(MemoryContext context);
89 static void PreparedCacheStats(MemoryContext context, int level);
90 #ifdef MEMORY_CONTEXT_CHECKING
91 static void PreparedCacheCheck(MemoryContext context);
92 #endif
93 
94 /* Memory context definition must match the current version of PostgreSQL */
95 static MemoryContextMethods PreparedCacheContextMethods =
96 {
97  NULL,
98  NULL,
99  NULL,
103  NULL,
106 #ifdef MEMORY_CONTEXT_CHECKING
107  , PreparedCacheCheck
108 #endif
109 };
110 
111 static void
112 PreparedCacheInit(MemoryContext context)
113 {
114  /*
115  * Do nothing as the cache is initialised when the transform()
116  * function is first called
117  */
118 }
119 
120 static void
121 PreparedCacheDelete(MemoryContext context)
122 {
123  PrepGeomHashEntry* pghe;
124 
125  /* Lookup the hash entry pointer in the global hash table so we can free it */
126  pghe = GetPrepGeomHashEntry(context);
127 
128  if (!pghe)
129  elog(ERROR, "PreparedCacheDelete: Trying to delete non-existant hash entry object with MemoryContext key (%p)", (void *)context);
130 
131  POSTGIS_DEBUGF(3, "deleting geom object (%p) and prepared geom object (%p) with MemoryContext key (%p)", pghe->geom, pghe->prepared_geom, context);
132 
133  /* Free them */
134  if ( pghe->prepared_geom )
135  GEOSPreparedGeom_destroy( pghe->prepared_geom );
136  if ( pghe->geom )
137  GEOSGeom_destroy( (GEOSGeometry *)pghe->geom );
138 
139  /* Remove the hash entry as it is no longer needed */
140  DeletePrepGeomHashEntry(context);
141 }
142 
143 static void
144 PreparedCacheReset(MemoryContext context)
145 {
146  /*
147  * Do nothing, but we must supply a function since this call is mandatory according to tgl
148  * (see postgis-devel archives July 2007)
149  */
150 }
151 
152 static bool
153 PreparedCacheIsEmpty(MemoryContext context)
154 {
155  /*
156  * Always return false since this call is mandatory according to tgl
157  * (see postgis-devel archives July 2007)
158  */
159  return FALSE;
160 }
161 
162 static void
163 PreparedCacheStats(MemoryContext context, int level)
164 {
165  /*
166  * Simple stats display function - we must supply a function since this call is mandatory according to tgl
167  * (see postgis-devel archives July 2007)
168  */
169 
170  fprintf(stderr, "%s: Prepared context\n", context->name);
171 }
172 
173 #ifdef MEMORY_CONTEXT_CHECKING
174 static void
175 PreparedCacheCheck(MemoryContext context)
176 {
177  /*
178  * Do nothing - stub required for when PostgreSQL is compiled
179  * with MEMORY_CONTEXT_CHECKING defined
180  */
181 }
182 #endif
183 
184 /* TODO: put this in common are for both transform and prepared
185 ** mcxt_ptr_hash
186 ** Build a key from a pointer and a size value.
187 */
188 uint32
189 mcxt_ptr_hasha(const void *key, Size keysize)
190 {
191  uint32 hashval;
192 
193  hashval = DatumGetUInt32(hash_any(key, keysize));
194 
195  return hashval;
196 }
197 
198 static void
200 {
201  HASHCTL ctl;
202 
203  ctl.keysize = sizeof(MemoryContext);
204  ctl.entrysize = sizeof(PrepGeomHashEntry);
205  ctl.hash = mcxt_ptr_hasha;
206 
207  PrepGeomHash = hash_create("PostGIS Prepared Geometry Backend MemoryContext Hash", PREPARED_BACKEND_HASH_SIZE, &ctl, (HASH_ELEM | HASH_FUNCTION));
208 }
209 
210 static void
212 {
213  bool found;
214  void **key;
215  PrepGeomHashEntry *he;
216 
217  /* The hash key is the MemoryContext pointer */
218  key = (void *)&(pghe.context);
219 
220  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_ENTER, &found);
221  if (!found)
222  {
223  /* Insert the entry into the new hash element */
224  he->context = pghe.context;
225  he->geom = pghe.geom;
226  he->prepared_geom = pghe.prepared_geom;
227  }
228  else
229  {
230  elog(ERROR, "AddPrepGeomHashEntry: This memory context is already in use! (%p)", (void *)pghe.context);
231  }
232 }
233 
234 static PrepGeomHashEntry*
235 GetPrepGeomHashEntry(MemoryContext mcxt)
236 {
237  void **key;
238  PrepGeomHashEntry *he;
239 
240  /* The hash key is the MemoryContext pointer */
241  key = (void *)&mcxt;
242 
243  /* Return the projection object from the hash */
244  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_FIND, NULL);
245 
246  return he;
247 }
248 
249 
250 static void
251 DeletePrepGeomHashEntry(MemoryContext mcxt)
252 {
253  void **key;
254  PrepGeomHashEntry *he;
255 
256  /* The hash key is the MemoryContext pointer */
257  key = (void *)&mcxt;
258 
259  /* Delete the projection object from the hash */
260  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_REMOVE, NULL);
261 
262  if (!he)
263  {
264  elog(ERROR, "DeletePrepGeomHashEntry: There was an error removing the geometry object from this MemoryContext (%p)", (void *)mcxt);
265  }
266 
267  he->prepared_geom = NULL;
268  he->geom = NULL;
269 }
270 
283 static int
284 PrepGeomCacheBuilder(const LWGEOM *lwgeom, GeomCache *cache)
285 {
286  PrepGeomCache* prepcache = (PrepGeomCache*)cache;
287  PrepGeomHashEntry* pghe;
288 
289  /*
290  * First time through? allocate the global hash.
291  */
292  if (!PrepGeomHash)
294 
295  /*
296  * No callback entry for this statement context yet? Set it up
297  */
298  if ( ! prepcache->context_callback )
299  {
300  PrepGeomHashEntry pghe;
301  prepcache->context_callback = MemoryContextCreate(T_AllocSetContext, 8192,
303  prepcache->context_statement,
304  "PostGIS Prepared Geometry Context");
305  pghe.context = prepcache->context_callback;
306  pghe.geom = 0;
307  pghe.prepared_geom = 0;
308  AddPrepGeomHashEntry( pghe );
309  }
310 
311  /*
312  * Hum, we shouldn't be asked to build a new cache on top of
313  * an existing one. Error.
314  */
315  if ( prepcache->argnum || prepcache->geom || prepcache->prepared_geom )
316  {
317  lwpgerror("PrepGeomCacheBuilder asked to build new prepcache where one already exists.");
318  return LW_FAILURE;
319  }
320 
321  /*
322  * Avoid creating a PreparedPoint around a Point or a MultiPoint.
323  * Consider changing this behavior in the future if supported GEOS
324  * versions correctly handle prepared points and multipoints and
325  * provide a performance benefit.
326  * See https://trac.osgeo.org/postgis/ticket/3437
327  */
328  if (lwgeom_get_type(lwgeom) == POINTTYPE || lwgeom_get_type(lwgeom) == MULTIPOINTTYPE)
329  return LW_FAILURE;
330 
331  prepcache->geom = LWGEOM2GEOS( lwgeom , 0);
332  if ( ! prepcache->geom ) return LW_FAILURE;
333  prepcache->prepared_geom = GEOSPrepare( prepcache->geom );
334  if ( ! prepcache->prepared_geom ) return LW_FAILURE;
335  prepcache->argnum = cache->argnum;
336 
337  /*
338  * In order to find the objects we need to destroy, we keep
339  * extra references in a global hash object.
340  */
341  pghe = GetPrepGeomHashEntry(prepcache->context_callback);
342  if ( ! pghe )
343  {
344  lwpgerror("PrepGeomCacheBuilder failed to find hash entry for context %p", prepcache->context_callback);
345  return LW_FAILURE;
346  }
347 
348  pghe->geom = prepcache->geom;
349  pghe->prepared_geom = prepcache->prepared_geom;
350 
351  return LW_SUCCESS;
352 }
353 
364 static int
365 PrepGeomCacheCleaner(GeomCache *cache)
366 {
367  PrepGeomHashEntry* pghe = 0;
368  PrepGeomCache* prepcache = (PrepGeomCache*)cache;
369 
370  if ( ! prepcache )
371  return LW_FAILURE;
372 
373  /*
374  * Clear out the references to the soon-to-be-freed GEOS objects
375  * from the callback hash entry
376  */
377  pghe = GetPrepGeomHashEntry(prepcache->context_callback);
378  if ( ! pghe )
379  {
380  lwpgerror("PrepGeomCacheCleaner failed to find hash entry for context %p", prepcache->context_callback);
381  return LW_FAILURE;
382  }
383  pghe->geom = 0;
384  pghe->prepared_geom = 0;
385 
386  /*
387  * Free the GEOS objects and free the index tree
388  */
389  POSTGIS_DEBUGF(3, "PrepGeomCacheFreeer: freeing %p argnum %d", prepcache, prepcache->argnum);
390  GEOSPreparedGeom_destroy( prepcache->prepared_geom );
391  GEOSGeom_destroy( (GEOSGeometry *)prepcache->geom );
392  prepcache->argnum = 0;
393  prepcache->prepared_geom = 0;
394  prepcache->geom = 0;
395 
396  return LW_SUCCESS;
397 }
398 
399 static GeomCache*
401 {
402  PrepGeomCache* prepcache = palloc(sizeof(PrepGeomCache));
403  memset(prepcache, 0, sizeof(PrepGeomCache));
404  prepcache->context_statement = CurrentMemoryContext;
405  prepcache->type = PREP_CACHE_ENTRY;
406  return (GeomCache*)prepcache;
407 }
408 
409 static GeomCacheMethods PrepGeomCacheMethods =
410 {
411  PREP_CACHE_ENTRY,
415 };
416 
417 
430 GetPrepGeomCache(FunctionCallInfoData* fcinfo, GSERIALIZED* g1, GSERIALIZED* g2)
431 {
432  return (PrepGeomCache*)GetGeomCache(fcinfo, &PrepGeomCacheMethods, g1, g2);
433 }
434 
static bool PreparedCacheIsEmpty(MemoryContext context)
const GEOSPreparedGeometry * prepared_geom
#define PREPARED_BACKEND_HASH_SIZE
static void PreparedCacheInit(MemoryContext context)
uint32_t lwgeom_get_type(const LWGEOM *geom)
Return LWTYPE number.
Definition: lwgeom.c:829
static void DeletePrepGeomHashEntry(MemoryContext mcxt)
#define MULTIPOINTTYPE
Definition: liblwgeom.h:73
#define LW_SUCCESS
Definition: liblwgeom.h:65
static void PreparedCacheReset(MemoryContext context)
#define LW_FAILURE
Definition: liblwgeom.h:64
static void PreparedCacheDelete(MemoryContext context)
static int PrepGeomCacheCleaner(GeomCache *cache)
This function is passed into the generic GetGeomCache function in the case of a cache miss...
const GEOSPreparedGeometry * prepared_geom
MemoryContext context_statement
static GeomCacheMethods PrepGeomCacheMethods
const GEOSGeometry * geom
static void AddPrepGeomHashEntry(PrepGeomHashEntry pghe)
const GEOSGeometry * geom
uint32 mcxt_ptr_hasha(const void *key, Size keysize)
GEOSGeometry * LWGEOM2GEOS(const LWGEOM *lwgeom, int autofix)
static void CreatePrepGeomHash(void)
static GeomCache * PrepGeomCacheAllocator()
static MemoryContextMethods PreparedCacheContextMethods
#define FALSE
Definition: dbfopen.c:168
static PrepGeomHashEntry * GetPrepGeomHashEntry(MemoryContext mcxt)
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:70
PrepGeomCache * GetPrepGeomCache(FunctionCallInfoData *fcinfo, GSERIALIZED *g1, GSERIALIZED *g2)
Given a couple potential geometries and a function call context, return a prepared structure for one ...
MemoryContext context_callback
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 HTAB * PrepGeomHash
static void PreparedCacheStats(MemoryContext context, int level)