PostGIS  2.3.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 /* Memory context cache function prototypes */
98 static void PreparedCacheInit(MemoryContext context);
99 static void PreparedCacheReset(MemoryContext context);
100 static void PreparedCacheDelete(MemoryContext context);
101 static bool PreparedCacheIsEmpty(MemoryContext context);
102 #if POSTGIS_PGSQL_VERSION >= 96
103 static void PreparedCacheStats(MemoryContext context, int level, bool print, MemoryContextCounters *totals);
104 #else
105 static void PreparedCacheStats(MemoryContext context, int level);
106 #endif
107 
108 #ifdef MEMORY_CONTEXT_CHECKING
109 static void PreparedCacheCheck(MemoryContext context);
110 #endif
111 
112 /* Memory context definition must match the current version of PostgreSQL */
113 static MemoryContextMethods PreparedCacheContextMethods =
114 {
115  NULL,
116  NULL,
117  NULL,
121  NULL,
124 #ifdef MEMORY_CONTEXT_CHECKING
125  , PreparedCacheCheck
126 #endif
127 };
128 
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 PreparedCacheDelete(MemoryContext context)
140 {
141  PrepGeomHashEntry* pghe;
142 
143  /* Lookup the hash entry pointer in the global hash table so we can free it */
144  pghe = GetPrepGeomHashEntry(context);
145 
146  if (!pghe)
147  elog(ERROR, "PreparedCacheDelete: Trying to delete non-existant hash entry object with MemoryContext key (%p)", (void *)context);
148 
149  POSTGIS_DEBUGF(3, "deleting geom object (%p) and prepared geom object (%p) with MemoryContext key (%p)", pghe->geom, pghe->prepared_geom, context);
150 
151  /* Free them */
152  if ( pghe->prepared_geom )
153  GEOSPreparedGeom_destroy( pghe->prepared_geom );
154  if ( pghe->geom )
155  GEOSGeom_destroy( (GEOSGeometry *)pghe->geom );
156 
157  /* Remove the hash entry as it is no longer needed */
158  DeletePrepGeomHashEntry(context);
159 }
160 
161 static void
162 PreparedCacheReset(MemoryContext context)
163 {
164  /*
165  * Do nothing, but we must supply a function since this call is mandatory according to tgl
166  * (see postgis-devel archives July 2007)
167  */
168 }
169 
170 static bool
171 PreparedCacheIsEmpty(MemoryContext context)
172 {
173  /*
174  * Always return false since this call is mandatory according to tgl
175  * (see postgis-devel archives July 2007)
176  */
177  return FALSE;
178 }
179 
180 static void
181 #if POSTGIS_PGSQL_VERSION >= 96
182 PreparedCacheStats(MemoryContext context, int level, bool print, MemoryContextCounters *totals)
183 #else
184 PreparedCacheStats(MemoryContext context, int level)
185 #endif
186 {
187  /*
188  * Simple stats display function - we must supply a function since this call is mandatory according to tgl
189  * (see postgis-devel archives July 2007)
190  fprintf(stderr, "%s: Prepared context\n", context->name);
191  */
192 
193 }
194 
195 #ifdef MEMORY_CONTEXT_CHECKING
196 static void
197 PreparedCacheCheck(MemoryContext context)
198 {
199  /*
200  * Do nothing - stub required for when PostgreSQL is compiled
201  * with MEMORY_CONTEXT_CHECKING defined
202  */
203 }
204 #endif
205 
206 /* TODO: put this in common are for both transform and prepared
207 ** mcxt_ptr_hash
208 ** Build a key from a pointer and a size value.
209 */
210 uint32
211 mcxt_ptr_hasha(const void *key, Size keysize)
212 {
213  uint32 hashval;
214 
215  hashval = DatumGetUInt32(hash_any(key, keysize));
216 
217  return hashval;
218 }
219 
220 static void
222 {
223  HASHCTL ctl;
224 
225  ctl.keysize = sizeof(MemoryContext);
226  ctl.entrysize = sizeof(PrepGeomHashEntry);
227  ctl.hash = mcxt_ptr_hasha;
228 
229  PrepGeomHash = hash_create("PostGIS Prepared Geometry Backend MemoryContext Hash", PREPARED_BACKEND_HASH_SIZE, &ctl, (HASH_ELEM | HASH_FUNCTION));
230 }
231 
232 static void
234 {
235  bool found;
236  void **key;
237  PrepGeomHashEntry *he;
238 
239  /* The hash key is the MemoryContext pointer */
240  key = (void *)&(pghe.context);
241 
242  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_ENTER, &found);
243  if (!found)
244  {
245  /* Insert the entry into the new hash element */
246  he->context = pghe.context;
247  he->geom = pghe.geom;
248  he->prepared_geom = pghe.prepared_geom;
249  }
250  else
251  {
252  elog(ERROR, "AddPrepGeomHashEntry: This memory context is already in use! (%p)", (void *)pghe.context);
253  }
254 }
255 
256 static PrepGeomHashEntry*
257 GetPrepGeomHashEntry(MemoryContext mcxt)
258 {
259  void **key;
260  PrepGeomHashEntry *he;
261 
262  /* The hash key is the MemoryContext pointer */
263  key = (void *)&mcxt;
264 
265  /* Return the projection object from the hash */
266  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_FIND, NULL);
267 
268  return he;
269 }
270 
271 
272 static void
273 DeletePrepGeomHashEntry(MemoryContext mcxt)
274 {
275  void **key;
276  PrepGeomHashEntry *he;
277 
278  /* The hash key is the MemoryContext pointer */
279  key = (void *)&mcxt;
280 
281  /* Delete the projection object from the hash */
282  he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_REMOVE, NULL);
283 
284  if (!he)
285  {
286  elog(ERROR, "DeletePrepGeomHashEntry: There was an error removing the geometry object from this MemoryContext (%p)", (void *)mcxt);
287  }
288 
289  he->prepared_geom = NULL;
290  he->geom = NULL;
291 }
292 
305 static int
306 PrepGeomCacheBuilder(const LWGEOM *lwgeom, GeomCache *cache)
307 {
308  PrepGeomCache* prepcache = (PrepGeomCache*)cache;
309  PrepGeomHashEntry* pghe;
310 
311  /*
312  * First time through? allocate the global hash.
313  */
314  if (!PrepGeomHash)
316 
317  /*
318  * No callback entry for this statement context yet? Set it up
319  */
320  if ( ! prepcache->context_callback )
321  {
322  PrepGeomHashEntry pghe;
323  prepcache->context_callback = MemoryContextCreate(T_AllocSetContext, 8192,
325  prepcache->context_statement,
326  "PostGIS Prepared Geometry Context");
327  pghe.context = prepcache->context_callback;
328  pghe.geom = 0;
329  pghe.prepared_geom = 0;
330  AddPrepGeomHashEntry( pghe );
331  }
332 
333  /*
334  * Hum, we shouldn't be asked to build a new cache on top of
335  * an existing one. Error.
336  */
337  if ( prepcache->argnum || prepcache->geom || prepcache->prepared_geom )
338  {
339  lwpgerror("PrepGeomCacheBuilder asked to build new prepcache where one already exists.");
340  return LW_FAILURE;
341  }
342 
343  /*
344  * Avoid creating a PreparedPoint around a Point or a MultiPoint.
345  * Consider changing this behavior in the future if supported GEOS
346  * versions correctly handle prepared points and multipoints and
347  * provide a performance benefit.
348  * See https://trac.osgeo.org/postgis/ticket/3437
349  */
350  if (lwgeom_get_type(lwgeom) == POINTTYPE || lwgeom_get_type(lwgeom) == MULTIPOINTTYPE)
351  return LW_FAILURE;
352 
353  prepcache->geom = LWGEOM2GEOS( lwgeom , 0);
354  if ( ! prepcache->geom ) return LW_FAILURE;
355  prepcache->prepared_geom = GEOSPrepare( prepcache->geom );
356  if ( ! prepcache->prepared_geom ) return LW_FAILURE;
357  prepcache->argnum = cache->argnum;
358 
359  /*
360  * In order to find the objects we need to destroy, we keep
361  * extra references in a global hash object.
362  */
363  pghe = GetPrepGeomHashEntry(prepcache->context_callback);
364  if ( ! pghe )
365  {
366  lwpgerror("PrepGeomCacheBuilder failed to find hash entry for context %p", prepcache->context_callback);
367  return LW_FAILURE;
368  }
369 
370  pghe->geom = prepcache->geom;
371  pghe->prepared_geom = prepcache->prepared_geom;
372 
373  return LW_SUCCESS;
374 }
375 
386 static int
387 PrepGeomCacheCleaner(GeomCache *cache)
388 {
389  PrepGeomHashEntry* pghe = 0;
390  PrepGeomCache* prepcache = (PrepGeomCache*)cache;
391 
392  if ( ! prepcache )
393  return LW_FAILURE;
394 
395  /*
396  * Clear out the references to the soon-to-be-freed GEOS objects
397  * from the callback hash entry
398  */
399  pghe = GetPrepGeomHashEntry(prepcache->context_callback);
400  if ( ! pghe )
401  {
402  lwpgerror("PrepGeomCacheCleaner failed to find hash entry for context %p", prepcache->context_callback);
403  return LW_FAILURE;
404  }
405  pghe->geom = 0;
406  pghe->prepared_geom = 0;
407 
408  /*
409  * Free the GEOS objects and free the index tree
410  */
411  POSTGIS_DEBUGF(3, "PrepGeomCacheFreeer: freeing %p argnum %d", prepcache, prepcache->argnum);
412  GEOSPreparedGeom_destroy( prepcache->prepared_geom );
413  GEOSGeom_destroy( (GEOSGeometry *)prepcache->geom );
414  prepcache->argnum = 0;
415  prepcache->prepared_geom = 0;
416  prepcache->geom = 0;
417 
418  return LW_SUCCESS;
419 }
420 
421 static GeomCache*
423 {
424  PrepGeomCache* prepcache = palloc(sizeof(PrepGeomCache));
425  memset(prepcache, 0, sizeof(PrepGeomCache));
426  prepcache->context_statement = CurrentMemoryContext;
427  prepcache->type = PREP_CACHE_ENTRY;
428  return (GeomCache*)prepcache;
429 }
430 
431 static GeomCacheMethods PrepGeomCacheMethods =
432 {
433  PREP_CACHE_ENTRY,
437 };
438 
439 
452 GetPrepGeomCache(FunctionCallInfoData* fcinfo, GSERIALIZED* g1, GSERIALIZED* g2)
453 {
454  return (PrepGeomCache*)GetGeomCache(fcinfo, &PrepGeomCacheMethods, g1, g2);
455 }
456 
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:842
static void DeletePrepGeomHashEntry(MemoryContext mcxt)
#define MULTIPOINTTYPE
Definition: liblwgeom.h:87
#define LW_SUCCESS
Definition: liblwgeom.h:79
static void PreparedCacheReset(MemoryContext context)
#define LW_FAILURE
Definition: liblwgeom.h:78
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:84
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)