1 /******************************************************************************
2 *
3 * Project: MapServer
4 * Purpose: MapCache tile caching support file: SQLite dimension support
5 * Author: Thomas Bonfort 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 #ifdef USE_SQLITE
32 #include <sqlite3.h>
33 #include <float.h>
34
35 typedef struct mapcache_dimension_sqlite mapcache_dimension_sqlite;
36
37 struct mapcache_dimension_sqlite {
38 mapcache_dimension dimension;
39 char *dbfile;
40 char *get_values_for_entry_query;
41 char *get_all_values_query;
42 };
43
44 struct sqlite_dimension_conn {
45 sqlite3 *handle;
46 sqlite3_stmt **prepared_statements;
47 int n_statements;
48 };
49
mapcache_sqlite_dimension_connection_constructor(mapcache_context * ctx,void ** conn_,void * params)50 void mapcache_sqlite_dimension_connection_constructor(mapcache_context *ctx, void **conn_, void *params)
51 {
52 int ret;
53 int flags;
54 char *dbfile = (char*) params;
55 struct sqlite_dimension_conn *conn = calloc(1, sizeof (struct sqlite_dimension_conn));
56 *conn_ = conn;
57 flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX;
58 ret = sqlite3_open_v2(dbfile, &conn->handle, flags, NULL);
59
60 if (ret != SQLITE_OK) {
61 ctx->set_error(ctx,500,"failed to open sqlite dimension dbfile (%s): %s",dbfile,sqlite3_errmsg(conn->handle));
62 sqlite3_close(conn->handle);
63 *conn_=NULL;
64 return;
65 }
66 sqlite3_busy_timeout(conn->handle, 300000);
67 }
68
mapcache_sqlite_dimension_connection_destructor(void * conn_)69 void mapcache_sqlite_dimension_connection_destructor(void *conn_)
70 {
71 struct sqlite_dimension_conn *conn = (struct sqlite_dimension_conn*) conn_;
72 while(conn->n_statements) {
73 conn->n_statements--;
74 if(conn->prepared_statements[conn->n_statements]) {
75 sqlite3_finalize(conn->prepared_statements[conn->n_statements]);
76 }
77 }
78 free(conn->prepared_statements);
79 sqlite3_close(conn->handle);
80 free(conn);
81 }
82
_sqlite_dimension_get_conn(mapcache_context * ctx,mapcache_tileset * tileset,mapcache_dimension_sqlite * dim)83 static mapcache_pooled_connection* _sqlite_dimension_get_conn(mapcache_context *ctx, mapcache_tileset *tileset, mapcache_dimension_sqlite *dim) {
84 mapcache_dimension *pdim = (mapcache_dimension*)dim;
85 char *conn_key = apr_pstrcat(ctx->pool,"dim_",tileset?tileset->name:"","_",pdim->name,NULL);
86 mapcache_pooled_connection *pc = mapcache_connection_pool_get_connection(ctx,conn_key,
87 mapcache_sqlite_dimension_connection_constructor,
88 mapcache_sqlite_dimension_connection_destructor, dim->dbfile);
89 return pc;
90 }
91
_sqlite_dimension_release_conn(mapcache_context * ctx,mapcache_pooled_connection * pc)92 static void _sqlite_dimension_release_conn(mapcache_context *ctx, mapcache_pooled_connection *pc)
93 {
94 if(GC_HAS_ERROR(ctx)) {
95 mapcache_connection_pool_invalidate_connection(ctx,pc);
96 } else {
97 mapcache_connection_pool_release_connection(ctx,pc);
98 }
99 }
100
_mapcache_dimension_sqlite_bind_parameters(mapcache_context * ctx,sqlite3_stmt * stmt,sqlite3 * handle,const char * value,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)101 static void _mapcache_dimension_sqlite_bind_parameters(mapcache_context *ctx, sqlite3_stmt *stmt, sqlite3 *handle,
102 const char *value,
103 mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid) {
104 int paramidx,ret;
105 paramidx = sqlite3_bind_parameter_index(stmt, ":dim");
106 if (paramidx) {
107 ret = sqlite3_bind_text(stmt, paramidx, value, -1, SQLITE_STATIC);
108 if(ret != SQLITE_OK) {
109 ctx->set_error(ctx,400, "sqlite dimension failed to bind :dim : %s", sqlite3_errmsg(handle));
110 return;
111 }
112 }
113
114 if(tileset) {
115 paramidx = sqlite3_bind_parameter_index(stmt, ":tileset");
116 if (paramidx) {
117 ret = sqlite3_bind_text(stmt, paramidx, tileset->name, -1, SQLITE_STATIC);
118 if(ret != SQLITE_OK) {
119 ctx->set_error(ctx,400, "sqlite dimension failed to bind :tileset : %s", sqlite3_errmsg(handle));
120 return;
121 }
122 }
123 }
124
125 if(grid) {
126 paramidx = sqlite3_bind_parameter_index(stmt, ":gridsrs");
127 if (paramidx) {
128 ret = sqlite3_bind_text(stmt, paramidx, grid->srs, -1, SQLITE_STATIC);
129 if(ret != SQLITE_OK) {
130 ctx->set_error(ctx,400, "failed to bind :gridsrs %s", sqlite3_errmsg(handle));
131 return;
132 }
133 }
134 }
135
136 paramidx = sqlite3_bind_parameter_index(stmt, ":minx");
137 if (paramidx) {
138 ret = sqlite3_bind_double(stmt, paramidx, extent?extent->minx:-DBL_MAX);
139 if(ret != SQLITE_OK) {
140 ctx->set_error(ctx,400, "failed to bind :minx %s", sqlite3_errmsg(handle));
141 return;
142 }
143 }
144 paramidx = sqlite3_bind_parameter_index(stmt, ":miny");
145 if (paramidx) {
146 ret = sqlite3_bind_double(stmt, paramidx, extent?extent->miny:-DBL_MAX);
147 if(ret != SQLITE_OK) {
148 ctx->set_error(ctx,400, "failed to bind :miny %s", sqlite3_errmsg(handle));
149 return;
150 }
151 }
152 paramidx = sqlite3_bind_parameter_index(stmt, ":maxx");
153 if (paramidx) {
154 ret = sqlite3_bind_double(stmt, paramidx, extent?extent->maxx:DBL_MAX);
155 if(ret != SQLITE_OK) {
156 ctx->set_error(ctx,400, "failed to bind :maxx %s", sqlite3_errmsg(handle));
157 return;
158 }
159 }
160 paramidx = sqlite3_bind_parameter_index(stmt, ":maxy");
161 if (paramidx) {
162 ret = sqlite3_bind_double(stmt, paramidx, extent?extent->maxy:DBL_MAX);
163 if(ret != SQLITE_OK) {
164 ctx->set_error(ctx,400, "failed to bind :maxy %s", sqlite3_errmsg(handle));
165 return;
166 }
167 }
168 }
_mapcache_dimension_sqlite_get_entries_for_value(mapcache_context * ctx,mapcache_dimension * dim,const char * value,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)169 static apr_array_header_t* _mapcache_dimension_sqlite_get_entries_for_value(mapcache_context *ctx, mapcache_dimension *dim, const char *value,
170 mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid)
171 {
172 mapcache_dimension_sqlite *dimension = (mapcache_dimension_sqlite*)dim;
173 struct sqlite_dimension_conn *conn = NULL;
174 mapcache_pooled_connection *pc;
175 apr_array_header_t *values = apr_array_make(ctx->pool,1,sizeof(char*));
176 int sqliteret;
177 pc = _sqlite_dimension_get_conn(ctx,tileset,dimension);
178 if (GC_HAS_ERROR(ctx)) {
179 return values;
180 }
181 conn = pc->connection;
182 if(!conn->prepared_statements) {
183 conn->prepared_statements = calloc(2,sizeof(sqlite3_stmt*));
184 conn->n_statements = 2;
185 }
186 if(!conn->prepared_statements[0]) {
187 if(SQLITE_OK != sqlite3_prepare_v2(conn->handle, dimension->get_values_for_entry_query, -1, &conn->prepared_statements[0], NULL)) {
188 ctx->set_error(ctx, 500, "sqlite dimension backend failed on preparing query: %s", sqlite3_errmsg(conn->handle));
189 goto cleanup;
190 }
191 }
192
193 _mapcache_dimension_sqlite_bind_parameters(ctx,conn->prepared_statements[0],conn->handle,value,
194 tileset,extent,grid);
195 if (GC_HAS_ERROR(ctx)) {
196 goto cleanup;
197 }
198
199
200 do {
201 sqliteret = sqlite3_step(conn->prepared_statements[0]);
202 if (sqliteret != SQLITE_DONE && sqliteret != SQLITE_ROW && sqliteret != SQLITE_BUSY && sqliteret != SQLITE_LOCKED) {
203 ctx->set_error(ctx, 500, "sqlite dimension backend failed on query : %s (%d)", sqlite3_errmsg(conn->handle), sqliteret);
204 goto cleanup;
205 }
206 if(sqliteret == SQLITE_ROW) {
207 const char* dimrow = (const char*) sqlite3_column_text(conn->prepared_statements[0], 0);
208 APR_ARRAY_PUSH(values,char*) = apr_pstrdup(ctx->pool,dimrow);
209 }
210 } while (sqliteret == SQLITE_ROW || sqliteret == SQLITE_BUSY || sqliteret == SQLITE_LOCKED);
211
212 cleanup:
213 if(conn->prepared_statements[0]) {
214 sqlite3_reset(conn->prepared_statements[0]);
215 }
216 _sqlite_dimension_release_conn(ctx,pc);
217
218 return values;
219 }
220
_mapcache_dimension_sqlite_get_all_entries(mapcache_context * ctx,mapcache_dimension * dim,mapcache_tileset * tileset,mapcache_extent * extent,mapcache_grid * grid)221 static apr_array_header_t* _mapcache_dimension_sqlite_get_all_entries(mapcache_context *ctx, mapcache_dimension *dim,
222 mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid)
223 {
224 mapcache_dimension_sqlite *dimension = (mapcache_dimension_sqlite*)dim;
225 struct sqlite_dimension_conn *conn = NULL;
226 int sqliteret;
227 apr_array_header_t *ret = apr_array_make(ctx->pool,0,sizeof(char*));
228 mapcache_pooled_connection *pc;
229 pc = _sqlite_dimension_get_conn(ctx,tileset,dimension);
230 if (GC_HAS_ERROR(ctx)) {
231 goto cleanup;
232 }
233 conn = pc->connection;
234 if(!conn->prepared_statements) {
235 conn->prepared_statements = calloc(2,sizeof(sqlite3_stmt*));
236 conn->n_statements = 2;
237 }
238
239 if(!conn->prepared_statements[1]) {
240 sqliteret = sqlite3_prepare_v2(conn->handle, dimension->get_all_values_query, -1, &conn->prepared_statements[1], NULL);
241 if(sqliteret != SQLITE_OK) {
242 ctx->set_error(ctx, 500, "sqlite dimension backend failed on preparing query: %s", sqlite3_errmsg(conn->handle));
243 goto cleanup;
244 }
245 }
246 _mapcache_dimension_sqlite_bind_parameters(ctx,conn->prepared_statements[1],conn->handle,NULL,tileset,extent,grid);
247 if (GC_HAS_ERROR(ctx)) {
248 return ret;
249 }
250 do {
251 sqliteret = sqlite3_step(conn->prepared_statements[1]);
252 if (sqliteret != SQLITE_DONE && sqliteret != SQLITE_ROW && sqliteret != SQLITE_BUSY && sqliteret != SQLITE_LOCKED) {
253 ctx->set_error(ctx, 500, "sqlite dimension backend failed on query : %s (%d)", sqlite3_errmsg(conn->handle), sqliteret);
254 goto cleanup;
255 }
256 if(sqliteret == SQLITE_ROW) {
257 const char* sqdim = (const char*) sqlite3_column_text(conn->prepared_statements[1], 0);
258 APR_ARRAY_PUSH(ret,char*) = apr_pstrdup(ctx->pool,sqdim);
259 }
260 } while (sqliteret == SQLITE_ROW || sqliteret == SQLITE_BUSY || sqliteret == SQLITE_LOCKED);
261
262 cleanup:
263 if(conn->prepared_statements[1]) {
264 sqlite3_reset(conn->prepared_statements[1]);
265 }
266 _sqlite_dimension_release_conn(ctx,pc);
267
268 return ret;
269 }
270
271
_mapcache_dimension_sqlite_parse_xml(mapcache_context * ctx,mapcache_dimension * dim,ezxml_t node)272 static void _mapcache_dimension_sqlite_parse_xml(mapcache_context *ctx, mapcache_dimension *dim,
273 ezxml_t node)
274 {
275 mapcache_dimension_sqlite *dimension;
276 ezxml_t child;
277
278 dimension = (mapcache_dimension_sqlite*)dim;
279
280 child = ezxml_child(node,"dbfile");
281 if(child) {
282 dimension->dbfile = apr_pstrdup(ctx->pool, child->txt);
283 } else {
284 ctx->set_error(ctx,400,"sqlite dimension \"%s\" has no <dbfile> node", dim->name);
285 return;
286 }
287 child = ezxml_child(node,"validate_query");
288 if(child) {
289 dimension->get_values_for_entry_query = apr_pstrdup(ctx->pool, child->txt);
290 } else {
291 ctx->set_error(ctx,400,"sqlite dimension \"%s\" has no <validate_query> node", dim->name);
292 return;
293 }
294 child = ezxml_child(node,"list_query");
295 if(child) {
296 dimension->get_all_values_query = apr_pstrdup(ctx->pool, child->txt);
297 } else {
298 ctx->set_error(ctx,400,"sqlite dimension \"%s\" has no <list_query> node", dim->name);
299 return;
300 }
301
302 }
303
304
_bind_sqlite_dimension_time_params(mapcache_context * ctx,sqlite3_stmt * stmt,sqlite3 * handle,const char * dim_value,mapcache_tileset * tileset,mapcache_grid * grid,mapcache_extent * extent,time_t start,time_t end)305 static void _bind_sqlite_dimension_time_params(mapcache_context *ctx, sqlite3_stmt *stmt,
306 sqlite3 *handle, const char *dim_value, mapcache_tileset *tileset, mapcache_grid *grid, mapcache_extent *extent,
307 time_t start, time_t end)
308 {
309 int paramidx,ret;
310
311 _mapcache_dimension_sqlite_bind_parameters(ctx,stmt,handle,dim_value,tileset,extent,grid);
312
313 paramidx = sqlite3_bind_parameter_index(stmt, ":start_timestamp");
314 if (paramidx) {
315 ret = sqlite3_bind_int64(stmt, paramidx, start);
316 if(ret != SQLITE_OK) {
317 ctx->set_error(ctx,400, "failed to bind :start_timestamp: %s", sqlite3_errmsg(handle));
318 return;
319 }
320 }
321
322 paramidx = sqlite3_bind_parameter_index(stmt, ":end_timestamp");
323 if (paramidx) {
324 ret = sqlite3_bind_int64(stmt, paramidx, end);
325 if(ret != SQLITE_OK) {
326 ctx->set_error(ctx,400, "failed to bind :end_timestamp: %s", sqlite3_errmsg(handle));
327 return;
328 }
329 }
330 }
331
_mapcache_dimension_sqlite_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)332 apr_array_header_t* _mapcache_dimension_sqlite_get_entries_for_time_range(mapcache_context *ctx, mapcache_dimension *dim, const char *dim_value,
333 time_t start, time_t end, mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid) {
334 mapcache_dimension_sqlite *sdim = (mapcache_dimension_sqlite*)dim;
335 int ret;
336 apr_array_header_t *time_ids = NULL;
337 mapcache_pooled_connection *pc;
338 struct sqlite_dimension_conn *conn;
339 pc = _sqlite_dimension_get_conn(ctx,tileset,sdim);
340 if (GC_HAS_ERROR(ctx)) {
341 return NULL;
342 }
343 conn = pc->connection;
344 if(!conn->prepared_statements) {
345 conn->prepared_statements = calloc(1,sizeof(sqlite3_stmt*));
346 conn->n_statements = 1;
347 }
348 if(!conn->prepared_statements[0]) {
349 ret = sqlite3_prepare_v2(conn->handle, sdim->get_values_for_entry_query, -1, &conn->prepared_statements[0], NULL);
350 if(ret != SQLITE_OK) {
351 ctx->set_error(ctx, 500, "time sqlite backend failed on preparing query: %s", sqlite3_errmsg(conn->handle));
352 _sqlite_dimension_release_conn(ctx, pc);
353 return NULL;
354 }
355 }
356
357 _bind_sqlite_dimension_time_params(ctx,conn->prepared_statements[0],conn->handle,dim_value,tileset,grid,extent,start,end);
358 if(GC_HAS_ERROR(ctx)) {
359 _sqlite_dimension_release_conn(ctx, pc);
360 return NULL;
361 }
362
363 time_ids = apr_array_make(ctx->pool,0,sizeof(char*));
364 do {
365 ret = sqlite3_step(conn->prepared_statements[0]);
366 if (ret != SQLITE_DONE && ret != SQLITE_ROW && ret != SQLITE_BUSY && ret != SQLITE_LOCKED) {
367 ctx->set_error(ctx, 500, "sqlite backend failed on dimension_time query : %s (%d)", sqlite3_errmsg(conn->handle), ret);
368 _sqlite_dimension_release_conn(ctx, pc);
369 return NULL;
370 }
371 if (ret == SQLITE_ROW) {
372 const char *time_id = (const char *)sqlite3_column_text(conn->prepared_statements[0], 0);
373 APR_ARRAY_PUSH(time_ids, char *) = apr_pstrdup(ctx->pool, time_id);
374 }
375 } while (ret == SQLITE_ROW || ret == SQLITE_BUSY || ret == SQLITE_LOCKED);
376
377 sqlite3_reset(conn->prepared_statements[0]);
378 _sqlite_dimension_release_conn(ctx, pc);
379 return time_ids;
380 }
381
382 /*
383 apr_array_header_t* _mapcache_dimension_time_get_all_entries(mapcache_context *ctx, mapcache_dimension *dim,
384 mapcache_tileset *tileset, mapcache_extent *extent, mapcache_grid *grid) {
385 mapcache_dimension_time *tdim = (mapcache_dimension_time*)dim;
386 time_t all[2] = {0,INT_MAX};
387 return _mapcache_dimension_time_get_entries(ctx,tdim,NULL,tileset,extent,grid,all,1);
388 }
389 */
390 #endif
391
mapcache_dimension_sqlite_create(mapcache_context * ctx,apr_pool_t * pool)392 mapcache_dimension* mapcache_dimension_sqlite_create(mapcache_context *ctx, apr_pool_t *pool)
393 {
394 #ifdef USE_SQLITE
395 mapcache_dimension_sqlite *dimension = apr_pcalloc(pool, sizeof(mapcache_dimension_sqlite));
396 dimension->dimension.type = MAPCACHE_DIMENSION_SQLITE;
397 dimension->dbfile = NULL;
398 dimension->dimension._get_entries_for_value = _mapcache_dimension_sqlite_get_entries_for_value;
399 dimension->dimension._get_entries_for_time_range = _mapcache_dimension_sqlite_get_entries_for_time_range;
400 dimension->dimension.configuration_parse_xml = _mapcache_dimension_sqlite_parse_xml;
401 dimension->dimension.get_all_entries = _mapcache_dimension_sqlite_get_all_entries;
402 dimension->dimension.get_all_ogc_formatted_entries = _mapcache_dimension_sqlite_get_all_entries;
403 return (mapcache_dimension*)dimension;
404 #else
405 ctx->set_error(ctx,400,"Sqlite dimension support requires SQLITE support to be built in");
406 return NULL;
407 #endif
408 }
409