1 /******************************************************************************
2  *
3  * Project:  MapServer
4  * Purpose:  MapCache tile caching support file: PostgreSQL dimension support
5  * Author:   Jerome Boue and the MapServer team.
6  *
7  ******************************************************************************
8  * Copyright (c) 1996-2018 Regents of the University of Minnesota.
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included in
18  * all copies of this Software or works derived from this Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  *****************************************************************************/
28 
29 #include "mapcache.h"
30 #include <apr_strings.h>
31 #include <apr_hash.h>
32 #include <float.h>
33 #ifdef USE_POSTGRESQL
34 #include <libpq-fe.h>
35 
36 typedef struct mapcache_dimension_postgresql mapcache_dimension_postgresql;
37 
38 struct mapcache_dimension_postgresql {
39   mapcache_dimension dimension;
40   char *dbconnection;
41   char *get_values_for_entry_query;
42   char *get_all_values_query;
43   apr_hash_t  *get_values_indexes;
44   apr_hash_t  *get_all_indexes;
45 };
46 
47 struct postgresql_dimension_conn {
48   PGconn      *pgconn;     /* Connection to database */
49 };
50 
51 /* lookup occurences of "param" in qstring, and replace them with $idx if found. returns the number of times
52  * param was found. replacement is done in place because the replacement string is known to be shorter than the original*/
qparam(mapcache_context * ctx,char * qstring,const char * param,int idx)53 static int qparam(mapcache_context *ctx, char *qstring, const char *param, int idx) {
54   int nFound = 0;
55   char *didx=NULL;
56   while(1) {
57     char *sidx = strstr(qstring,param);
58     char *endstring;
59     //printf("lookup %s iter %d: string %s\n",param,nFound,qstring);
60     if(!sidx) {
61       return nFound;
62     }
63     nFound++;
64     if(!didx) {
65       didx = apr_psprintf(ctx->pool,"$%d", idx);
66     }
67     strcpy(sidx,didx);
68     endstring = apr_pstrdup(ctx->pool,sidx+strlen(param));
69     strcpy(sidx+strlen(didx),endstring);
70   }
71 }
72 #define INT2VOIDP(i) (void*)(uintptr_t)(i)
73 
parse_queries(mapcache_context * ctx,mapcache_dimension_postgresql * dim)74 static void parse_queries(mapcache_context *ctx, mapcache_dimension_postgresql *dim) {
75   const char *keys[9] = {":tileset",":dim",":gridsrs",":minx",":maxx",":miny",":maxy",":start_timestamp",":end_timestamp"};
76   int i;
77   int gaidx=1,gvidx=1;
78   dim->get_all_indexes = apr_hash_make(ctx->pool);
79   dim->get_values_indexes = apr_hash_make(ctx->pool);
80   for(i=0;i<9;i++) {
81     if(qparam(ctx,dim->get_all_values_query,keys[i],gaidx)) {
82       apr_hash_set(dim->get_all_indexes,keys[i],APR_HASH_KEY_STRING,INT2VOIDP(gaidx));
83       gaidx++;
84     }
85     if(qparam(ctx,dim->get_values_for_entry_query,keys[i],gvidx)) {
86       apr_hash_set(dim->get_values_indexes,keys[i],APR_HASH_KEY_STRING,INT2VOIDP(gvidx));
87       gvidx++;
88     }
89   }
90 }
91 
92 #define VOIDP2INT(i) (int)(uintptr_t)(i)
_mapcache_dimension_postgresql_bind_parameters(mapcache_context * ctx,apr_hash_t * param_indexes,char * dim_value,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid,time_t start,time_t end,int * nParams,char *** paramValues,int ** paramLengths,int ** paramFormats)93 static void _mapcache_dimension_postgresql_bind_parameters(mapcache_context *ctx, apr_hash_t *param_indexes,
94        char *dim_value,
95        mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid,
96        time_t start, time_t end,
97        int *nParams, char ***paramValues, int **paramLengths, int **paramFormats) {
98 
99   int paramidx;
100   *nParams = apr_hash_count(param_indexes);
101   *paramValues = apr_pcalloc(ctx->pool, *nParams*sizeof(char*));
102   *paramLengths = apr_pcalloc(ctx->pool, *nParams*sizeof(int));
103   *paramFormats = apr_pcalloc(ctx->pool, *nParams*sizeof(int));
104 
105   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":dim",APR_HASH_KEY_STRING));
106   if (paramidx) {
107     paramidx-=1;
108     (*paramValues)[paramidx] = dim_value;
109     (*paramLengths)[paramidx] = strlen(dim_value);
110     (*paramFormats)[paramidx] = 0;
111   }
112   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":tileset",APR_HASH_KEY_STRING));
113   if (paramidx) {
114     paramidx-=1;
115     //printf("set tileset at %d to %s\n",paramidx,tileset->name);
116     (*paramValues)[paramidx] = tileset->name;
117     (*paramLengths)[paramidx] = strlen(tileset->name);
118     (*paramFormats)[paramidx] = 0;
119   }
120   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":gridsrs",APR_HASH_KEY_STRING));
121   if (paramidx) {
122     paramidx-=1;
123     (*paramValues)[paramidx] = grid->srs;
124     (*paramLengths)[paramidx] = strlen(grid->srs);
125     (*paramFormats)[paramidx] = 0;
126   }
127   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":minx",APR_HASH_KEY_STRING));
128   if (paramidx) {
129     char *buf = apr_psprintf(ctx->pool,"%f",extent?extent->minx:-DBL_MAX);
130     paramidx-=1;
131     (*paramValues)[paramidx] = buf;
132     (*paramLengths)[paramidx] = strlen(buf);
133     (*paramFormats)[paramidx] = 0;
134   }
135   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":miny",APR_HASH_KEY_STRING));
136   if (paramidx) {
137     char *buf = apr_psprintf(ctx->pool,"%f",extent?extent->miny:-DBL_MAX);
138     paramidx-=1;
139     (*paramValues)[paramidx] = buf;
140     (*paramLengths)[paramidx] = strlen(buf);
141     (*paramFormats)[paramidx] = 0;
142   }
143   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":maxx",APR_HASH_KEY_STRING));
144   if (paramidx) {
145     char *buf = apr_psprintf(ctx->pool,"%f",extent?extent->maxx:DBL_MAX);
146     paramidx-=1;
147     (*paramValues)[paramidx] = buf;
148     (*paramLengths)[paramidx] = strlen(buf);
149     (*paramFormats)[paramidx] = 0;
150   }
151   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":maxy",APR_HASH_KEY_STRING));
152   if (paramidx) {
153     char *buf = apr_psprintf(ctx->pool,"%f",extent?extent->maxy:DBL_MAX);
154     paramidx-=1;
155     (*paramValues)[paramidx] = buf;
156     (*paramLengths)[paramidx] = strlen(buf);
157     (*paramFormats)[paramidx] = 0;
158   }
159   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":start_timestamp",APR_HASH_KEY_STRING));
160   if (paramidx) {
161     char *buf = apr_psprintf(ctx->pool,"%ld",start);
162     paramidx-=1;
163     (*paramValues)[paramidx] = buf;
164     (*paramLengths)[paramidx] = strlen(buf);
165     (*paramFormats)[paramidx] = 0;
166   }
167   paramidx = VOIDP2INT(apr_hash_get(param_indexes,":end_timestamp",APR_HASH_KEY_STRING));
168   if (paramidx) {
169     char *buf = apr_psprintf(ctx->pool,"%ld",end);
170     paramidx-=1;
171     (*paramValues)[paramidx] = buf;
172     (*paramLengths)[paramidx] = strlen(buf);
173     (*paramFormats)[paramidx] = 0;
174   }
175 }
176 
prepare_query(mapcache_context * ctx,PGconn * conn,char * stmt_name,char * qstring,apr_hash_t * param_indexes)177 static void prepare_query(mapcache_context *ctx, PGconn *conn, char *stmt_name, char *qstring, apr_hash_t *param_indexes) {
178 
179   /*
180   apr_hash_index_t *param_index = apr_hash_first(ctx->pool,param_indexes);
181   while(param_index) {
182     const void *key;
183     apr_ssize_t keylen;
184     int idx;
185     apr_hash_this(param_index,&key,&keylen,(void**)&idx);
186     param_index = apr_hash_next(param_index);
187   }
188   */
189   PGresult *res = PQprepare(conn,stmt_name,qstring,apr_hash_count(param_indexes), NULL);
190   if (PQresultStatus(res) != PGRES_COMMAND_OK) {
191     ctx->set_error(ctx,500,"prepare query: %s",PQerrorMessage(conn));
192     PQclear(res);
193     return;
194   }
195   PQclear(res);
196 }
197 
mapcache_postgresql_dimension_connection_constructor(mapcache_context * ctx,void ** conn_,void * params)198 void mapcache_postgresql_dimension_connection_constructor(mapcache_context *ctx, void **conn_, void *params)
199 {
200   mapcache_dimension_postgresql *dim = (mapcache_dimension_postgresql*) params;
201   struct postgresql_dimension_conn *conn = calloc(1, sizeof (struct postgresql_dimension_conn));
202   *conn_ = conn;
203   conn->pgconn = PQconnectdb(dim->dbconnection);
204   /* Check to see that the backend connection was successfully made */
205   if (PQstatus(conn->pgconn) != CONNECTION_OK) {
206     ctx->set_error(ctx, 500, "failed to open postgresql connection: %s", PQerrorMessage(conn->pgconn));
207     PQfinish(conn->pgconn);
208     *conn_ = NULL;
209     return;
210   }
211   prepare_query(ctx,conn->pgconn, "get_value", dim->get_values_for_entry_query, dim->get_values_indexes);
212   if(GC_HAS_ERROR(ctx)) {
213     PQfinish(conn->pgconn);
214     *conn_ = NULL;
215     return;
216   }
217   prepare_query(ctx,conn->pgconn, "get_all", dim->get_all_values_query, dim->get_all_indexes);
218   if(GC_HAS_ERROR(ctx)) {
219     PQfinish(conn->pgconn);
220     *conn_ = NULL;
221     return;
222   }
223 }
224 
mapcache_postgresql_dimension_connection_destructor(void * conn_)225 void mapcache_postgresql_dimension_connection_destructor(void *conn_)
226 {
227   struct postgresql_dimension_conn *conn = (struct postgresql_dimension_conn*) conn_;
228   PQfinish(conn->pgconn);
229   free(conn);
230 }
231 
_postgresql_dimension_get_conn(mapcache_context * ctx,mapcache_tileset * tileset,mapcache_dimension_postgresql * dim)232 static mapcache_pooled_connection* _postgresql_dimension_get_conn(mapcache_context *ctx, mapcache_tileset *tileset, mapcache_dimension_postgresql *dim) {
233   mapcache_dimension *pdim = (mapcache_dimension*)dim;
234   char *conn_key = apr_pstrcat(ctx->pool,"dim_",tileset?tileset->name:"","_",pdim->name,NULL);
235   mapcache_pooled_connection *pc = mapcache_connection_pool_get_connection(ctx,conn_key,
236         mapcache_postgresql_dimension_connection_constructor,
237         mapcache_postgresql_dimension_connection_destructor, dim);
238   return pc;
239 }
240 
_postgresql_dimension_release_conn(mapcache_context * ctx,mapcache_pooled_connection * pc)241 static void _postgresql_dimension_release_conn(mapcache_context *ctx, mapcache_pooled_connection *pc)
242 {
243   if(GC_HAS_ERROR(ctx)) {
244     mapcache_connection_pool_invalidate_connection(ctx,pc);
245   } else {
246     mapcache_connection_pool_release_connection(ctx,pc);
247   }
248 }
249 
_mapcache_dimension_postgresql_get_entries_for_time_range(mapcache_context * ctx,mapcache_dimension * dim,const char * dim_value,time_t start,time_t end,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)250 apr_array_header_t* _mapcache_dimension_postgresql_get_entries_for_time_range(mapcache_context *ctx, mapcache_dimension *dim, const char *dim_value,
251         time_t start, time_t end, mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid) {
252   mapcache_dimension_postgresql *sdim = (mapcache_dimension_postgresql*)dim;
253   PGresult *res;
254   apr_array_header_t *time_ids = NULL;
255   mapcache_pooled_connection *pc;
256   struct postgresql_dimension_conn *conn;
257   int nParams, *paramLengths,*paramFormats,i;
258   char **paramValues;
259 
260   pc = _postgresql_dimension_get_conn(ctx,tileset,sdim);
261   if (GC_HAS_ERROR(ctx)) {
262     return NULL;
263   }
264   conn = pc->connection;
265   _mapcache_dimension_postgresql_bind_parameters(ctx,sdim->get_values_indexes,(char*)dim_value,tileset,extent,grid,start,end,&nParams,&paramValues,&paramLengths,&paramFormats);
266   if(GC_HAS_ERROR(ctx)) {
267     _postgresql_dimension_release_conn(ctx, pc);
268     return NULL;
269   }
270 
271   res = PQexecPrepared(conn->pgconn,"get_value",nParams,(const char *const*)paramValues,paramLengths,paramFormats,0);
272   if (PQresultStatus(res) != PGRES_TUPLES_OK) {
273     ctx->set_error(ctx, 500, "postgresql query: %s", PQerrorMessage(conn->pgconn));
274     PQclear(res);
275     _postgresql_dimension_release_conn(ctx, pc);
276     return NULL;
277   }
278 
279   time_ids = apr_array_make(ctx->pool,0,sizeof(char*));
280   for(i=0;i<PQntuples(res);i++) {
281     APR_ARRAY_PUSH(time_ids, char *) = apr_pstrdup(ctx->pool, PQgetvalue(res,i,0));
282   }
283   PQclear(res);
284   _postgresql_dimension_release_conn(ctx, pc);
285   return time_ids;
286 }
287 
_mapcache_dimension_postgresql_get_entries_for_value(mapcache_context * ctx,mapcache_dimension * dim,const char * value,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)288 static apr_array_header_t* _mapcache_dimension_postgresql_get_entries_for_value(mapcache_context *ctx, mapcache_dimension *dim, const char *value,
289      mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid) {
290   return _mapcache_dimension_postgresql_get_entries_for_time_range(ctx,dim,value,0,0,tileset,extent,grid);
291 }
292 
_mapcache_dimension_postgresql_get_all_entries(mapcache_context * ctx,mapcache_dimension * dim,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)293 static apr_array_header_t* _mapcache_dimension_postgresql_get_all_entries(mapcache_context *ctx, mapcache_dimension *dim,
294        mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid) {
295   mapcache_dimension_postgresql *sdim = (mapcache_dimension_postgresql*)dim;
296   PGresult *res;
297   apr_array_header_t *time_ids = NULL;
298   mapcache_pooled_connection *pc;
299   struct postgresql_dimension_conn *conn;
300   int nParams, *paramLengths,*paramFormats,i;
301   char **paramValues;
302 
303   pc = _postgresql_dimension_get_conn(ctx,tileset,sdim);
304   if (GC_HAS_ERROR(ctx)) {
305     return NULL;
306   }
307   conn = pc->connection;
308   _mapcache_dimension_postgresql_bind_parameters(ctx,sdim->get_all_indexes,NULL,tileset,extent,grid,0,0,&nParams,&paramValues,&paramLengths,&paramFormats);
309   if(GC_HAS_ERROR(ctx)) {
310     _postgresql_dimension_release_conn(ctx, pc);
311     return NULL;
312   }
313 
314   res = PQexecPrepared(conn->pgconn,"get_all",nParams,(const char *const*)paramValues,paramLengths,paramFormats,0);
315   if (PQresultStatus(res) != PGRES_TUPLES_OK) {
316     ctx->set_error(ctx, 500, "postgresql query: %s", PQerrorMessage(conn->pgconn));
317     PQclear(res);
318     _postgresql_dimension_release_conn(ctx, pc);
319     return NULL;
320   }
321 
322   //printf("got %d results\n",PQntuples(res));
323   time_ids = apr_array_make(ctx->pool,0,sizeof(char*));
324   for(i=0;i<PQntuples(res);i++) {
325     APR_ARRAY_PUSH(time_ids, char *) = apr_pstrdup(ctx->pool, PQgetvalue(res,i,0));
326   }
327   PQclear(res);
328   _postgresql_dimension_release_conn(ctx, pc);
329   return time_ids;
330 
331 }
332 
_mapcache_dimension_postgresql_parse_xml(mapcache_context * ctx,mapcache_dimension * dim,ezxml_t node)333 static void _mapcache_dimension_postgresql_parse_xml(mapcache_context *ctx, mapcache_dimension *dim,
334     ezxml_t node)
335 {
336   mapcache_dimension_postgresql *dimension;
337   ezxml_t child;
338 
339   dimension = (mapcache_dimension_postgresql*)dim;
340 
341   child = ezxml_child(node,"connection");
342   if(child) {
343     dimension->dbconnection = apr_pstrdup(ctx->pool, child->txt);
344   } else {
345     ctx->set_error(ctx,400,"postgresql dimension \"%s\" has no <connection> node", dim->name);
346     return;
347   }
348   child = ezxml_child(node,"validate_query");
349   if(child) {
350     dimension->get_values_for_entry_query = apr_pstrdup(ctx->pool, child->txt);
351   } else {
352     ctx->set_error(ctx,400,"postgresql dimension \"%s\" has no <validate_query> node", dim->name);
353     return;
354   }
355   child = ezxml_child(node,"list_query");
356   if(child) {
357     dimension->get_all_values_query = apr_pstrdup(ctx->pool, child->txt);
358   } else {
359     ctx->set_error(ctx,400,"postgresql dimension \"%s\" has no <list_query> node", dim->name);
360     return;
361   }
362   parse_queries(ctx,dimension);
363   //printf("q1: %s\n",dimension->get_all_values_query);
364   //printf("q2: %s\n",dimension->get_values_for_entry_query);
365 }
366 #endif
367 
368 
mapcache_dimension_postgresql_create(mapcache_context * ctx,apr_pool_t * pool)369 mapcache_dimension* mapcache_dimension_postgresql_create(mapcache_context *ctx, apr_pool_t *pool)
370 {
371 #ifdef USE_POSTGRESQL
372   mapcache_dimension_postgresql *dimension = apr_pcalloc(pool, sizeof(mapcache_dimension_postgresql));
373   dimension->dimension.type = MAPCACHE_DIMENSION_POSTGRESQL;
374   dimension->dbconnection = NULL;
375   dimension->dimension._get_entries_for_value = _mapcache_dimension_postgresql_get_entries_for_value;
376   dimension->dimension._get_entries_for_time_range = _mapcache_dimension_postgresql_get_entries_for_time_range;
377   dimension->dimension.configuration_parse_xml = _mapcache_dimension_postgresql_parse_xml;
378   dimension->dimension.get_all_entries = _mapcache_dimension_postgresql_get_all_entries;
379   dimension->dimension.get_all_ogc_formatted_entries = _mapcache_dimension_postgresql_get_all_entries;
380   return (mapcache_dimension*)dimension;
381 #else
382   ctx->set_error(ctx,400,"postgresql dimension support requires POSTGRESQL support to be built in");
383   return NULL;
384 #endif
385 }
386