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,¶mValues,¶mLengths,¶mFormats);
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,¶mValues,¶mLengths,¶mFormats);
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