PostGIS  2.4.9dev-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-existant 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 false;
155 }
156 
157 static void
158 #if POSTGIS_PGSQL_VERSION >= 96
159 PreparedCacheStats(MemoryContext context, int level, bool print, MemoryContextCounters *totals)
160 #else
161 PreparedCacheStats(MemoryContext context, int level)
162 #endif
163 {
164  /*
165  * Simple stats display function - we must supply a function since this call is mandatory according to tgl
166  * (see postgis-devel archives July 2007)
167  fprintf(stderr, "%s: Prepared context\n", context->name);
168  */
169 }
170 
171 #ifdef MEMORY_CONTEXT_CHECKING
172 static void
173 PreparedCacheCheck(MemoryContext context)
174 {
175  /*
176  * Do nothing - stub required for when PostgreSQL is compiled
177  * with MEMORY_CONTEXT_CHECKING defined
178  */
179 }
180 #endif /* MEMORY_CONTEXT_CHECKING */
181 
182 
183 /* Memory context definition must match the current version of PostgreSQL */
184 static MemoryContextMethods PreparedCacheContextMethods =
185 {
186  NULL,
187  NULL,
188  NULL,
189  PreparedCacheInit,
190  PreparedCacheReset,
192  NULL,
193  PreparedCacheIsEmpty,
194  PreparedCacheStats
195 #ifdef MEMORY_CONTEXT_CHECKING
196  , PreparedCacheCheck
197 #endif
198 };
199 
200 #endif /* POSTGIS_PGSQL_VERSION < 96 */
201 
202 
203 /* TODO: put this in common are for both transform and prepared
204 ** mcxt_ptr_hash
205 ** Build a key from a pointer and a size value.
206 */
207 uint32
208 mcxt_ptr_hasha(const void *key, Size keysize)
209 {
210  uint32 hashval;
211 
212  hashval = DatumGetUInt32(hash_any(key, keysize));
213 
214  return hashval;
215 }
216 
217 static void
219 {
220  HASHCTL ctl;
221 
222  ctl.keysize = sizeof(MemoryContext);
223  ctl.entrysize = sizeof(PrepGeomHashEntry);
224  ctl.hash = mcxt_ptr_hasha;
225 
226  PrepGeomHash = hash_create("PostGIS Prepared Geometry Backend MemoryContext Hash", PREPARED_BACKEND_HASH_SIZE, &ctl, (HASH_ELEM | HASH_FUNCTION));
227 }
228 
229 static void
231 {
232  bool found;
233  void **key;
234  PrepGeomHashEntry *he;
235 
236  /* The hash key is the MemoryContext pointer */
237  key = (void *)&(pghe.context);
238 
239  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_ENTER, &found);
240  if (!found)
241  {
242  /* Insert the entry into the new hash element */
243  he->context = pghe.context;
244  he->geom = pghe.geom;
245  he->prepared_geom = pghe.prepared_geom;
246  }
247  else
248  {
249  elog(ERROR, "AddPrepGeomHashEntry: This memory context is already in use! (%p)", (void *)pghe.context);
250  }
251 }
252 
253 static PrepGeomHashEntry*
254 GetPrepGeomHashEntry(MemoryContext mcxt)
255 {
256  void **key;
257  PrepGeomHashEntry *he;
258 
259  /* The hash key is the MemoryContext pointer */
260  key = (void *)&mcxt;
261 
262  /* Return the projection object from the hash */
263  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_FIND, NULL);
264 
265  return he;
266 }
267 
268 
269 static void
270 DeletePrepGeomHashEntry(MemoryContext mcxt)
271 {
272  void **key;
273  PrepGeomHashEntry *he;
274 
275  /* The hash key is the MemoryContext pointer */
276  key = (void *)&mcxt;
277 
278  /* Delete the projection object from the hash */
279  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_REMOVE, NULL);
280 
281  if (!he)
282  {
283  elog(ERROR, "DeletePrepGeomHashEntry: There was an error removing the geometry object from this MemoryContext (%p)", (void *)mcxt);
284  }
285 
286  he->prepared_geom = NULL;
287  he->geom = NULL;
288 }
289 
302 static int
303 PrepGeomCacheBuilder(const LWGEOM *lwgeom, GeomCache *cache)
304 {
305  PrepGeomCache* prepcache = (PrepGeomCache*)cache;
306  PrepGeomHashEntry* pghe;
307 
308  /*
309  * First time through? allocate the global hash.
310  */
311  if (!PrepGeomHash)
313 
314  /*
315  * No callback entry for this statement context yet? Set it up
316  */
317  if ( ! prepcache->context_callback )
318  {
319  PrepGeomHashEntry pghe;
320 #if POSTGIS_PGSQL_VERSION < 96
321  prepcache->context_callback = MemoryContextCreate(T_AllocSetContext, 8192,
322  &PreparedCacheContextMethods,
323  prepcache->context_statement,
324  "PostGIS Prepared Geometry Context");
325 
326 #else
327  prepcache->context_callback = AllocSetContextCreate(prepcache->context_statement,
328  "PostGIS Prepared Geometry Context",
329  ALLOCSET_SMALL_SIZES);
330 
331  /* PgSQL comments suggest allocating callback in the context */
332  /* being managed, so that the callback object gets cleaned along with */
333  /* the context */
334  MemoryContextCallback *callback = MemoryContextAlloc(prepcache->context_callback, sizeof(MemoryContextCallback));
335  callback->arg = (void*)(prepcache->context_callback);
336  callback->func = PreparedCacheDelete;
337  MemoryContextRegisterResetCallback(prepcache->context_callback, callback);
338 #endif
339 
340 
341  pghe.context = prepcache->context_callback;
342  pghe.geom = 0;
343  pghe.prepared_geom = 0;
344  AddPrepGeomHashEntry( pghe );
345  }
346 
347  /*
348  * Hum, we shouldn't be asked to build a new cache on top of
349  * an existing one. Error.
350  */
351  if ( prepcache->argnum || prepcache->geom || prepcache->prepared_geom )
352  {
353  lwpgerror("PrepGeomCacheBuilder asked to build new prepcache where one already exists.");
354  return LW_FAILURE;
355  }
356 
357  /*
358  * Avoid creating a PreparedPoint around a Point or a MultiPoint.
359  * Consider changing this behavior in the future if supported GEOS
360  * versions correctly handle prepared points and multipoints and
361  * provide a performance benefit.
362  * See https://trac.osgeo.org/postgis/ticket/3437
363  */
364  if (lwgeom_get_type(lwgeom) == POINTTYPE || lwgeom_get_type(lwgeom) == MULTIPOINTTYPE)
365  return LW_FAILURE;
366 
367  prepcache->geom = LWGEOM2GEOS( lwgeom , 0);
368  if ( ! prepcache->geom ) return LW_FAILURE;
369  prepcache->prepared_geom = GEOSPrepare( prepcache->geom );
370  if ( ! prepcache->prepared_geom ) return LW_FAILURE;
371  prepcache->argnum = cache->argnum;
372 
373  /*
374  * In order to find the objects we need to destroy, we keep
375  * extra references in a global hash object.
376  */
377  pghe = GetPrepGeomHashEntry(prepcache->context_callback);
378  if ( ! pghe )
379  {
380  lwpgerror("PrepGeomCacheBuilder failed to find hash entry for context %p", prepcache->context_callback);
381  return LW_FAILURE;
382  }
383 
384  pghe->geom = prepcache->geom;
385  pghe->prepared_geom = prepcache->prepared_geom;
386 
387  return LW_SUCCESS;
388 }
389 
400 static int
401 PrepGeomCacheCleaner(GeomCache *cache)
402 {
403  PrepGeomHashEntry* pghe = 0;
404  PrepGeomCache* prepcache = (PrepGeomCache*)cache;
405 
406  if ( ! prepcache )
407  return LW_FAILURE;
408 
409  /*
410  * Clear out the references to the soon-to-be-freed GEOS objects
411  * from the callback hash entry
412  */
413  pghe = GetPrepGeomHashEntry(prepcache->context_callback);
414  if ( ! pghe )
415  {
416  lwpgerror("PrepGeomCacheCleaner failed to find hash entry for context %p", prepcache->context_callback);
417  return LW_FAILURE;
418  }
419  pghe->geom = 0;
420  pghe->prepared_geom = 0;
421 
422  /*
423  * Free the GEOS objects and free the index tree
424  */
425  POSTGIS_DEBUGF(3, "PrepGeomCacheFreeer: freeing %p argnum %d", prepcache, prepcache->argnum);
426  GEOSPreparedGeom_destroy( prepcache->prepared_geom );
427  GEOSGeom_destroy( (GEOSGeometry *)prepcache->geom );
428  prepcache->argnum = 0;
429  prepcache->prepared_geom = 0;
430  prepcache->geom = 0;
431 
432  return LW_SUCCESS;
433 }
434 
435 static GeomCache*
437 {
438  PrepGeomCache* prepcache = palloc(sizeof(PrepGeomCache));
439  memset(prepcache, 0, sizeof(PrepGeomCache));
440  prepcache->context_statement = CurrentMemoryContext;
441  prepcache->type = PREP_CACHE_ENTRY;
442  return (GeomCache*)prepcache;
443 }
444 
445 static GeomCacheMethods PrepGeomCacheMethods =
446 {
447  PREP_CACHE_ENTRY,
451 };
452 
453 
466 GetPrepGeomCache(FunctionCallInfo fcinfo, GSERIALIZED *g1, GSERIALIZED *g2)
467 {
468  return (PrepGeomCache*)GetGeomCache(fcinfo, &PrepGeomCacheMethods, g1, g2);
469 }
470 
PrepGeomCache * GetPrepGeomCache(FunctionCallInfo fcinfo, GSERIALIZED *g1, GSERIALIZED *g2)
Given a couple potential geometries and a function call context, return a prepared structure for one ...
const GEOSPreparedGeometry * prepared_geom
#define PREPARED_BACKEND_HASH_SIZE
uint32_t lwgeom_get_type(const LWGEOM *geom)
Return LWTYPE number.
Definition: lwgeom.c:878
static void DeletePrepGeomHashEntry(MemoryContext mcxt)
#define MULTIPOINTTYPE
Definition: liblwgeom.h:88
#define LW_SUCCESS
Definition: liblwgeom.h:80
#define LW_FAILURE
Definition: liblwgeom.h:79
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
#define HASH_FIND(hh, head, keyptr, keylen, out)
Definition: uthash.h:132
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 PreparedCacheDelete(void *ptr)
static void CreatePrepGeomHash(void)
static GeomCache * PrepGeomCacheAllocator()
static PrepGeomHashEntry * GetPrepGeomHashEntry(MemoryContext mcxt)
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:85
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