/src/MapServer/src/mappostgis.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * $Id$ |
3 | | * |
4 | | * Project: MapServer |
5 | | * Purpose: PostGIS CONNECTIONTYPE support. |
6 | | * Author: Paul Ramsey <pramsey@cleverelephant.ca> |
7 | | * Dave Blasby <dblasby@gmail.com> |
8 | | * |
9 | | ****************************************************************************** |
10 | | * Copyright (c) 2010 Paul Ramsey |
11 | | * Copyright (c) 2002 Refractions Research |
12 | | * |
13 | | * Permission is hereby granted, free of charge, to any person obtaining a |
14 | | * copy of this software and associated documentation files (the "Software"), |
15 | | * to deal in the Software without restriction, including without limitation |
16 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
17 | | * and/or sell copies of the Software, and to permit persons to whom the |
18 | | * Software is furnished to do so, subject to the following conditions: |
19 | | * |
20 | | * The above copyright notice and this permission notice shall be included in |
21 | | * all copies of this Software or works derived from this Software. |
22 | | * |
23 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
24 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
25 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
26 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
27 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
28 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
29 | | * DEALINGS IN THE SOFTWARE. |
30 | | ****************************************************************************/ |
31 | | |
32 | | /* |
33 | | ** Some theory of operation: |
34 | | ** |
35 | | ** Build SQL from DATA statement and LAYER state. SQL is always of the form: |
36 | | ** |
37 | | ** SELECT [this, that, other], geometry, uid |
38 | | ** FROM [table|(subquery) as sub] |
39 | | ** WHERE [box] AND [filter] |
40 | | ** |
41 | | ** So the geometry always resides at layer->numitems and the uid always |
42 | | ** resides at layer->numitems + 1 |
43 | | ** |
44 | | ** Geometry is requested as Hex encoded WKB. The endian is always requested |
45 | | ** as the client endianness. |
46 | | ** |
47 | | ** msPostGISLayerWhichShapes creates SQL based on DATA and LAYER state, |
48 | | ** executes it, and places the un-read PGresult handle in the |
49 | | *layerinfo->pgresult, |
50 | | ** setting the layerinfo->rownum to 0. |
51 | | ** |
52 | | ** msPostGISNextShape reads a row, increments layerinfo->rownum, and returns |
53 | | ** MS_SUCCESS, until rownum reaches ntuples, and it returns MS_DONE instead. |
54 | | ** |
55 | | */ |
56 | | |
57 | | /* required for MSVC */ |
58 | | #define _USE_MATH_DEFINES |
59 | | |
60 | | #include <assert.h> |
61 | | #include <string.h> |
62 | | #include <math.h> |
63 | | #include "mapserver.h" |
64 | | #include "maptime.h" |
65 | | #include "mappostgis.h" |
66 | | #include "mapows.h" |
67 | | |
68 | | #include <vector> |
69 | | |
70 | | #define FP_EPSILON 1e-12 |
71 | | #define FP_EQ(a, b) (fabs((a) - (b)) < FP_EPSILON) |
72 | | #define FP_LEFT -1 |
73 | | #define FP_RIGHT 1 |
74 | | #define FP_COLINEAR 0 |
75 | | |
76 | | #define SEGMENT_ANGLE 10.0 |
77 | | #define SEGMENT_MINPOINTS 10 |
78 | | |
79 | | #define WKBZOFFSET_NONISO 0x80000000 |
80 | | #define WKBMOFFSET_NONISO 0x40000000 |
81 | | |
82 | | #define HAS_Z 0x1 |
83 | | #define HAS_M 0x2 |
84 | | |
85 | | #if TRANSFER_ENCODING == 256 |
86 | | #define RESULTSET_TYPE 1 |
87 | | #else |
88 | | #define RESULTSET_TYPE 0 |
89 | | #endif |
90 | | |
91 | | /* These are the OIDs for some builtin types, as returned by PQftype(). */ |
92 | | /* They were copied from pg_type.h in src/include/catalog/pg_type.h */ |
93 | | |
94 | | #ifndef BOOLOID |
95 | | #define BOOLOID 16 |
96 | | #define BYTEAOID 17 |
97 | | #define CHAROID 18 |
98 | | #define NAMEOID 19 |
99 | | #define INT8OID 20 |
100 | | #define INT2OID 21 |
101 | | #define INT2VECTOROID 22 |
102 | | #define INT4OID 23 |
103 | | #define REGPROCOID 24 |
104 | | #define TEXTOID 25 |
105 | | #define OIDOID 26 |
106 | | #define TIDOID 27 |
107 | | #define XIDOID 28 |
108 | | #define CIDOID 29 |
109 | | #define OIDVECTOROID 30 |
110 | | #define FLOAT4OID 700 |
111 | | #define FLOAT8OID 701 |
112 | | #define INT4ARRAYOID 1007 |
113 | | #define TEXTARRAYOID 1009 |
114 | | #define BPCHARARRAYOID 1014 |
115 | | #define VARCHARARRAYOID 1015 |
116 | | #define FLOAT4ARRAYOID 1021 |
117 | | #define FLOAT8ARRAYOID 1022 |
118 | | #define BPCHAROID 1042 |
119 | | #define VARCHAROID 1043 |
120 | | #define DATEOID 1082 |
121 | | #define TIMEOID 1083 |
122 | | #define TIMETZOID 1266 |
123 | | #define TIMESTAMPOID 1114 |
124 | | #define TIMESTAMPTZOID 1184 |
125 | | #define NUMERICOID 1700 |
126 | | #endif |
127 | | |
128 | | #ifdef USE_POSTGIS |
129 | | |
130 | | static int wkbConvGeometryToShape(wkbObj *w, shapeObj *shape); |
131 | | static int arcStrokeCircularString(wkbObj *w, double segment_angle, |
132 | | lineObj *line, int nZMFlag); |
133 | | |
134 | | /* |
135 | | ** msPostGISCloseConnection() |
136 | | ** |
137 | | ** Handler registered with msConnPoolRegister so that Mapserver |
138 | | ** can clean up open connections during a shutdown. |
139 | | */ |
140 | | static void msPostGISCloseConnection(void *pgconn) { |
141 | | PQfinish((PGconn *)pgconn); |
142 | | } |
143 | | |
144 | | /* |
145 | | ** msPostGISCreateLayerInfo() |
146 | | */ |
147 | | static msPostGISLayerInfo *msPostGISCreateLayerInfo(void) { |
148 | | msPostGISLayerInfo *layerinfo = new msPostGISLayerInfo; |
149 | | layerinfo->paging = MS_TRUE; |
150 | | layerinfo->force2d = MS_FALSE; |
151 | | return layerinfo; |
152 | | } |
153 | | |
154 | | /* |
155 | | ** msPostGISFreeLayerInfo() |
156 | | */ |
157 | | static void msPostGISFreeLayerInfo(layerObj *layer) { |
158 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
159 | | if (layerinfo->pgresult) |
160 | | PQclear(layerinfo->pgresult); |
161 | | if (layerinfo->pgconn) |
162 | | msConnPoolRelease(layer, layerinfo->pgconn); |
163 | | delete layerinfo; |
164 | | layer->layerinfo = nullptr; |
165 | | } |
166 | | |
167 | | /* |
168 | | ** postgresqlNoticeHandler() |
169 | | ** |
170 | | ** Propagate messages from the database to the Mapserver log, |
171 | | ** set in PQsetNoticeProcessor during layer open. |
172 | | */ |
173 | | static void postresqlNoticeHandler(void *arg, const char *message) { |
174 | | layerObj *lp = (layerObj *)arg; |
175 | | |
176 | | if (lp->debug) { |
177 | | msDebug("%s\n", message); |
178 | | } |
179 | | } |
180 | | |
181 | | /* |
182 | | ** Expandable pointObj array. The lineObj unfortunately |
183 | | ** is not useful for this purpose, so we have this one. |
184 | | */ |
185 | | static std::vector<pointObj> pointArrayNew(int maxpoints) { |
186 | | auto v = std::vector<pointObj>(); |
187 | | v.reserve(maxpoints); |
188 | | return v; |
189 | | } |
190 | | |
191 | | /* |
192 | | ** Add a pointObj to the pointObjArray. |
193 | | */ |
194 | | static void pointArrayAddPoint(std::vector<pointObj> &v, const pointObj &p) { |
195 | | v.push_back(p); |
196 | | } |
197 | | |
198 | | /* |
199 | | ** Pass an input type number through the PostGIS version |
200 | | ** type map array to handle the pre-2.0 incorrect WKB types |
201 | | */ |
202 | | |
203 | | static int wkbTypeMap(wkbObj *w, int type, int *pnZMFlag) { |
204 | | *pnZMFlag = 0; |
205 | | /* PostGIS >= 2 : ISO SQL/MM style Z types ? */ |
206 | | if (type >= 1000 && type < 2000) { |
207 | | type -= 1000; |
208 | | *pnZMFlag = HAS_Z; |
209 | | } |
210 | | /* PostGIS >= 2 : ISO SQL/MM style M types ? */ |
211 | | else if (type >= 2000 && type < 3000) { |
212 | | type -= 2000; |
213 | | *pnZMFlag = HAS_M; |
214 | | } |
215 | | /* PostGIS >= 2 : ISO SQL/MM style ZM types ? */ |
216 | | else if (type >= 3000 && type < 4000) { |
217 | | type -= 3000; |
218 | | *pnZMFlag = HAS_Z | HAS_M; |
219 | | } |
220 | | /* PostGIS 1.X EWKB : Extended WKB Z or ZM ? */ |
221 | | else if ((type & WKBZOFFSET_NONISO) != 0) { |
222 | | if ((type & WKBMOFFSET_NONISO) != 0) |
223 | | *pnZMFlag = HAS_Z | HAS_M; |
224 | | else |
225 | | *pnZMFlag = HAS_Z; |
226 | | type &= 0x00FFFFFF; |
227 | | } |
228 | | /* PostGIS 1.X EWKB: Extended WKB M ? */ |
229 | | else if ((type & WKBMOFFSET_NONISO) != 0) { |
230 | | *pnZMFlag = HAS_M; |
231 | | type &= 0x00FFFFFF; |
232 | | } |
233 | | if (type >= 0 && type < WKB_TYPE_COUNT) |
234 | | return w->typemap[type]; |
235 | | else |
236 | | return 0; |
237 | | } |
238 | | |
239 | | /* |
240 | | ** Read the WKB type number from a wkbObj without |
241 | | ** advancing the read pointer. |
242 | | */ |
243 | | static int wkbType(wkbObj *w, int *pnZMFlag) { |
244 | | int t; |
245 | | memcpy(&t, (w->ptr + 1), sizeof(int)); |
246 | | return wkbTypeMap(w, t, pnZMFlag); |
247 | | } |
248 | | |
249 | | /* |
250 | | ** Read the type number of the first element of a |
251 | | ** collection without advancing the read pointer. |
252 | | */ |
253 | | static int wkbCollectionSubType(wkbObj *w, int *pnZMFlag) { |
254 | | int t; |
255 | | memcpy(&t, (w->ptr + 1 + 4 + 4 + 1), sizeof(int)); |
256 | | return wkbTypeMap(w, t, pnZMFlag); |
257 | | } |
258 | | |
259 | | /* |
260 | | ** Read one byte from the WKB and advance the read pointer |
261 | | */ |
262 | | static char wkbReadChar(wkbObj *w) { |
263 | | char c; |
264 | | memcpy(&c, w->ptr, sizeof(char)); |
265 | | w->ptr += sizeof(char); |
266 | | return c; |
267 | | } |
268 | | |
269 | | /* |
270 | | ** Read one integer from the WKB and advance the read pointer. |
271 | | ** We assume the endianness of the WKB is the same as this machine. |
272 | | */ |
273 | | static inline int wkbReadInt(wkbObj *w) { |
274 | | int i; |
275 | | memcpy(&i, w->ptr, sizeof(int)); |
276 | | w->ptr += sizeof(int); |
277 | | return i; |
278 | | } |
279 | | |
280 | | /* |
281 | | ** Read one pointObj (two doubles) from the WKB and advance the read pointer. |
282 | | ** We assume the endianness of the WKB is the same as this machine. |
283 | | */ |
284 | | static inline void wkbReadPointP(wkbObj *w, pointObj *p, int nZMFlag) { |
285 | | memcpy(&(p->x), w->ptr, sizeof(double)); |
286 | | w->ptr += sizeof(double); |
287 | | memcpy(&(p->y), w->ptr, sizeof(double)); |
288 | | w->ptr += sizeof(double); |
289 | | if (nZMFlag & HAS_Z) { |
290 | | memcpy(&(p->z), w->ptr, sizeof(double)); |
291 | | w->ptr += sizeof(double); |
292 | | } else { |
293 | | p->z = 0; |
294 | | } |
295 | | if (nZMFlag & HAS_M) { |
296 | | memcpy(&(p->m), w->ptr, sizeof(double)); |
297 | | w->ptr += sizeof(double); |
298 | | } else { |
299 | | p->m = 0; |
300 | | } |
301 | | } |
302 | | |
303 | | /* |
304 | | ** Read one pointObj (two doubles) from the WKB and advance the read pointer. |
305 | | ** We assume the endianness of the WKB is the same as this machine. |
306 | | */ |
307 | | static inline pointObj wkbReadPoint(wkbObj *w, int nZMFlag) { |
308 | | pointObj p; |
309 | | wkbReadPointP(w, &p, nZMFlag); |
310 | | return p; |
311 | | } |
312 | | |
313 | | /* |
314 | | ** Read a "point array" and return an allocated lineObj. |
315 | | ** A point array is a WKB fragment that starts with a |
316 | | ** point count, which is followed by that number of doubles * 2. |
317 | | ** Linestrings, circular strings, polygon rings, all show this |
318 | | ** form. |
319 | | */ |
320 | | static void wkbReadLine(wkbObj *w, lineObj *line, int nZMFlag) { |
321 | | pointObj p; |
322 | | const int npoints = wkbReadInt(w); |
323 | | if (npoints > (int)((w->size - (w->ptr - w->wkb)) / 16)) |
324 | | return; |
325 | | |
326 | | line->numpoints = npoints; |
327 | | line->point = (pointObj *)msSmallMalloc(npoints * sizeof(pointObj)); |
328 | | for (int i = 0; i < npoints; i++) { |
329 | | wkbReadPointP(w, &p, nZMFlag); |
330 | | line->point[i] = p; |
331 | | } |
332 | | } |
333 | | |
334 | | /* |
335 | | ** Advance the read pointer past a geometry without returning any |
336 | | ** values. Used for skipping un-drawable elements in a collection. |
337 | | */ |
338 | | static void wkbSkipGeometry(wkbObj *w) { |
339 | | /*endian = */ wkbReadChar(w); |
340 | | int nZMFlag; |
341 | | const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag); |
342 | | const int nCoordDim = 2 + (((nZMFlag & HAS_Z) != 0) ? 1 : 0) + |
343 | | (((nZMFlag & HAS_M) != 0) ? 1 : 0); |
344 | | switch (type) { |
345 | | case WKB_POINT: |
346 | | w->ptr += nCoordDim * sizeof(double); |
347 | | break; |
348 | | case WKB_CIRCULARSTRING: |
349 | | case WKB_LINESTRING: { |
350 | | const int npoints = wkbReadInt(w); |
351 | | w->ptr += sizeof(double) * npoints * nCoordDim; |
352 | | break; |
353 | | } |
354 | | case WKB_POLYGON: { |
355 | | const int nrings = wkbReadInt(w); |
356 | | if (nrings > (int)((w->size - (w->ptr - w->wkb)) / 4)) |
357 | | return; |
358 | | for (int i = 0; i < nrings; i++) { |
359 | | const int npoints = wkbReadInt(w); |
360 | | w->ptr += sizeof(double) * npoints * nCoordDim; |
361 | | } |
362 | | break; |
363 | | } |
364 | | case WKB_MULTIPOINT: |
365 | | case WKB_MULTILINESTRING: |
366 | | case WKB_MULTIPOLYGON: |
367 | | case WKB_GEOMETRYCOLLECTION: |
368 | | case WKB_COMPOUNDCURVE: |
369 | | case WKB_CURVEPOLYGON: |
370 | | case WKB_MULTICURVE: |
371 | | case WKB_MULTISURFACE: { |
372 | | const int ngeoms = wkbReadInt(w); |
373 | | if (ngeoms > (int)((w->size - (w->ptr - w->wkb)) / 4)) |
374 | | return; |
375 | | for (int i = 0; i < ngeoms; i++) { |
376 | | wkbSkipGeometry(w); |
377 | | } |
378 | | break; |
379 | | } |
380 | | } |
381 | | } |
382 | | |
383 | | /* |
384 | | ** Convert a WKB point to a shapeObj, advancing the read pointer as we go. |
385 | | */ |
386 | | static int wkbConvPointToShape(wkbObj *w, shapeObj *shape) { |
387 | | /*endian = */ wkbReadChar(w); |
388 | | int nZMFlag; |
389 | | const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag); |
390 | | |
391 | | if (type != WKB_POINT) |
392 | | return MS_FAILURE; |
393 | | |
394 | | if (!(shape->type == MS_SHAPE_POINT)) |
395 | | return MS_FAILURE; |
396 | | lineObj line; |
397 | | line.numpoints = 1; |
398 | | line.point = (pointObj *)msSmallMalloc(sizeof(pointObj)); |
399 | | line.point[0] = wkbReadPoint(w, nZMFlag); |
400 | | msAddLineDirectly(shape, &line); |
401 | | return MS_SUCCESS; |
402 | | } |
403 | | |
404 | | /* |
405 | | ** Convert a WKB line string to a shapeObj, advancing the read pointer as we go. |
406 | | */ |
407 | | static int wkbConvLineStringToShape(wkbObj *w, shapeObj *shape) { |
408 | | /*endian = */ wkbReadChar(w); |
409 | | int nZMFlag; |
410 | | const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag); |
411 | | |
412 | | if (type != WKB_LINESTRING) |
413 | | return MS_FAILURE; |
414 | | |
415 | | lineObj line; |
416 | | wkbReadLine(w, &line, nZMFlag); |
417 | | msAddLineDirectly(shape, &line); |
418 | | |
419 | | return MS_SUCCESS; |
420 | | } |
421 | | |
422 | | /* |
423 | | ** Convert a WKB polygon to a shapeObj, advancing the read pointer as we go. |
424 | | */ |
425 | | static int wkbConvPolygonToShape(wkbObj *w, shapeObj *shape) { |
426 | | /*endian = */ wkbReadChar(w); |
427 | | int nZMFlag; |
428 | | const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag); |
429 | | |
430 | | if (type != WKB_POLYGON) |
431 | | return MS_FAILURE; |
432 | | |
433 | | /* How many rings? */ |
434 | | const int nrings = wkbReadInt(w); |
435 | | if (nrings > (int)((w->size - (w->ptr - w->wkb)) / 4)) |
436 | | return MS_FAILURE; |
437 | | |
438 | | /* Add each ring to the shape */ |
439 | | lineObj line; |
440 | | for (int i = 0; i < nrings; i++) { |
441 | | wkbReadLine(w, &line, nZMFlag); |
442 | | msAddLineDirectly(shape, &line); |
443 | | } |
444 | | |
445 | | return MS_SUCCESS; |
446 | | } |
447 | | |
448 | | /* |
449 | | ** Convert a WKB curve polygon to a shapeObj, advancing the read pointer as we |
450 | | *go. |
451 | | ** The arc portions of the rings will be stroked to linestrings as they |
452 | | ** are read by the underlying circular string handling. |
453 | | */ |
454 | | static int wkbConvCurvePolygonToShape(wkbObj *w, shapeObj *shape) { |
455 | | const int was_poly = (shape->type == MS_SHAPE_POLYGON); |
456 | | |
457 | | /*endian = */ wkbReadChar(w); |
458 | | int nZMFlag; |
459 | | const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag); |
460 | | if (type != WKB_CURVEPOLYGON) |
461 | | return MS_FAILURE; |
462 | | |
463 | | const int ncomponents = wkbReadInt(w); |
464 | | if (ncomponents > (int)((w->size - (w->ptr - w->wkb)) / 4)) |
465 | | return MS_FAILURE; |
466 | | |
467 | | /* Lower the allowed dimensionality so we can |
468 | | * catch the linear ring components */ |
469 | | shape->type = MS_SHAPE_LINE; |
470 | | |
471 | | int failures = 0; |
472 | | for (int i = 0; i < ncomponents; i++) { |
473 | | if (wkbConvGeometryToShape(w, shape) == MS_FAILURE) { |
474 | | wkbSkipGeometry(w); |
475 | | failures++; |
476 | | } |
477 | | } |
478 | | |
479 | | /* Go back to expected dimensionality */ |
480 | | if (was_poly) |
481 | | shape->type = MS_SHAPE_POLYGON; |
482 | | |
483 | | if (failures == ncomponents) |
484 | | return MS_FAILURE; |
485 | | else |
486 | | return MS_SUCCESS; |
487 | | } |
488 | | |
489 | | /* |
490 | | ** Convert a WKB circular string to a shapeObj, advancing the read pointer as we |
491 | | *go. |
492 | | ** Arcs will be stroked to linestrings. |
493 | | */ |
494 | | static int wkbConvCircularStringToShape(wkbObj *w, shapeObj *shape) { |
495 | | /*endian = */ wkbReadChar(w); |
496 | | int nZMFlag; |
497 | | const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag); |
498 | | |
499 | | if (type != WKB_CIRCULARSTRING) |
500 | | return MS_FAILURE; |
501 | | |
502 | | lineObj line = {0, nullptr}; |
503 | | /* Stroke the string into a point array */ |
504 | | if (arcStrokeCircularString(w, SEGMENT_ANGLE, &line, nZMFlag) == MS_FAILURE) { |
505 | | if (line.point) |
506 | | free(line.point); |
507 | | return MS_FAILURE; |
508 | | } |
509 | | |
510 | | /* Fill in the lineObj */ |
511 | | if (line.numpoints > 0) { |
512 | | msAddLine(shape, &line); |
513 | | if (line.point) |
514 | | free(line.point); |
515 | | } |
516 | | |
517 | | return MS_SUCCESS; |
518 | | } |
519 | | |
520 | | /* |
521 | | ** Compound curves need special handling. First we load |
522 | | ** each component of the curve on the a lineObj in a shape. |
523 | | ** Then we merge those lineObjs into a single lineObj. This |
524 | | ** allows compound curves to serve as closed rings in |
525 | | ** curve polygons. |
526 | | */ |
527 | | static int wkbConvCompoundCurveToShape(wkbObj *w, shapeObj *shape) { |
528 | | /*endian = */ wkbReadChar(w); |
529 | | int nZMFlag; |
530 | | const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag); |
531 | | |
532 | | /* Init our shape buffer */ |
533 | | shapeObj shapebuf; |
534 | | msInitShape(&shapebuf); |
535 | | |
536 | | if (type != WKB_COMPOUNDCURVE) |
537 | | return MS_FAILURE; |
538 | | |
539 | | /* How many components in the compound curve? */ |
540 | | const int ncomponents = wkbReadInt(w); |
541 | | if (ncomponents > (int)((w->size - (w->ptr - w->wkb)) / 4)) |
542 | | return MS_FAILURE; |
543 | | |
544 | | /* We'll load each component onto a line in a shape */ |
545 | | for (int i = 0; i < ncomponents; i++) |
546 | | wkbConvGeometryToShape(w, &shapebuf); |
547 | | |
548 | | /* Do nothing on empty */ |
549 | | if (shapebuf.numlines == 0) |
550 | | return MS_FAILURE; |
551 | | |
552 | | /* Count the total number of points */ |
553 | | int npoints = 0; |
554 | | for (int i = 0; i < shapebuf.numlines; i++) |
555 | | npoints += shapebuf.line[i].numpoints; |
556 | | |
557 | | /* Do nothing on empty */ |
558 | | if (npoints == 0) |
559 | | return MS_FAILURE; |
560 | | |
561 | | lineObj line; |
562 | | line.numpoints = npoints; |
563 | | line.point = (pointObj *)msSmallMalloc(sizeof(pointObj) * npoints); |
564 | | |
565 | | /* Copy in the points */ |
566 | | npoints = 0; |
567 | | for (int i = 0; i < shapebuf.numlines; i++) { |
568 | | for (int j = 0; j < shapebuf.line[i].numpoints; j++) { |
569 | | /* Don't add a start point that duplicates an endpoint */ |
570 | | if (j == 0 && i > 0 && |
571 | | memcmp(&(line.point[npoints - 1]), &(shapebuf.line[i].point[j]), |
572 | | sizeof(pointObj)) == 0) { |
573 | | continue; |
574 | | } |
575 | | line.point[npoints++] = shapebuf.line[i].point[j]; |
576 | | } |
577 | | } |
578 | | line.numpoints = npoints; |
579 | | |
580 | | /* Clean up */ |
581 | | msFreeShape(&shapebuf); |
582 | | |
583 | | /* Fill in the lineObj */ |
584 | | msAddLineDirectly(shape, &line); |
585 | | |
586 | | return MS_SUCCESS; |
587 | | } |
588 | | |
589 | | /* |
590 | | ** Convert a WKB collection string to a shapeObj, advancing the read pointer as |
591 | | *we go. |
592 | | ** Many WKB types (MultiPoint, MultiLineString, MultiPolygon, MultiSurface, |
593 | | ** MultiCurve, GeometryCollection) can be treated identically as collections |
594 | | ** (they start with endian, type number and count of sub-elements, then provide |
595 | | *the |
596 | | ** subelements as WKB) so are handled with this one function. |
597 | | */ |
598 | | static int wkbConvCollectionToShape(wkbObj *w, shapeObj *shape) { |
599 | | /*endian = */ wkbReadChar(w); |
600 | | int nZMFlag; |
601 | | /*type = */ wkbTypeMap(w, wkbReadInt(w), &nZMFlag); |
602 | | const int ncomponents = wkbReadInt(w); |
603 | | if (ncomponents > (int)((w->size - (w->ptr - w->wkb)) / 4)) |
604 | | return MS_FAILURE; |
605 | | |
606 | | /* |
607 | | * If we can draw any portion of the collection, we will, |
608 | | * but if all the components fail, we will draw nothing. |
609 | | */ |
610 | | int failures = 0; |
611 | | for (int i = 0; i < ncomponents; i++) { |
612 | | if (wkbConvGeometryToShape(w, shape) == MS_FAILURE) { |
613 | | wkbSkipGeometry(w); |
614 | | failures++; |
615 | | } |
616 | | } |
617 | | if (failures == ncomponents || ncomponents == 0) |
618 | | return MS_FAILURE; |
619 | | else |
620 | | return MS_SUCCESS; |
621 | | } |
622 | | |
623 | | /* |
624 | | ** Generic handler to switch to the appropriate function for the WKB type. |
625 | | ** Note that we also handle switching here to avoid processing shapes |
626 | | ** we will be unable to draw. Example: we can't draw point features as |
627 | | ** a MS_SHAPE_LINE layer, so if the type is WKB_POINT and the layer is |
628 | | ** MS_SHAPE_LINE, we exit before converting. |
629 | | */ |
630 | | static int wkbConvGeometryToShape(wkbObj *w, shapeObj *shape) { |
631 | | int nZMFlag; |
632 | | const int wkbtype = wkbType(w, &nZMFlag); /* Peak at the type number */ |
633 | | |
634 | | switch (wkbtype) { |
635 | | /* Recurse into anonymous collections */ |
636 | | case WKB_GEOMETRYCOLLECTION: |
637 | | return wkbConvCollectionToShape(w, shape); |
638 | | /* Handle area types */ |
639 | | case WKB_POLYGON: |
640 | | return wkbConvPolygonToShape(w, shape); |
641 | | case WKB_MULTIPOLYGON: |
642 | | return wkbConvCollectionToShape(w, shape); |
643 | | case WKB_CURVEPOLYGON: |
644 | | return wkbConvCurvePolygonToShape(w, shape); |
645 | | case WKB_MULTISURFACE: |
646 | | return wkbConvCollectionToShape(w, shape); |
647 | | } |
648 | | |
649 | | /* We can't convert any of the following types into polygons */ |
650 | | if (shape->type == MS_SHAPE_POLYGON) |
651 | | return MS_FAILURE; |
652 | | |
653 | | /* Handle linear types */ |
654 | | switch (wkbtype) { |
655 | | case WKB_LINESTRING: |
656 | | return wkbConvLineStringToShape(w, shape); |
657 | | case WKB_CIRCULARSTRING: |
658 | | return wkbConvCircularStringToShape(w, shape); |
659 | | case WKB_COMPOUNDCURVE: |
660 | | return wkbConvCompoundCurveToShape(w, shape); |
661 | | case WKB_MULTILINESTRING: |
662 | | return wkbConvCollectionToShape(w, shape); |
663 | | case WKB_MULTICURVE: |
664 | | return wkbConvCollectionToShape(w, shape); |
665 | | } |
666 | | |
667 | | /* We can't convert any of the following types into lines */ |
668 | | if (shape->type == MS_SHAPE_LINE) |
669 | | return MS_FAILURE; |
670 | | |
671 | | /* Handle point types */ |
672 | | switch (wkbtype) { |
673 | | case WKB_POINT: |
674 | | return wkbConvPointToShape(w, shape); |
675 | | case WKB_MULTIPOINT: |
676 | | return wkbConvCollectionToShape(w, shape); |
677 | | } |
678 | | |
679 | | /* This is a WKB type we don't know about! */ |
680 | | return MS_FAILURE; |
681 | | } |
682 | | |
683 | | /* |
684 | | ** What side of p1->p2 is q on? |
685 | | */ |
686 | | static inline int arcSegmentSide(const pointObj &p1, const pointObj &p2, |
687 | | const pointObj &q) { |
688 | | double side = ((q.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (q.y - p1.y)); |
689 | | if (FP_EQ(side, 0.0)) { |
690 | | return FP_COLINEAR; |
691 | | } else { |
692 | | if (side < 0.0) |
693 | | return FP_LEFT; |
694 | | else |
695 | | return FP_RIGHT; |
696 | | } |
697 | | } |
698 | | |
699 | | /* |
700 | | ** Calculate the center of the circle defined by three points. |
701 | | ** Using matrix approach from |
702 | | *http://mathforum.org/library/drmath/view/55239.html |
703 | | */ |
704 | | static int arcCircleCenter(const pointObj &p1, const pointObj &p2, |
705 | | const pointObj &p3, pointObj *center, |
706 | | double *radius) { |
707 | | pointObj c{}; // initialize |
708 | | double r; |
709 | | |
710 | | /* Circle is closed, so p2 must be opposite p1 & p3. */ |
711 | | if ((fabs(p1.x - p3.x) < FP_EPSILON) && (fabs(p1.y - p3.y) < FP_EPSILON)) { |
712 | | c.x = p1.x + (p2.x - p1.x) / 2.0; |
713 | | c.y = p1.y + (p2.y - p1.y) / 2.0; |
714 | | r = sqrt(pow(c.x - p1.x, 2.0) + pow(c.y - p1.y, 2.0)); |
715 | | } |
716 | | /* There is no circle here, the points are actually co-linear */ |
717 | | else if (arcSegmentSide(p1, p3, p2) == FP_COLINEAR) { |
718 | | return MS_FAILURE; |
719 | | } |
720 | | /* Calculate the center and radius. */ |
721 | | else { |
722 | | |
723 | | /* Radius */ |
724 | | const double dx21 = p2.x - p1.x; |
725 | | const double dy21 = p2.y - p1.y; |
726 | | const double dx31 = p3.x - p1.x; |
727 | | const double dy31 = p3.y - p1.y; |
728 | | |
729 | | const double h21 = pow(dx21, 2.0) + pow(dy21, 2.0); |
730 | | const double h31 = pow(dx31, 2.0) + pow(dy31, 2.0); |
731 | | |
732 | | /* 2 * |Cross product|, d<0 means clockwise and d>0 counterclockwise |
733 | | * sweeping angle */ |
734 | | const double d = 2 * (dx21 * dy31 - dx31 * dy21); |
735 | | |
736 | | c.x = p1.x + (h21 * dy31 - h31 * dy21) / d; |
737 | | c.y = p1.y - (h21 * dx31 - h31 * dx21) / d; |
738 | | r = sqrt(pow(c.x - p1.x, 2) + pow(c.y - p1.y, 2)); |
739 | | } |
740 | | |
741 | | if (radius) |
742 | | *radius = r; |
743 | | if (center) |
744 | | *center = c; |
745 | | |
746 | | return MS_SUCCESS; |
747 | | } |
748 | | |
749 | | /* |
750 | | ** Write a stroked version of the circle defined by three points into a |
751 | | ** point buffer. The segment_angle (degrees) is the coverage of each stroke |
752 | | *segment, |
753 | | ** and depending on whether this is the first arc in a circularstring, |
754 | | ** you might want to include_first |
755 | | */ |
756 | | static int arcStrokeCircle(const pointObj &p1, const pointObj &p2, |
757 | | const pointObj &p3, double segment_angle, |
758 | | int include_first, std::vector<pointObj> &pa) { |
759 | | const int side = |
760 | | arcSegmentSide(p1, p3, p2); /* What side of p1,p3 is the middle point? */ |
761 | | int is_closed = MS_FALSE; |
762 | | |
763 | | /* We need to know if we're dealing with a circle early */ |
764 | | if (FP_EQ(p1.x, p3.x) && FP_EQ(p1.y, p3.y)) |
765 | | is_closed = MS_TRUE; |
766 | | |
767 | | /* Check if the "arc" is actually straight */ |
768 | | if (!is_closed && side == FP_COLINEAR) { |
769 | | /* We just need to write in the end points */ |
770 | | if (include_first) |
771 | | pointArrayAddPoint(pa, p1); |
772 | | pointArrayAddPoint(pa, p3); |
773 | | return MS_SUCCESS; |
774 | | } |
775 | | |
776 | | /* We should always be able to find the center of a non-linear arc */ |
777 | | pointObj center; /* Center of our circular arc */ |
778 | | double radius; /* Radius of our circular arc */ |
779 | | if (arcCircleCenter(p1, p2, p3, ¢er, &radius) == MS_FAILURE) |
780 | | return MS_FAILURE; |
781 | | |
782 | | /* Calculate the angles relative to center that our three points represent */ |
783 | | const double a1 = atan2(p1.y - center.y, p1.x - center.x); |
784 | | /* UNUSED |
785 | | a2 = atan2(p2.y - center.y, p2.x - center.x); |
786 | | */ |
787 | | const double a3 = atan2(p3.y - center.y, p3.x - center.x); |
788 | | double segment_angle_r = |
789 | | M_PI * segment_angle / 180.0; /* Segment angle in radians */ |
790 | | |
791 | | double sweep_angle_r; /* Total angular size of our circular arc in radians */ |
792 | | /* Closed-circle case, we sweep the whole circle! */ |
793 | | if (is_closed) { |
794 | | sweep_angle_r = 2.0 * M_PI; |
795 | | } |
796 | | /* Clockwise sweep direction */ |
797 | | else if (side == FP_LEFT) { |
798 | | if (a3 > a1) /* Wrapping past 180? */ |
799 | | sweep_angle_r = a1 + (2.0 * M_PI - a3); |
800 | | else |
801 | | sweep_angle_r = a1 - a3; |
802 | | } |
803 | | /* Counter-clockwise sweep direction */ |
804 | | else if (side == FP_RIGHT) { |
805 | | if (a3 > a1) /* Wrapping past 180? */ |
806 | | sweep_angle_r = a3 - a1; |
807 | | else |
808 | | sweep_angle_r = a3 + (2.0 * M_PI - a1); |
809 | | } else |
810 | | sweep_angle_r = 0.0; |
811 | | |
812 | | /* We don't have enough resolution, let's invert our strategy. */ |
813 | | if ((sweep_angle_r / segment_angle_r) < SEGMENT_MINPOINTS) { |
814 | | segment_angle_r = sweep_angle_r / (SEGMENT_MINPOINTS + 1); |
815 | | } |
816 | | |
817 | | /* We don't have enough resolution to stroke this arc, |
818 | | * so just join the start to the end. */ |
819 | | if (sweep_angle_r < segment_angle_r) { |
820 | | if (include_first) |
821 | | pointArrayAddPoint(pa, p1); |
822 | | pointArrayAddPoint(pa, p3); |
823 | | return MS_SUCCESS; |
824 | | } |
825 | | |
826 | | /* How many edges to generate (we add the final edge |
827 | | * by sticking on the last point */ |
828 | | int num_edges = floor(sweep_angle_r / fabs(segment_angle_r)); |
829 | | |
830 | | /* Go backwards (negative angular steps) if we are stroking clockwise */ |
831 | | if (side == FP_LEFT) |
832 | | segment_angle_r *= -1; |
833 | | |
834 | | /* What point should we start with? */ |
835 | | double current_angle_r; /* What angle are we generating now (radians)? */ |
836 | | if (include_first) { |
837 | | current_angle_r = a1; |
838 | | } else { |
839 | | current_angle_r = a1 + segment_angle_r; |
840 | | num_edges--; |
841 | | } |
842 | | |
843 | | /* For each edge, increment or decrement by our segment angle */ |
844 | | for (int i = 0; i < num_edges; i++) { |
845 | | if (segment_angle_r > 0.0 && current_angle_r > M_PI) |
846 | | current_angle_r -= 2 * M_PI; |
847 | | if (segment_angle_r < 0.0 && current_angle_r < -1 * M_PI) |
848 | | current_angle_r -= 2 * M_PI; |
849 | | pointObj p; |
850 | | p.x = center.x + radius * cos(current_angle_r); |
851 | | p.y = center.y + radius * sin(current_angle_r); |
852 | | p.z = 0; |
853 | | p.m = 0; |
854 | | pointArrayAddPoint(pa, p); |
855 | | current_angle_r += segment_angle_r; |
856 | | } |
857 | | |
858 | | /* Add the last point */ |
859 | | pointArrayAddPoint(pa, p3); |
860 | | return MS_SUCCESS; |
861 | | } |
862 | | |
863 | | /* |
864 | | ** This function does not actually take WKB as input, it takes the |
865 | | ** WKB starting from the numpoints integer. Each three-point edge |
866 | | ** is stroked into a linestring and appended into the lineObj |
867 | | ** argument. |
868 | | */ |
869 | | static int arcStrokeCircularString(wkbObj *w, double segment_angle, |
870 | | lineObj *line, int nZMFlag) { |
871 | | if (!w || !line) |
872 | | return MS_FAILURE; |
873 | | |
874 | | const int npoints = wkbReadInt(w); |
875 | | const int nedges = npoints / 2; |
876 | | |
877 | | /* All CircularStrings have an odd number of points */ |
878 | | if (npoints < 3 || npoints % 2 != 1) |
879 | | return MS_FAILURE; |
880 | | |
881 | | /* Make a large guess at how much space we'll need */ |
882 | | auto pa = pointArrayNew(nedges * 180 / segment_angle); |
883 | | |
884 | | pointObj p1, p2, p3; |
885 | | wkbReadPointP(w, &p3, nZMFlag); |
886 | | |
887 | | /* Fill out the point array with stroked arcs */ |
888 | | int edge = 0; |
889 | | while (edge < nedges) { |
890 | | p1 = p3; |
891 | | wkbReadPointP(w, &p2, nZMFlag); |
892 | | wkbReadPointP(w, &p3, nZMFlag); |
893 | | if (arcStrokeCircle(p1, p2, p3, segment_angle, edge ? 0 : 1, pa) == |
894 | | MS_FAILURE) { |
895 | | return MS_FAILURE; |
896 | | } |
897 | | edge++; |
898 | | } |
899 | | |
900 | | /* Copy the point array into the line */ |
901 | | line->numpoints = static_cast<int>(pa.size()); |
902 | | line->point = (pointObj *)msSmallMalloc(line->numpoints * sizeof(pointObj)); |
903 | | memcpy(line->point, pa.data(), line->numpoints * sizeof(pointObj)); |
904 | | |
905 | | return MS_SUCCESS; |
906 | | } |
907 | | |
908 | | /* |
909 | | ** For LAYER types that are not the usual ones (charts, |
910 | | ** annotations, etc) we will convert to a shape type |
911 | | ** that "makes sense" given the WKB input. We do this |
912 | | ** by peaking at the type number of the first collection |
913 | | ** sub-element. |
914 | | */ |
915 | | static int msPostGISFindBestType(wkbObj *w, shapeObj *shape) { |
916 | | /* What kind of geometry is this? */ |
917 | | int nZMFlag; |
918 | | int wkbtype = wkbType(w, &nZMFlag); |
919 | | |
920 | | /* Generic collection, we need to look a little deeper. */ |
921 | | if (wkbtype == WKB_GEOMETRYCOLLECTION) |
922 | | wkbtype = wkbCollectionSubType(w, &nZMFlag); |
923 | | |
924 | | switch (wkbtype) { |
925 | | case WKB_POLYGON: |
926 | | case WKB_CURVEPOLYGON: |
927 | | case WKB_MULTIPOLYGON: |
928 | | shape->type = MS_SHAPE_POLYGON; |
929 | | break; |
930 | | case WKB_LINESTRING: |
931 | | case WKB_CIRCULARSTRING: |
932 | | case WKB_COMPOUNDCURVE: |
933 | | case WKB_MULTICURVE: |
934 | | case WKB_MULTILINESTRING: |
935 | | shape->type = MS_SHAPE_LINE; |
936 | | break; |
937 | | case WKB_POINT: |
938 | | case WKB_MULTIPOINT: |
939 | | shape->type = MS_SHAPE_POINT; |
940 | | break; |
941 | | default: |
942 | | return MS_FAILURE; |
943 | | } |
944 | | |
945 | | return wkbConvGeometryToShape(w, shape); |
946 | | } |
947 | | |
948 | | /* |
949 | | ** Get the PostGIS version number from the database as integer. |
950 | | ** Versions are multiplied out as with PgSQL: 1.5.2 -> 10502, 2.0.0 -> 20000. |
951 | | */ |
952 | | static int msPostGISRetrieveVersion(PGconn *pgconn) { |
953 | | static const char *sql = "SELECT postgis_version()"; |
954 | | if (!pgconn) { |
955 | | msSetError(MS_QUERYERR, "No open connection.", |
956 | | "msPostGISRetrieveVersion()"); |
957 | | return MS_FAILURE; |
958 | | } |
959 | | |
960 | | PGresult *pgresult = |
961 | | PQexecParams(pgconn, sql, 0, nullptr, nullptr, nullptr, nullptr, 0); |
962 | | |
963 | | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) { |
964 | | msDebug("Error executing SQL: (%s) in msPostGISRetrieveVersion()", sql); |
965 | | msSetError(MS_QUERYERR, "Error executing SQL. check server logs.", |
966 | | "msPostGISRetrieveVersion()"); |
967 | | return MS_FAILURE; |
968 | | } |
969 | | |
970 | | if (PQgetisnull(pgresult, 0, 0)) { |
971 | | PQclear(pgresult); |
972 | | msSetError(MS_QUERYERR, "Null result returned.", |
973 | | "msPostGISRetrieveVersion()"); |
974 | | return MS_FAILURE; |
975 | | } |
976 | | |
977 | | std::string strVersion = PQgetvalue(pgresult, 0, 0); |
978 | | PQclear(pgresult); |
979 | | |
980 | | char *ptr = &strVersion[0]; |
981 | | char *strParts[3] = {nullptr, nullptr, nullptr}; |
982 | | int j = 0; |
983 | | strParts[j++] = &strVersion[0]; |
984 | | while (*ptr != '\0' && j < 3) { |
985 | | if (*ptr == '.') { |
986 | | *ptr = '\0'; |
987 | | strParts[j++] = ptr + 1; |
988 | | } |
989 | | if (*ptr == ' ') { |
990 | | *ptr = '\0'; |
991 | | break; |
992 | | } |
993 | | ptr++; |
994 | | } |
995 | | |
996 | | int version = 0; |
997 | | int factor = 10000; |
998 | | for (int i = 0; i < j; i++) { |
999 | | version += factor * atoi(strParts[i]); |
1000 | | factor = factor / 100; |
1001 | | } |
1002 | | |
1003 | | return version; |
1004 | | } |
1005 | | |
1006 | | /* |
1007 | | ** Get the PostgreSQL server version number from the database as integer. |
1008 | | ** 12.7.1 ==> 120701 |
1009 | | */ |
1010 | | static int msPostGISRetrievePostgreSQLVersion(PGconn *pgconn) { |
1011 | | static const char *sql = "SELECT version()"; |
1012 | | if (!pgconn) { |
1013 | | msSetError(MS_QUERYERR, "No open connection.", |
1014 | | "msPostGISRetrievePostgreSQLVersion()"); |
1015 | | return MS_FAILURE; |
1016 | | } |
1017 | | |
1018 | | PGresult *pgresult = |
1019 | | PQexecParams(pgconn, sql, 0, nullptr, nullptr, nullptr, nullptr, 0); |
1020 | | |
1021 | | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) { |
1022 | | msDebug("Error executing SQL: (%s) in msPostGISRetrievePostgreSQLVersion()", |
1023 | | sql); |
1024 | | msSetError(MS_QUERYERR, "Error executing SQL. check server logs.", |
1025 | | "msPostGISRetrievePostgreSQLVersion()"); |
1026 | | return MS_FAILURE; |
1027 | | } |
1028 | | |
1029 | | if (PQgetisnull(pgresult, 0, 0)) { |
1030 | | PQclear(pgresult); |
1031 | | msSetError(MS_QUERYERR, "Null result returned.", |
1032 | | "msPostGISRetrievePostgreSQLVersion()"); |
1033 | | return MS_FAILURE; |
1034 | | } |
1035 | | |
1036 | | std::string strVersion = PQgetvalue(pgresult, 0, 0); |
1037 | | PQclear(pgresult); |
1038 | | |
1039 | | // Skip leading "PostgreSQL " (or other vendorized name) |
1040 | | auto nPos = strVersion.find(' '); |
1041 | | if (nPos == std::string::npos) |
1042 | | return 0; |
1043 | | |
1044 | | char *ptr = &strVersion[nPos + 1]; |
1045 | | char *strParts[3] = {nullptr, nullptr, nullptr}; |
1046 | | int j = 0; |
1047 | | strParts[j++] = ptr; |
1048 | | while (*ptr != '\0' && j < 3) { |
1049 | | if (*ptr == '.') { |
1050 | | *ptr = '\0'; |
1051 | | strParts[j++] = ptr + 1; |
1052 | | } |
1053 | | if (*ptr == ' ') { |
1054 | | *ptr = '\0'; |
1055 | | break; |
1056 | | } |
1057 | | ptr++; |
1058 | | } |
1059 | | |
1060 | | int version = 0; |
1061 | | int factor = 10000; |
1062 | | for (int i = 0; i < j; i++) { |
1063 | | version += factor * atoi(strParts[i]); |
1064 | | factor = factor / 100; |
1065 | | } |
1066 | | |
1067 | | return version; |
1068 | | } |
1069 | | |
1070 | | /* |
1071 | | ** msPostGISRetrievePK() |
1072 | | ** |
1073 | | ** Find out that the primary key is for this layer. |
1074 | | ** The layerinfo->fromsource must already be populated and |
1075 | | ** must not be a subquery. |
1076 | | */ |
1077 | | static int msPostGISRetrievePK(layerObj *layer) { |
1078 | | char *sql = nullptr; |
1079 | | |
1080 | | if (layer->debug) { |
1081 | | msDebug("msPostGISRetrievePK called.\n"); |
1082 | | } |
1083 | | |
1084 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
1085 | | |
1086 | | if (layerinfo->pgconn == nullptr) { |
1087 | | msSetError(MS_QUERYERR, "Layer does not have a postgis connection.", |
1088 | | "msPostGISRetrievePK()"); |
1089 | | return MS_FAILURE; |
1090 | | } |
1091 | | |
1092 | | { |
1093 | | /* Attempt to separate fromsource into schema.table */ |
1094 | | std::string schema; |
1095 | | std::string table; |
1096 | | const auto pos_sep = layerinfo->fromsource.find('.'); |
1097 | | if (pos_sep != std::string::npos) { |
1098 | | schema = layerinfo->fromsource.substr(0, pos_sep); |
1099 | | table = layerinfo->fromsource.substr(pos_sep + 1); |
1100 | | |
1101 | | if (layer->debug) { |
1102 | | msDebug("msPostGISRetrievePK(): Found schema %s, table %s.\n", |
1103 | | schema.c_str(), table.c_str()); |
1104 | | } |
1105 | | } |
1106 | | /* |
1107 | | ** PostgreSQL v7.3 and later treat primary keys as constraints. |
1108 | | ** We only support single column primary keys, so multicolumn |
1109 | | ** pks are explicitly excluded from the query. |
1110 | | */ |
1111 | | if (!schema.empty()) { |
1112 | | static const char *v73sql = |
1113 | | "select attname from pg_attribute, pg_constraint, pg_class, " |
1114 | | "pg_namespace where pg_constraint.conrelid = pg_class.oid and " |
1115 | | "pg_class.oid = pg_attribute.attrelid and pg_constraint.contype = " |
1116 | | "'p' and pg_constraint.conkey[1] = pg_attribute.attnum and " |
1117 | | "pg_class.relname = '%s' and pg_class.relnamespace = " |
1118 | | "pg_namespace.oid and pg_namespace.nspname = '%s' and " |
1119 | | "pg_constraint.conkey[2] is null"; |
1120 | | const size_t nSize = schema.size() + table.size() + strlen(v73sql) + 1; |
1121 | | sql = (char *)msSmallMalloc(nSize); |
1122 | | snprintf(sql, nSize, v73sql, table.c_str(), schema.c_str()); |
1123 | | } else { |
1124 | | static const char *v73sql = |
1125 | | "select attname from pg_attribute, pg_constraint, pg_class where " |
1126 | | "pg_constraint.conrelid = pg_class.oid and pg_class.oid = " |
1127 | | "pg_attribute.attrelid and pg_constraint.contype = 'p' and " |
1128 | | "pg_constraint.conkey[1] = pg_attribute.attnum and pg_class.relname " |
1129 | | "= '%s' and pg_table_is_visible(pg_class.oid) and " |
1130 | | "pg_constraint.conkey[2] is null"; |
1131 | | const size_t nSize = layerinfo->fromsource.size() + strlen(v73sql) + 1; |
1132 | | sql = (char *)msSmallMalloc(nSize); |
1133 | | snprintf(sql, nSize, v73sql, layerinfo->fromsource.c_str()); |
1134 | | } |
1135 | | } |
1136 | | |
1137 | | if (layer->debug > 1) { |
1138 | | msDebug("msPostGISRetrievePK: %s\n", sql); |
1139 | | } |
1140 | | |
1141 | | layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
1142 | | |
1143 | | if (layerinfo->pgconn == nullptr) { |
1144 | | msSetError(MS_QUERYERR, "Layer does not have a postgis connection.", |
1145 | | "msPostGISRetrievePK()"); |
1146 | | free(sql); |
1147 | | return MS_FAILURE; |
1148 | | } |
1149 | | |
1150 | | PGresult *pgresult = PQexecParams(layerinfo->pgconn, sql, 0, nullptr, nullptr, |
1151 | | nullptr, nullptr, 0); |
1152 | | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) { |
1153 | | msSetError(MS_QUERYERR, "%s", "msPostGISRetrievePK()", |
1154 | | (std::string("Error executing SQL: ") + sql).c_str()); |
1155 | | return MS_FAILURE; |
1156 | | } |
1157 | | |
1158 | | if (PQntuples(pgresult) < 1) { |
1159 | | if (layer->debug) { |
1160 | | msDebug("msPostGISRetrievePK: No results found.\n"); |
1161 | | } |
1162 | | PQclear(pgresult); |
1163 | | free(sql); |
1164 | | return MS_FAILURE; |
1165 | | } |
1166 | | if (PQntuples(pgresult) > 1) { |
1167 | | if (layer->debug) { |
1168 | | msDebug("msPostGISRetrievePK: Multiple results found.\n"); |
1169 | | } |
1170 | | PQclear(pgresult); |
1171 | | free(sql); |
1172 | | return MS_FAILURE; |
1173 | | } |
1174 | | |
1175 | | if (PQgetisnull(pgresult, 0, 0)) { |
1176 | | if (layer->debug) { |
1177 | | msDebug("msPostGISRetrievePK: Null result returned.\n"); |
1178 | | } |
1179 | | PQclear(pgresult); |
1180 | | free(sql); |
1181 | | return MS_FAILURE; |
1182 | | } |
1183 | | |
1184 | | layerinfo->uid = PQgetvalue(pgresult, 0, 0); |
1185 | | |
1186 | | PQclear(pgresult); |
1187 | | free(sql); |
1188 | | return MS_SUCCESS; |
1189 | | } |
1190 | | |
1191 | | /* |
1192 | | ** msPostGISParseData() |
1193 | | ** |
1194 | | ** Parse the DATA string for geometry column name, table name, |
1195 | | ** unique id column, srid, and SQL string. |
1196 | | */ |
1197 | | static int msPostGISParseData(layerObj *layer) { |
1198 | | assert(layer != nullptr); |
1199 | | assert(layer->layerinfo != nullptr); |
1200 | | |
1201 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)(layer->layerinfo); |
1202 | | |
1203 | | if (layer->debug) { |
1204 | | msDebug("msPostGISParseData called.\n"); |
1205 | | } |
1206 | | |
1207 | | if (!layer->data) { |
1208 | | msSetError( |
1209 | | MS_QUERYERR, |
1210 | | "Missing DATA clause. DATA statement must contain 'geometry_column " |
1211 | | "from table_name' or 'geometry_column from (sub-query) as sub'.", |
1212 | | "msPostGISParseData()"); |
1213 | | return MS_FAILURE; |
1214 | | } |
1215 | | |
1216 | | std::string data(layer->data); |
1217 | | for (char &ch : data) { |
1218 | | if (ch == '\t' || ch == '\r' || ch == '\n') { |
1219 | | ch = ' '; |
1220 | | } |
1221 | | } |
1222 | | |
1223 | | /* |
1224 | | ** Clean up any existing strings first, as we will be populating these fields. |
1225 | | */ |
1226 | | layerinfo->srid.clear(); |
1227 | | layerinfo->uid.clear(); |
1228 | | layerinfo->geomcolumn.clear(); |
1229 | | layerinfo->fromsource.clear(); |
1230 | | |
1231 | | /* |
1232 | | ** Look for the optional ' using ' clauses. |
1233 | | */ |
1234 | | const char *pos_srid = nullptr; |
1235 | | const char *pos_uid = nullptr; |
1236 | | const char *pos_use_1st = nullptr; |
1237 | | const char *pos_use_2nd = nullptr; |
1238 | | { |
1239 | | const char *tmp = strcasestr(data.c_str(), " using "); |
1240 | | while (tmp) { |
1241 | | pos_use_1st = pos_use_2nd; |
1242 | | pos_use_2nd = tmp + 1; |
1243 | | tmp = strcasestr(tmp + 1, " using "); |
1244 | | } |
1245 | | } |
1246 | | |
1247 | | /* |
1248 | | ** What clause appear after 2nd 'using', if set? |
1249 | | */ |
1250 | | if (pos_use_2nd) { |
1251 | | const char *tmp; |
1252 | | for (tmp = pos_use_2nd + 5; *tmp == ' '; tmp++) |
1253 | | ; |
1254 | | if (strncasecmp(tmp, "unique ", 7) == 0) |
1255 | | for (pos_uid = tmp + 7; *pos_uid == ' '; pos_uid++) |
1256 | | ; |
1257 | | if (strncasecmp(tmp, "srid=", 5) == 0) |
1258 | | pos_srid = tmp + 5; |
1259 | | }; |
1260 | | |
1261 | | /* |
1262 | | ** What clause appear after 1st 'using', if set? |
1263 | | */ |
1264 | | if (pos_use_1st) { |
1265 | | const char *tmp; |
1266 | | for (tmp = pos_use_1st + 5; *tmp == ' '; tmp++) |
1267 | | ; |
1268 | | if (strncasecmp(tmp, "unique ", 7) == 0) { |
1269 | | if (pos_uid) { |
1270 | | msSetError(MS_QUERYERR, |
1271 | | "Error parsing PostGIS DATA variable. Too many 'USING " |
1272 | | "UNIQUE' found! %s", |
1273 | | "msPostGISParseData()", layer->data); |
1274 | | return MS_FAILURE; |
1275 | | }; |
1276 | | for (pos_uid = tmp + 7; *pos_uid == ' '; pos_uid++) |
1277 | | ; |
1278 | | }; |
1279 | | if (strncasecmp(tmp, "srid=", 5) == 0) { |
1280 | | if (pos_srid) { |
1281 | | msSetError(MS_QUERYERR, |
1282 | | "Error parsing PostGIS DATA variable. Too many 'USING SRID' " |
1283 | | "found! %s", |
1284 | | "msPostGISParseData()", layer->data); |
1285 | | return MS_FAILURE; |
1286 | | } |
1287 | | pos_srid = tmp + 5; |
1288 | | } |
1289 | | } |
1290 | | |
1291 | | /* |
1292 | | ** Look for the optional ' using unique ID' string first. |
1293 | | */ |
1294 | | if (pos_uid) { |
1295 | | /* Find the end of this case 'using unique ftab_id using srid=33' */ |
1296 | | const char *tmp = strstr(pos_uid, " "); |
1297 | | /* Find the end of this case 'using srid=33 using unique ftab_id' */ |
1298 | | if (!tmp) { |
1299 | | tmp = pos_uid + strlen(pos_uid); |
1300 | | } |
1301 | | layerinfo->uid.assign(pos_uid, tmp - pos_uid); |
1302 | | msStringTrim(layerinfo->uid); |
1303 | | } |
1304 | | |
1305 | | /* |
1306 | | ** Look for the optional ' using srid=333 ' string next. |
1307 | | */ |
1308 | | if (pos_srid) { |
1309 | | const int slength = strspn(pos_srid, "-0123456789"); |
1310 | | if (!slength) { |
1311 | | msSetError(MS_QUERYERR, |
1312 | | "Error parsing PostGIS DATA variable. You specified 'USING " |
1313 | | "SRID' but didn't have any numbers! %s", |
1314 | | "msPostGISParseData()", layer->data); |
1315 | | return MS_FAILURE; |
1316 | | } else { |
1317 | | layerinfo->srid.assign(pos_srid, slength); |
1318 | | msStringTrim(layerinfo->srid); |
1319 | | } |
1320 | | } |
1321 | | |
1322 | | /* |
1323 | | * This is a little hack so the rest of the code works. |
1324 | | * pos_opt should point to the start of the optional blocks. |
1325 | | * |
1326 | | * If they are both set, return the smaller one. |
1327 | | * If pos_use_1st set, then it smaller. |
1328 | | * If one or none is set, return the larger one. |
1329 | | */ |
1330 | | const char *pos_opt = pos_use_1st ? pos_use_1st : pos_use_2nd; |
1331 | | /* No pos_opt? Move it to the end of the string. */ |
1332 | | if (!pos_opt) { |
1333 | | pos_opt = data.c_str() + data.size(); |
1334 | | } |
1335 | | /* Back after the last non-space character. */ |
1336 | | while ((pos_opt > data.c_str()) && (*(pos_opt - 1) == ' ')) { |
1337 | | --pos_opt; |
1338 | | } |
1339 | | |
1340 | | /* |
1341 | | ** Scan for the 'geometry from table' or 'geometry from () as foo' clause. |
1342 | | */ |
1343 | | |
1344 | | /* Find the first non-white character to start from */ |
1345 | | const char *pos_geom; |
1346 | | for (pos_geom = data.c_str(); *pos_geom == ' '; pos_geom++) { |
1347 | | } |
1348 | | |
1349 | | /* Find the end of the geom column name */ |
1350 | | const char *pos_scn = strcasestr(data.c_str(), " from "); |
1351 | | if (!pos_scn) { |
1352 | | msSetError(MS_QUERYERR, |
1353 | | "Error parsing PostGIS DATA variable. Must contain 'geometry " |
1354 | | "from table' or 'geometry from (subselect) as foo'. %s", |
1355 | | "msPostGISParseData()", layer->data); |
1356 | | return MS_FAILURE; |
1357 | | } |
1358 | | |
1359 | | /* Copy the geometry column name */ |
1360 | | layerinfo->geomcolumn.assign(pos_geom, pos_scn - pos_geom); |
1361 | | msStringTrim(layerinfo->geomcolumn); |
1362 | | |
1363 | | /* Copy the table name or sub-select clause */ |
1364 | | for (pos_scn += 6; *pos_scn == ' '; pos_scn++) |
1365 | | ; |
1366 | | if (pos_opt - pos_scn < 1) { |
1367 | | msSetError(MS_QUERYERR, |
1368 | | "Error parsing PostGIS DATA variable. Must contain 'geometry " |
1369 | | "from table' or 'geometry from (subselect) as foo'. %s", |
1370 | | "msPostGISParseData()", layer->data); |
1371 | | return MS_FAILURE; |
1372 | | }; |
1373 | | layerinfo->fromsource.assign(layer->data + (pos_scn - data.c_str()), |
1374 | | pos_opt - pos_scn); |
1375 | | msStringTrim(layerinfo->fromsource); |
1376 | | |
1377 | | /* Something is wrong, our goemetry column and table references are not there. |
1378 | | */ |
1379 | | if (layerinfo->fromsource.empty() || layerinfo->geomcolumn.empty()) { |
1380 | | msSetError(MS_QUERYERR, |
1381 | | "Error parsing PostGIS DATA variable. Must contain 'geometry " |
1382 | | "from table' or 'geometry from (subselect) as foo'. %s", |
1383 | | "msPostGISParseData()", layer->data); |
1384 | | return MS_FAILURE; |
1385 | | } |
1386 | | |
1387 | | /* |
1388 | | ** We didn't find a ' using unique ' in the DATA string so try and find a |
1389 | | ** primary key on the table. |
1390 | | */ |
1391 | | if (layerinfo->uid.empty()) { |
1392 | | if (strstr(layerinfo->fromsource.c_str(), " ")) { |
1393 | | msSetError( |
1394 | | MS_QUERYERR, |
1395 | | "Error parsing PostGIS DATA variable. You must specify 'using " |
1396 | | "unique' when supplying a subselect in the data definition.", |
1397 | | "msPostGISParseData()"); |
1398 | | return MS_FAILURE; |
1399 | | } |
1400 | | if (msPostGISRetrievePK(layer) != MS_SUCCESS) { |
1401 | | if (layerinfo->pgconn && |
1402 | | msPostGISRetrievePostgreSQLVersion(layerinfo->pgconn) < 120000) { |
1403 | | /* For PostgreSQL < 12: No user specified unique id so we will use the |
1404 | | * PostgreSQL oid */ |
1405 | | layerinfo->uid = "oid"; |
1406 | | } else { |
1407 | | msSetError(MS_QUERYERR, |
1408 | | "Error parsing PostGIS DATA variable. " |
1409 | | "No primary key was found. " |
1410 | | "You must specify 'using unique'.", |
1411 | | "msPostGISParseData()"); |
1412 | | return MS_FAILURE; |
1413 | | } |
1414 | | } |
1415 | | } |
1416 | | |
1417 | | if (layer->debug) { |
1418 | | msDebug("msPostGISParseData: unique_column=%s, srid=%s, " |
1419 | | "geom_column_name=%s, table_name=%s\n", |
1420 | | layerinfo->uid.c_str(), layerinfo->srid.c_str(), |
1421 | | layerinfo->geomcolumn.c_str(), layerinfo->fromsource.c_str()); |
1422 | | } |
1423 | | return MS_SUCCESS; |
1424 | | } |
1425 | | |
1426 | | #if TRANSFER_ENCODING == 16 |
1427 | | |
1428 | | // This is dead code given current settings in mappostgis.h |
1429 | | |
1430 | | /* |
1431 | | ** Decode a hex character. |
1432 | | */ |
1433 | | static const unsigned char msPostGISHexDecodeChar[256] = { |
1434 | | /* not Hex characters */ |
1435 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1436 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1437 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1438 | | /* 0-9 */ |
1439 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, |
1440 | | /* not Hex characters */ |
1441 | | 64, 64, 64, 64, 64, 64, 64, |
1442 | | /* A-F */ |
1443 | | 10, 11, 12, 13, 14, 15, |
1444 | | /* not Hex characters */ |
1445 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1446 | | 64, 64, 64, 64, 64, 64, 64, |
1447 | | /* a-f */ |
1448 | | 10, 11, 12, 13, 14, 15, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1449 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1450 | | /* not Hex characters (upper 128 characters) */ |
1451 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1452 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1453 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1454 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1455 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1456 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1457 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; |
1458 | | |
1459 | | /* |
1460 | | ** Decode hex string "src" (null terminated) |
1461 | | ** into "dest" (not null terminated). |
1462 | | ** Returns length of decoded array or 0 on failure. |
1463 | | */ |
1464 | | static int msPostGISHexDecode(unsigned char *dest, const char *src, |
1465 | | int srclen) { |
1466 | | |
1467 | | if (src && *src && (srclen % 2 == 0)) { |
1468 | | |
1469 | | unsigned char *p = dest; |
1470 | | int i; |
1471 | | |
1472 | | for (i = 0; i < srclen; i += 2) { |
1473 | | const unsigned char c1 = src[i]; |
1474 | | const unsigned char c2 = src[i + 1]; |
1475 | | const unsigned char b1 = msPostGISHexDecodeChar[c1]; |
1476 | | const unsigned char b2 = msPostGISHexDecodeChar[c2]; |
1477 | | |
1478 | | *p++ = (b1 << 4) | b2; |
1479 | | } |
1480 | | return (p - dest); |
1481 | | } |
1482 | | return 0; |
1483 | | } |
1484 | | |
1485 | | /* |
1486 | | ** Decode a base64 character. |
1487 | | */ |
1488 | | static const unsigned char msPostGISBase64DecodeChar[256] = { |
1489 | | /* not Base64 characters */ |
1490 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1491 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1492 | | 64, 64, 64, 64, 64, |
1493 | | /* + */ |
1494 | | 62, |
1495 | | /* not Base64 characters */ |
1496 | | 64, 64, 64, |
1497 | | /* / */ |
1498 | | 63, |
1499 | | /* 0-9 */ |
1500 | | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, |
1501 | | /* not Base64 characters */ |
1502 | | 64, 64, 64, 64, 64, 64, 64, |
1503 | | /* A-Z */ |
1504 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, |
1505 | | 21, 22, 23, 24, 25, |
1506 | | /* not Base64 characters */ |
1507 | | 64, 64, 64, 64, 64, 64, |
1508 | | /* a-z */ |
1509 | | 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, |
1510 | | 45, 46, 47, 48, 49, 50, 51, |
1511 | | /* not Base64 characters */ |
1512 | | 64, 64, 64, 64, 64, |
1513 | | /* not Base64 characters (upper 128 characters) */ |
1514 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1515 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1516 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1517 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1518 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1519 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
1520 | | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; |
1521 | | |
1522 | | /* |
1523 | | ** Decode base64 string "src" (null terminated) |
1524 | | ** into "dest" (not null terminated). |
1525 | | ** Returns length of decoded array or 0 on failure. |
1526 | | */ |
1527 | | static int msPostGISBase64Decode(unsigned char *dest, const char *src, |
1528 | | int srclen) { |
1529 | | |
1530 | | if (src && *src) { |
1531 | | |
1532 | | unsigned char *p = dest; |
1533 | | int i, j, k; |
1534 | | unsigned char *buf = |
1535 | | (unsigned char *)calloc(srclen + 1, sizeof(unsigned char)); |
1536 | | |
1537 | | /* Drop illegal chars first */ |
1538 | | for (i = 0, j = 0; src[i]; i++) { |
1539 | | unsigned char c = src[i]; |
1540 | | if ((msPostGISBase64DecodeChar[c] != 64) || (c == '=')) { |
1541 | | buf[j++] = c; |
1542 | | } |
1543 | | } |
1544 | | |
1545 | | for (k = 0; k < j; k += 4) { |
1546 | | unsigned char c1 = 'A', c2 = 'A', c3 = 'A', c4 = 'A'; |
1547 | | unsigned char b1 = 0, b2 = 0, b3 = 0, b4 = 0; |
1548 | | |
1549 | | c1 = buf[k]; |
1550 | | |
1551 | | if (k + 1 < j) { |
1552 | | c2 = buf[k + 1]; |
1553 | | } |
1554 | | if (k + 2 < j) { |
1555 | | c3 = buf[k + 2]; |
1556 | | } |
1557 | | if (k + 3 < j) { |
1558 | | c4 = buf[k + 3]; |
1559 | | } |
1560 | | |
1561 | | b1 = msPostGISBase64DecodeChar[c1]; |
1562 | | b2 = msPostGISBase64DecodeChar[c2]; |
1563 | | b3 = msPostGISBase64DecodeChar[c3]; |
1564 | | b4 = msPostGISBase64DecodeChar[c4]; |
1565 | | |
1566 | | *p++ = ((b1 << 2) | (b2 >> 4)); |
1567 | | if (c3 != '=') { |
1568 | | *p++ = (((b2 & 0xf) << 4) | (b3 >> 2)); |
1569 | | } |
1570 | | if (c4 != '=') { |
1571 | | *p++ = (((b3 & 0x3) << 6) | b4); |
1572 | | } |
1573 | | } |
1574 | | free(buf); |
1575 | | return (p - dest); |
1576 | | } |
1577 | | return 0; |
1578 | | } |
1579 | | |
1580 | | #endif |
1581 | | |
1582 | | /* |
1583 | | ** msPostGISBuildSQLBox() |
1584 | | ** |
1585 | | ** Returns malloc'ed char* that must be freed by caller. |
1586 | | */ |
1587 | | static char *msPostGISBuildSQLBox(layerObj *layer, const rectObj *rect, |
1588 | | const char *strSRID) { |
1589 | | |
1590 | | char *strBox = nullptr; |
1591 | | size_t sz; |
1592 | | |
1593 | | if (layer->debug) { |
1594 | | msDebug("msPostGISBuildSQLBox called.\n"); |
1595 | | } |
1596 | | |
1597 | | const bool bIsPoint = rect->minx == rect->maxx && rect->miny == rect->maxy; |
1598 | | |
1599 | | if (strSRID) { |
1600 | | static const char *strBoxTemplate = |
1601 | | "ST_GeomFromText('POLYGON((%.15g %.15g,%.15g %.15g,%.15g %.15g,%.15g " |
1602 | | "%.15g,%.15g %.15g))',%s)"; |
1603 | | static const char *strBoxTemplatePoint = |
1604 | | "ST_GeomFromText('POINT(%.15g %.15g)',%s)"; |
1605 | | /* 10 doubles + 1 integer + template characters */ |
1606 | | sz = 10 * 22 + strlen(strSRID) + strlen(strBoxTemplate); |
1607 | | strBox = (char *)msSmallMalloc(sz + 1); /* add space for terminating NULL */ |
1608 | | if ((bIsPoint && sz <= static_cast<size_t>( |
1609 | | snprintf(strBox, sz, strBoxTemplatePoint, |
1610 | | rect->minx, rect->miny, strSRID))) || |
1611 | | (!bIsPoint && |
1612 | | sz <= static_cast<size_t>(snprintf( |
1613 | | strBox, sz, strBoxTemplate, rect->minx, rect->miny, |
1614 | | rect->minx, rect->maxy, rect->maxx, rect->maxy, rect->maxx, |
1615 | | rect->miny, rect->minx, rect->miny, strSRID)))) { |
1616 | | msSetError(MS_MISCERR, "Bounding box digits truncated.", |
1617 | | "msPostGISBuildSQLBox"); |
1618 | | return nullptr; |
1619 | | } |
1620 | | } else { |
1621 | | static const char *strBoxTemplate = |
1622 | | "ST_GeomFromText('POLYGON((%.15g %.15g,%.15g %.15g,%.15g %.15g,%.15g " |
1623 | | "%.15g,%.15g %.15g))')"; |
1624 | | static const char *strBoxTemplatePoint = |
1625 | | "ST_GeomFromText('POINT(%.15g %.15g)')"; |
1626 | | /* 10 doubles + template characters */ |
1627 | | sz = 10 * 22 + strlen(strBoxTemplate); |
1628 | | strBox = (char *)msSmallMalloc(sz + 1); /* add space for terminating NULL */ |
1629 | | if ((bIsPoint && |
1630 | | sz <= static_cast<size_t>(snprintf(strBox, sz, strBoxTemplatePoint, |
1631 | | rect->minx, rect->miny))) || |
1632 | | (!bIsPoint && |
1633 | | sz <= static_cast<size_t>( |
1634 | | snprintf(strBox, sz, strBoxTemplate, rect->minx, rect->miny, |
1635 | | rect->minx, rect->maxy, rect->maxx, rect->maxy, |
1636 | | rect->maxx, rect->miny, rect->minx, rect->miny)))) { |
1637 | | msSetError(MS_MISCERR, "Bounding box digits truncated.", |
1638 | | "msPostGISBuildSQLBox"); |
1639 | | return nullptr; |
1640 | | } |
1641 | | } |
1642 | | |
1643 | | return strBox; |
1644 | | } |
1645 | | |
1646 | | /* |
1647 | | ** msPostGISBuildSQLItems() |
1648 | | ** |
1649 | | ** Returns malloc'ed char* that must be freed by caller. |
1650 | | */ |
1651 | | static std::string msPostGISBuildSQLItems(layerObj *layer) { |
1652 | | |
1653 | | const char *strEndian = nullptr; |
1654 | | if (layer->debug) { |
1655 | | msDebug("msPostGISBuildSQLItems called.\n"); |
1656 | | } |
1657 | | |
1658 | | assert(layer->layerinfo != nullptr); |
1659 | | |
1660 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
1661 | | |
1662 | | if (layerinfo->geomcolumn.empty()) { |
1663 | | msSetError(MS_MISCERR, "layerinfo->geomcolumn is not initialized.", |
1664 | | "msPostGISBuildSQLItems()"); |
1665 | | return std::string(); |
1666 | | } |
1667 | | |
1668 | | /* |
1669 | | ** Get the server to transform the geometry into our |
1670 | | ** native endian before transmitting it to us.. |
1671 | | */ |
1672 | | if (layerinfo->endian == LITTLE_ENDIAN) { |
1673 | | strEndian = "NDR"; |
1674 | | } else { |
1675 | | strEndian = "XDR"; |
1676 | | } |
1677 | | |
1678 | | std::string strGeom; |
1679 | | { |
1680 | | /* |
1681 | | ** We transfer the geometry from server to client as a |
1682 | | ** hex or base64 encoded WKB byte-array. We will have to decode this |
1683 | | ** data once we get it. Forcing to 2D (via the AsBinary function |
1684 | | ** which includes a 2D force in it) removes ordinates we don't |
1685 | | ** need, saving transfer and encode/decode time. |
1686 | | */ |
1687 | | const char *force2d = ""; |
1688 | | #if TRANSFER_ENCODING == 64 |
1689 | | const char *strGeomTemplate = |
1690 | | "encode(ST_AsBinary(%s(\"%s\"),'%s'),'base64') as geom,\"%s\""; |
1691 | | #elif TRANSFER_ENCODING == 256 |
1692 | | const char *strGeomTemplate = |
1693 | | "ST_AsBinary(%s(\"%s\"),'%s') as geom,\"%s\"::text"; |
1694 | | #else |
1695 | | const char *strGeomTemplate = |
1696 | | "encode(ST_AsBinary(%s(\"%s\"),'%s'),'hex') as geom,\"%s\""; |
1697 | | #endif |
1698 | | if (layerinfo->force2d) { |
1699 | | if (layerinfo->version >= 20100) |
1700 | | force2d = "ST_Force2D"; |
1701 | | else |
1702 | | force2d = "ST_Force_2D"; |
1703 | | } else if (layerinfo->version < 20000) { |
1704 | | /* Use AsEWKB() to get 3D */ |
1705 | | #if TRANSFER_ENCODING == 64 |
1706 | | strGeomTemplate = |
1707 | | "encode(AsEWKB(%s(\"%s\"),'%s'),'base64') as geom,\"%s\""; |
1708 | | #elif TRANSFER_ENCODING == 256 |
1709 | | strGeomTemplate = "AsEWKB(%s(\"%s\"),'%s') as geom,\"%s\"::text"; |
1710 | | #else |
1711 | | strGeomTemplate = "encode(AsEWKB(%s(\"%s\"),'%s'),'hex') as geom,\"%s\""; |
1712 | | #endif |
1713 | | } |
1714 | | strGeom.resize(strlen(strGeomTemplate) + strlen(force2d) + |
1715 | | strlen(strEndian) + layerinfo->geomcolumn.size() + |
1716 | | layerinfo->uid.size()); |
1717 | | snprintf(&strGeom[0], strGeom.size(), strGeomTemplate, force2d, |
1718 | | layerinfo->geomcolumn.c_str(), strEndian, layerinfo->uid.c_str()); |
1719 | | strGeom.resize(strlen(strGeom.data())); |
1720 | | } |
1721 | | |
1722 | | if (layer->debug > 1) { |
1723 | | msDebug("msPostGISBuildSQLItems: %d items requested.\n", layer->numitems); |
1724 | | } |
1725 | | |
1726 | | /* |
1727 | | ** Not requesting items? We just need geometry and unique id. |
1728 | | */ |
1729 | | std::string strItems; |
1730 | | /* |
1731 | | ** Build SQL to pull all the items. |
1732 | | */ |
1733 | | for (int t = 0; t < layer->numitems; t++) { |
1734 | | strItems += "\""; |
1735 | | strItems += layer->items[t]; |
1736 | | #if TRANSFER_ENCODING == 256 |
1737 | | strItems += "\"::text,"; |
1738 | | #else |
1739 | | strItems += "\","; |
1740 | | #endif |
1741 | | } |
1742 | | strItems += strGeom; |
1743 | | |
1744 | | return strItems; |
1745 | | } |
1746 | | |
1747 | | /* |
1748 | | ** msPostGISFindTableName() |
1749 | | */ |
1750 | | static std::string msPostGISFindTableName(const char *fromsource) { |
1751 | | std::string f_table_name; |
1752 | | const char *pos = strchr(fromsource, ' '); |
1753 | | |
1754 | | if (!pos) { |
1755 | | /* target table is one word */ |
1756 | | f_table_name = fromsource; |
1757 | | } else { |
1758 | | /* target table is hiding in sub-select clause */ |
1759 | | pos = strcasestr(fromsource, " from "); |
1760 | | if (pos) { |
1761 | | pos += 6; /* should be start of table name */ |
1762 | | const char *pos_paren = strstr(pos, ")"); /* first ) after table name */ |
1763 | | const char *pos_space = |
1764 | | strstr(pos, " "); /* first space after table name */ |
1765 | | if (pos_space < pos_paren) { |
1766 | | /* found space first */ |
1767 | | f_table_name.assign(pos, pos_space - pos); |
1768 | | } else { |
1769 | | /* found ) first */ |
1770 | | f_table_name.assign(pos, pos_paren - pos); |
1771 | | } |
1772 | | } |
1773 | | } |
1774 | | return f_table_name; |
1775 | | } |
1776 | | |
1777 | | /* |
1778 | | ** msPostGISBuildSQLSRID() |
1779 | | */ |
1780 | | static std::string msPostGISBuildSQLSRID(layerObj *layer) { |
1781 | | |
1782 | | std::string strSRID; |
1783 | | |
1784 | | if (layer->debug) { |
1785 | | msDebug("msPostGISBuildSQLSRID called.\n"); |
1786 | | } |
1787 | | |
1788 | | assert(layer->layerinfo != nullptr); |
1789 | | |
1790 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
1791 | | |
1792 | | /* An SRID was already provided in the DATA line. */ |
1793 | | if (!layerinfo->srid.empty()) { |
1794 | | strSRID = layerinfo->srid; |
1795 | | if (layer->debug > 1) { |
1796 | | msDebug("msPostGISBuildSQLSRID: SRID provided (%s)\n", strSRID.c_str()); |
1797 | | } |
1798 | | } |
1799 | | /* |
1800 | | ** No SRID in data line, so extract target table from the 'fromsource'. |
1801 | | ** Either of form "thetable" (one word) or "(select ... from thetable)" |
1802 | | ** or "(select ... from thetable where ...)". |
1803 | | */ |
1804 | | else { |
1805 | | if (layer->debug > 1) { |
1806 | | msDebug("msPostGISBuildSQLSRID: Building find_srid line.\n"); |
1807 | | } |
1808 | | |
1809 | | strSRID = "find_srid('','"; |
1810 | | strSRID += msPostGISFindTableName(layerinfo->fromsource.c_str()); |
1811 | | strSRID += "','"; |
1812 | | strSRID += layerinfo->geomcolumn; |
1813 | | strSRID += "')"; |
1814 | | } |
1815 | | return strSRID; |
1816 | | } |
1817 | | |
1818 | | /* |
1819 | | ** msPostGISReplaceBoxToken() |
1820 | | ** |
1821 | | ** Convert a fromsource data statement into something usable by replacing the |
1822 | | *!BOX! token. |
1823 | | */ |
1824 | | static std::string msPostGISReplaceBoxToken(layerObj *layer, |
1825 | | const rectObj *rect, |
1826 | | const char *fromsource) { |
1827 | | std::string result(fromsource); |
1828 | | |
1829 | | if (layer->debug > 1) { |
1830 | | msDebug("msPostGISReplaceBoxToken called.\n"); |
1831 | | } |
1832 | | |
1833 | | if (strstr(fromsource, BOXTOKEN) && rect) { |
1834 | | char *strBox = nullptr; |
1835 | | |
1836 | | /* We see to set the SRID on the box, but to what SRID? */ |
1837 | | const std::string strSRID = msPostGISBuildSQLSRID(layer); |
1838 | | if (strSRID.empty()) { |
1839 | | return std::string(); |
1840 | | } |
1841 | | |
1842 | | /* Create a suitable SQL string from the rectangle and SRID. */ |
1843 | | strBox = msPostGISBuildSQLBox(layer, rect, strSRID.c_str()); |
1844 | | if (!strBox) { |
1845 | | msSetError(MS_MISCERR, "Unable to build box SQL.", |
1846 | | "msPostGISReplaceBoxToken()"); |
1847 | | return std::string(); |
1848 | | } |
1849 | | |
1850 | | /* Do the substitution. */ |
1851 | | size_t pos = 0; |
1852 | | while (true) { |
1853 | | pos = result.find(BOXTOKEN, pos); |
1854 | | if (pos == std::string::npos) { |
1855 | | break; |
1856 | | } |
1857 | | const auto resultAfter(result.substr(pos + BOXTOKENLENGTH)); |
1858 | | result.resize(pos); |
1859 | | result += strBox; |
1860 | | result += resultAfter; |
1861 | | } |
1862 | | |
1863 | | free(strBox); |
1864 | | } |
1865 | | return result; |
1866 | | } |
1867 | | |
1868 | | /* |
1869 | | ** msPostGISBuildSQLFrom() |
1870 | | */ |
1871 | | static std::string msPostGISBuildSQLFrom(layerObj *layer, const rectObj *rect) { |
1872 | | if (layer->debug) { |
1873 | | msDebug("msPostGISBuildSQLFrom called.\n"); |
1874 | | } |
1875 | | |
1876 | | assert(layer->layerinfo != nullptr); |
1877 | | |
1878 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
1879 | | |
1880 | | if (layerinfo->fromsource.empty()) { |
1881 | | msSetError(MS_MISCERR, "Layerinfo->fromsource is not initialized.", |
1882 | | "msPostGISBuildSQLFrom()"); |
1883 | | return std::string(); |
1884 | | } |
1885 | | |
1886 | | /* |
1887 | | ** If there's a '!BOX!' in our source we need to substitute the |
1888 | | ** current rectangle for it... |
1889 | | */ |
1890 | | return msPostGISReplaceBoxToken(layer, rect, layerinfo->fromsource.c_str()); |
1891 | | } |
1892 | | |
1893 | | /* |
1894 | | ** msPostGISBuildSQLWhere() |
1895 | | */ |
1896 | | static std::string msPostGISBuildSQLWhere(layerObj *layer, const rectObj *rect, |
1897 | | const long *uid, |
1898 | | const rectObj *rectInOtherSRID, |
1899 | | int otherSRID) { |
1900 | | if (layer->debug) { |
1901 | | msDebug("msPostGISBuildSQLWhere called.\n"); |
1902 | | } |
1903 | | |
1904 | | assert(layer->layerinfo != nullptr); |
1905 | | |
1906 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
1907 | | |
1908 | | if (layerinfo->fromsource.empty()) { |
1909 | | msSetError(MS_MISCERR, "Layerinfo->fromsource is not initialized.", |
1910 | | "msPostGISBuildSQLWhere()"); |
1911 | | return std::string(); |
1912 | | } |
1913 | | |
1914 | | const rectObj rectInvalid = MS_INIT_INVALID_RECT; |
1915 | | bool bIsValidRect = |
1916 | | (rect && memcmp(rect, &rectInvalid, sizeof(rectInvalid)) != 0); |
1917 | | |
1918 | | /* Populate strRect, if necessary. */ |
1919 | | std::string strRect; |
1920 | | if (bIsValidRect) { |
1921 | | |
1922 | | if (rect && !layerinfo->geomcolumn.empty()) { |
1923 | | /* We see to set the SRID on the box, but to what SRID? */ |
1924 | | const std::string strSRID = msPostGISBuildSQLSRID(layer); |
1925 | | if (strSRID.empty()) { |
1926 | | return std::string(); |
1927 | | } |
1928 | | |
1929 | | char *strBox = msPostGISBuildSQLBox(layer, rect, strSRID.c_str()); |
1930 | | |
1931 | | if (!strBox) { |
1932 | | msSetError(MS_MISCERR, "Unable to build box SQL.", |
1933 | | "msPostGISBuildSQLWhere()"); |
1934 | | return std::string(); |
1935 | | } |
1936 | | |
1937 | | if (strSRID.find("find_srid(") == std::string::npos) { |
1938 | | // If the SRID is known, we can safely use ST_Intersects() |
1939 | | // otherwise if find_srid() would return 0, ST_Intersects() would not |
1940 | | // work at all, which breaks the msautotest/query/query_postgis.map |
1941 | | // tests, related to bdry_counpy2 layer that has no SRID |
1942 | | if (layerinfo->version >= 20500) { |
1943 | | strRect = "ST_Intersects(\""; |
1944 | | strRect += layerinfo->geomcolumn; |
1945 | | strRect += "\", "; |
1946 | | strRect += strBox; |
1947 | | strRect += ')'; |
1948 | | } else { |
1949 | | // ST_Intersects() before PostGIS 2.5 doesn't support collections |
1950 | | // See |
1951 | | // https://github.com/MapServer/MapServer/pull/6355#issuecomment-877355007 |
1952 | | strRect = "(\""; |
1953 | | strRect += layerinfo->geomcolumn; |
1954 | | strRect += "\" && "; |
1955 | | strRect += strBox; |
1956 | | strRect += ") AND ST_Distance(\""; |
1957 | | strRect += layerinfo->geomcolumn; |
1958 | | strRect += "\", "; |
1959 | | strRect += strBox; |
1960 | | strRect += ") = 0"; |
1961 | | } |
1962 | | } else { |
1963 | | strRect = '"'; |
1964 | | strRect += layerinfo->geomcolumn; |
1965 | | strRect += "\" && "; |
1966 | | strRect += strBox; |
1967 | | } |
1968 | | free(strBox); |
1969 | | |
1970 | | /* Combine with other rectangle expressed in another SRS */ |
1971 | | /* (generally equivalent to the above in current code paths) */ |
1972 | | if (rectInOtherSRID != nullptr && otherSRID > 0) { |
1973 | | strBox = msPostGISBuildSQLBox(layer, rectInOtherSRID, |
1974 | | std::to_string(otherSRID).c_str()); |
1975 | | if (!strBox) { |
1976 | | msSetError(MS_MISCERR, "Unable to build box SQL.", |
1977 | | "msPostGISBuildSQLWhere()"); |
1978 | | return std::string(); |
1979 | | } |
1980 | | |
1981 | | std::string strRectOtherSRID = "ST_Intersects(ST_Transform("; |
1982 | | strRectOtherSRID += layerinfo->geomcolumn; |
1983 | | strRectOtherSRID += ','; |
1984 | | strRectOtherSRID += std::to_string(otherSRID); |
1985 | | strRectOtherSRID += "),"; |
1986 | | strRectOtherSRID += strBox; |
1987 | | strRectOtherSRID += ')'; |
1988 | | |
1989 | | free(strBox); |
1990 | | |
1991 | | std::string strTmp = "(("; |
1992 | | strTmp += strRect; |
1993 | | strTmp += ") AND "; |
1994 | | strTmp += strRectOtherSRID; |
1995 | | strTmp += ')'; |
1996 | | |
1997 | | strRect = std::move(strTmp); |
1998 | | } else if (rectInOtherSRID != nullptr && otherSRID < 0) { |
1999 | | const std::string strSRID = msPostGISBuildSQLSRID(layer); |
2000 | | if (strSRID.empty()) { |
2001 | | return std::string(); |
2002 | | } |
2003 | | char *strBox = |
2004 | | msPostGISBuildSQLBox(layer, rectInOtherSRID, strSRID.c_str()); |
2005 | | |
2006 | | if (!strBox) { |
2007 | | msSetError(MS_MISCERR, "Unable to build box SQL.", |
2008 | | "msPostGISBuildSQLWhere()"); |
2009 | | return std::string(); |
2010 | | } |
2011 | | |
2012 | | std::string strRectOtherSRID = "ST_Intersects("; |
2013 | | strRectOtherSRID += layerinfo->geomcolumn; |
2014 | | strRectOtherSRID += ','; |
2015 | | strRectOtherSRID += strBox; |
2016 | | strRectOtherSRID += ')'; |
2017 | | |
2018 | | free(strBox); |
2019 | | |
2020 | | std::string strTmp = "(("; |
2021 | | strTmp += strRect; |
2022 | | strTmp += ") AND "; |
2023 | | strTmp += strRectOtherSRID; |
2024 | | strTmp += ')'; |
2025 | | |
2026 | | strRect = std::move(strTmp); |
2027 | | } |
2028 | | } |
2029 | | } |
2030 | | bool insert_and = false; |
2031 | | std::string strWhere; |
2032 | | if (bIsValidRect && !strRect.empty()) { |
2033 | | strWhere += strRect; |
2034 | | insert_and = true; |
2035 | | } |
2036 | | |
2037 | | /* Handle a translated filter (RFC91). */ |
2038 | | if (layer->filter.native_string) { |
2039 | | if (insert_and) { |
2040 | | strWhere += " AND "; |
2041 | | insert_and = true; |
2042 | | } |
2043 | | strWhere += '('; |
2044 | | strWhere += layer->filter.native_string; |
2045 | | strWhere += ')'; |
2046 | | } |
2047 | | |
2048 | | /* Handle a native filter set as a PROCESSING option (#5001). */ |
2049 | | const char *native_filter = msLayerGetProcessingKey(layer, "NATIVE_FILTER"); |
2050 | | if (native_filter) { |
2051 | | if (insert_and) { |
2052 | | strWhere += " AND "; |
2053 | | insert_and = true; |
2054 | | } |
2055 | | strWhere += '('; |
2056 | | strWhere += native_filter; |
2057 | | strWhere += ')'; |
2058 | | } |
2059 | | |
2060 | | if (uid) { |
2061 | | if (insert_and) { |
2062 | | strWhere += " AND "; |
2063 | | } |
2064 | | |
2065 | | bool is_numeric = msLayerPropertyIsNumeric(layer, layerinfo->uid.c_str()); |
2066 | | if (is_numeric) { |
2067 | | strWhere += layerinfo->uid; |
2068 | | strWhere += " = "; |
2069 | | strWhere += *uid; |
2070 | | } else { |
2071 | | strWhere += '"'; |
2072 | | strWhere += layerinfo->uid; |
2073 | | strWhere += "\" = "; |
2074 | | strWhere += std::to_string(*uid); |
2075 | | } |
2076 | | } |
2077 | | |
2078 | | if (strWhere.empty()) { |
2079 | | // return all records |
2080 | | strWhere = "true"; |
2081 | | } |
2082 | | |
2083 | | if (layer->sortBy.nProperties > 0) { |
2084 | | char *pszTmp = msLayerBuildSQLOrderBy(layer); |
2085 | | strWhere += " ORDER BY "; |
2086 | | strWhere += pszTmp; |
2087 | | msFree(pszTmp); |
2088 | | } |
2089 | | |
2090 | | if (layerinfo->paging && layer->maxfeatures >= 0) { |
2091 | | strWhere += " LIMIT "; |
2092 | | strWhere += std::to_string(layer->maxfeatures); |
2093 | | } |
2094 | | |
2095 | | /* Populate strOffset, if necessary. */ |
2096 | | if (layerinfo->paging && layer->startindex > 0) { |
2097 | | strWhere += " OFFSET "; |
2098 | | strWhere += std::to_string(layer->startindex - 1); |
2099 | | } |
2100 | | |
2101 | | return strWhere; |
2102 | | } |
2103 | | |
2104 | | /* |
2105 | | ** msPostGISBuildSQL() |
2106 | | ** |
2107 | | ** rect is the search rectangle in layer SRS. It can be set to NULL |
2108 | | ** uid can be set to NULL |
2109 | | ** rectInOtherSRID is an additional rectangle potentially in another SRS. It can |
2110 | | *be set to NULL. |
2111 | | ** Only used if rect != NULL |
2112 | | ** otherSRID is the SRID of the additional rectangle. It can be set to -1 if |
2113 | | ** rectInOtherSRID is in the SRID of the layer. |
2114 | | */ |
2115 | | static std::string msPostGISBuildSQL(layerObj *layer, const rectObj *rect, |
2116 | | const long *uid, |
2117 | | const rectObj *rectInOtherSRID, |
2118 | | int otherSRID) { |
2119 | | if (layer->debug) { |
2120 | | msDebug("msPostGISBuildSQL called.\n"); |
2121 | | } |
2122 | | |
2123 | | assert(layer->layerinfo != nullptr); |
2124 | | |
2125 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
2126 | | |
2127 | | const std::string strItems = msPostGISBuildSQLItems(layer); |
2128 | | if (strItems.empty()) { |
2129 | | msSetError(MS_MISCERR, "Failed to build SQL items.", "msPostGISBuildSQL()"); |
2130 | | return std::string(); |
2131 | | } |
2132 | | |
2133 | | const std::string strFrom = msPostGISBuildSQLFrom(layer, rect); |
2134 | | if (strFrom.empty()) { |
2135 | | msSetError(MS_MISCERR, "Failed to build SQL 'from'.", |
2136 | | "msPostGISBuildSQL()"); |
2137 | | return std::string(); |
2138 | | } |
2139 | | |
2140 | | /* If there's BOX hackery going on, we don't want to append a box index test |
2141 | | at the end of the query, the user is going to be responsible for making |
2142 | | things work with their hackery. */ |
2143 | | std::string strWhere; |
2144 | | if (strstr(layerinfo->fromsource.c_str(), BOXTOKEN)) |
2145 | | strWhere = |
2146 | | msPostGISBuildSQLWhere(layer, nullptr, uid, rectInOtherSRID, otherSRID); |
2147 | | else |
2148 | | strWhere = |
2149 | | msPostGISBuildSQLWhere(layer, rect, uid, rectInOtherSRID, otherSRID); |
2150 | | |
2151 | | if (strWhere.empty()) { |
2152 | | msSetError(MS_MISCERR, "Failed to build SQL 'where'.", |
2153 | | "msPostGISBuildSQL()"); |
2154 | | return std::string(); |
2155 | | } |
2156 | | |
2157 | | std::string sql("SELECT "); |
2158 | | sql += strItems; |
2159 | | sql += " FROM "; |
2160 | | sql += strFrom; |
2161 | | sql += " WHERE "; |
2162 | | sql += strWhere; |
2163 | | |
2164 | | return sql; |
2165 | | } |
2166 | | |
2167 | | #define wkbstaticsize 4096 |
2168 | | static int msPostGISReadShape(layerObj *layer, shapeObj *shape) { |
2169 | | if (layer->debug) { |
2170 | | msDebug("msPostGISReadShape called.\n"); |
2171 | | } |
2172 | | |
2173 | | assert(layer->layerinfo != nullptr); |
2174 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
2175 | | |
2176 | | /* Retrieve the geometry. */ |
2177 | | const char *wkbstr = |
2178 | | PQgetvalue(layerinfo->pgresult, layerinfo->rownum, layer->numitems); |
2179 | | const int wkbstrlen = |
2180 | | PQgetlength(layerinfo->pgresult, layerinfo->rownum, layer->numitems); |
2181 | | |
2182 | | if (!wkbstr || wkbstrlen == 0) { |
2183 | | msSetError(MS_QUERYERR, "WKB returned is null!", "msPostGISReadShape()"); |
2184 | | return MS_FAILURE; |
2185 | | } |
2186 | | |
2187 | | unsigned char wkbstatic[wkbstaticsize]; |
2188 | | unsigned char *wkb = nullptr; |
2189 | | if (wkbstrlen > wkbstaticsize) { |
2190 | | wkb = |
2191 | | static_cast<unsigned char *>(calloc(wkbstrlen, sizeof(unsigned char))); |
2192 | | } else { |
2193 | | wkb = wkbstatic; |
2194 | | } |
2195 | | |
2196 | | wkbObj w; |
2197 | | int result = 0; |
2198 | | #if TRANSFER_ENCODING == 64 |
2199 | | result = msPostGISBase64Decode(wkb, wkbstr, wkbstrlen - 1); |
2200 | | w.size = (wkbstrlen - 1) / 2; |
2201 | | if (!result) { |
2202 | | if (wkb != wkbstatic) |
2203 | | free(wkb); |
2204 | | return MS_FAILURE; |
2205 | | } |
2206 | | #elif TRANSFER_ENCODING == 256 |
2207 | | memcpy(wkb, wkbstr, wkbstrlen); |
2208 | | w.size = wkbstrlen; |
2209 | | #else |
2210 | | result = msPostGISHexDecode(wkb, wkbstr, wkbstrlen); |
2211 | | w.size = (wkbstrlen - 1) / 2; |
2212 | | if (!result) { |
2213 | | if (wkb != wkbstatic) |
2214 | | free(wkb); |
2215 | | return MS_FAILURE; |
2216 | | } |
2217 | | #endif |
2218 | | |
2219 | | /* Initialize our wkbObj */ |
2220 | | w.wkb = (char *)wkb; |
2221 | | w.ptr = w.wkb; |
2222 | | |
2223 | | /* Set the type map according to what version of PostGIS we are dealing with |
2224 | | */ |
2225 | | if (layerinfo->version >= 20000) /* PostGIS 2.0+ */ |
2226 | | w.typemap = wkb_postgis20; |
2227 | | else { |
2228 | | w.typemap = wkb_postgis15; |
2229 | | if (layerinfo->force2d == MS_FALSE) { |
2230 | | /* Is there SRID ? Skip it */ |
2231 | | if (w.size >= 9 && (w.ptr[4] & 0x20) != 0) { |
2232 | | w.ptr[5] = w.ptr[1]; |
2233 | | w.ptr[6] = w.ptr[2]; |
2234 | | w.ptr[7] = w.ptr[3]; |
2235 | | w.ptr[8] = w.ptr[4] & ~(0x20); |
2236 | | w.ptr[4] = 1; |
2237 | | w.ptr += 4; |
2238 | | w.size -= 4; |
2239 | | } |
2240 | | } |
2241 | | } |
2242 | | |
2243 | | switch (layer->type) { |
2244 | | |
2245 | | case MS_LAYER_POINT: |
2246 | | shape->type = MS_SHAPE_POINT; |
2247 | | result = wkbConvGeometryToShape(&w, shape); |
2248 | | break; |
2249 | | |
2250 | | case MS_LAYER_LINE: |
2251 | | shape->type = MS_SHAPE_LINE; |
2252 | | result = wkbConvGeometryToShape(&w, shape); |
2253 | | break; |
2254 | | |
2255 | | case MS_LAYER_POLYGON: |
2256 | | shape->type = MS_SHAPE_POLYGON; |
2257 | | result = wkbConvGeometryToShape(&w, shape); |
2258 | | break; |
2259 | | |
2260 | | case MS_LAYER_QUERY: |
2261 | | case MS_LAYER_CHART: |
2262 | | result = msPostGISFindBestType(&w, shape); |
2263 | | break; |
2264 | | |
2265 | | case MS_LAYER_RASTER: |
2266 | | msDebug("Ignoring MS_LAYER_RASTER in msPostGISReadShape.\n"); |
2267 | | break; |
2268 | | |
2269 | | case MS_LAYER_CIRCLE: |
2270 | | msDebug("Ignoring MS_LAYER_RASTER in msPostGISReadShape.\n"); |
2271 | | break; |
2272 | | |
2273 | | default: |
2274 | | msDebug("Unsupported layer type in msPostGISReadShape()!\n"); |
2275 | | break; |
2276 | | } |
2277 | | |
2278 | | /* All done with WKB geometry, free it! */ |
2279 | | if (wkb != wkbstatic) |
2280 | | free(wkb); |
2281 | | |
2282 | | if (result != MS_FAILURE) { |
2283 | | /* Found a drawable shape, so now retrieve the attributes. */ |
2284 | | |
2285 | | shape->values = (char **)msSmallMalloc(sizeof(char *) * layer->numitems); |
2286 | | for (int t = 0; t < layer->numitems; t++) { |
2287 | | const int size = PQgetlength(layerinfo->pgresult, layerinfo->rownum, t); |
2288 | | const char *val = PQgetvalue(layerinfo->pgresult, layerinfo->rownum, t); |
2289 | | const int isnull = PQgetisnull(layerinfo->pgresult, layerinfo->rownum, t); |
2290 | | if (isnull) { |
2291 | | shape->values[t] = msStrdup(""); |
2292 | | } else { |
2293 | | shape->values[t] = (char *)msSmallMalloc(size + 1); |
2294 | | memcpy(shape->values[t], val, size); |
2295 | | shape->values[t][size] = '\0'; /* null terminate it */ |
2296 | | |
2297 | | // From https://www.postgresql.org/docs/9.0/datatype-character.html |
2298 | | // fields of type Char are blank padded, but this blank is semantically |
2299 | | // insignificant, so let's trim it |
2300 | | if (PQftype(layerinfo->pgresult, t) == CHAROID) |
2301 | | msStringTrimBlanks(shape->values[t]); |
2302 | | } |
2303 | | if (layer->debug > 4) { |
2304 | | msDebug("msPostGISReadShape: PQgetlength = %d\n", size); |
2305 | | } |
2306 | | if (layer->debug > 1) { |
2307 | | msDebug("msPostGISReadShape: [%s] \"%s\"\n", layer->items[t], |
2308 | | shape->values[t]); |
2309 | | } |
2310 | | } |
2311 | | |
2312 | | /* layer->numitems is the geometry, layer->numitems+1 is the uid */ |
2313 | | const char *tmp = |
2314 | | PQgetvalue(layerinfo->pgresult, layerinfo->rownum, layer->numitems + 1); |
2315 | | long uid = 0; |
2316 | | if (tmp) { |
2317 | | uid = strtol(tmp, nullptr, 10); |
2318 | | } |
2319 | | if (layer->debug > 4) { |
2320 | | msDebug("msPostGISReadShape: Setting shape->index = %ld\n", uid); |
2321 | | msDebug("msPostGISReadShape: Setting shape->resultindex = %ld\n", |
2322 | | layerinfo->rownum); |
2323 | | } |
2324 | | shape->index = uid; |
2325 | | shape->resultindex = layerinfo->rownum; |
2326 | | |
2327 | | if (layer->debug > 2) { |
2328 | | msDebug("msPostGISReadShape: [index] %ld\n", shape->index); |
2329 | | } |
2330 | | |
2331 | | shape->numvalues = layer->numitems; |
2332 | | |
2333 | | msComputeBounds(shape); |
2334 | | } else { |
2335 | | shape->type = MS_SHAPE_NULL; |
2336 | | } |
2337 | | |
2338 | | if (layer->debug > 2) { |
2339 | | char *tmp = msShapeToWKT(shape); |
2340 | | msDebug("msPostGISReadShape: [shape] %s\n", tmp); |
2341 | | free(tmp); |
2342 | | } |
2343 | | |
2344 | | return MS_SUCCESS; |
2345 | | } |
2346 | | |
2347 | | #endif /* USE_POSTGIS */ |
2348 | | |
2349 | | /* |
2350 | | ** msPostGISLayerOpen() |
2351 | | ** |
2352 | | ** Registered vtable->LayerOpen function. |
2353 | | */ |
2354 | 0 | static int msPostGISLayerOpen(layerObj *layer) { |
2355 | | #ifdef USE_POSTGIS |
2356 | | assert(layer != nullptr); |
2357 | | |
2358 | | if (layer->debug) { |
2359 | | msDebug("msPostGISLayerOpen called: %s\n", layer->data); |
2360 | | } |
2361 | | |
2362 | | if (layer->layerinfo) { |
2363 | | if (layer->debug) { |
2364 | | msDebug("msPostGISLayerOpen: Layer is already open!\n"); |
2365 | | } |
2366 | | return MS_SUCCESS; /* already open */ |
2367 | | } |
2368 | | |
2369 | | if (!layer->data) { |
2370 | | msSetError(MS_QUERYERR, "Nothing specified in DATA statement.", |
2371 | | "msPostGISLayerOpen()"); |
2372 | | return MS_FAILURE; |
2373 | | } |
2374 | | |
2375 | | /* |
2376 | | ** Initialize the layerinfo |
2377 | | **/ |
2378 | | msPostGISLayerInfo *layerinfo = msPostGISCreateLayerInfo(); |
2379 | | |
2380 | | int order_test = 1; |
2381 | | if (((char *)&order_test)[0] == 1) { |
2382 | | layerinfo->endian = LITTLE_ENDIAN; |
2383 | | } else { |
2384 | | layerinfo->endian = BIG_ENDIAN; |
2385 | | } |
2386 | | |
2387 | | /* |
2388 | | ** Get a database connection from the pool. |
2389 | | */ |
2390 | | layerinfo->pgconn = (PGconn *)msConnPoolRequest(layer); |
2391 | | |
2392 | | /* No connection in the pool, so set one up. */ |
2393 | | if (!layerinfo->pgconn) { |
2394 | | if (layer->debug) { |
2395 | | msDebug( |
2396 | | "msPostGISLayerOpen: No connection in pool, creating a fresh one.\n"); |
2397 | | } |
2398 | | |
2399 | | if (!layer->connection) { |
2400 | | msSetError(MS_MISCERR, "Missing CONNECTION keyword.", |
2401 | | "msPostGISLayerOpen()"); |
2402 | | delete layerinfo; |
2403 | | return MS_FAILURE; |
2404 | | } |
2405 | | |
2406 | | /* |
2407 | | ** Decrypt any encrypted token in connection string and attempt to connect. |
2408 | | */ |
2409 | | char *conn_decrypted = msDecryptStringTokens(layer->map, layer->connection); |
2410 | | if (conn_decrypted == nullptr) { |
2411 | | delete layerinfo; |
2412 | | return MS_FAILURE; /* An error should already have been produced */ |
2413 | | } |
2414 | | layerinfo->pgconn = PQconnectdb(conn_decrypted); |
2415 | | msFree(conn_decrypted); |
2416 | | conn_decrypted = nullptr; |
2417 | | |
2418 | | /* |
2419 | | ** Connection failed, return error message with passwords ***ed out. |
2420 | | */ |
2421 | | if (!layerinfo->pgconn || PQstatus(layerinfo->pgconn) == CONNECTION_BAD) { |
2422 | | if (layer->debug) |
2423 | | msDebug("msPostGISLayerOpen: Connection failure.\n"); |
2424 | | |
2425 | | msDebug("Database connection failed (%s) with connect string '%s'\nIs " |
2426 | | "the database running? Is it allowing connections? Does the " |
2427 | | "specified user exist? Is the password valid? Is the database on " |
2428 | | "the standard port? in msPostGISLayerOpen()", |
2429 | | PQerrorMessage(layerinfo->pgconn), layer->connection); |
2430 | | msSetError(MS_QUERYERR, |
2431 | | "Database connection failed. Check server logs for more " |
2432 | | "details.Is the database running? Is it allowing connections? " |
2433 | | "Does the specified user exist? Is the password valid? Is the " |
2434 | | "database on the standard port?", |
2435 | | "msPostGISLayerOpen()"); |
2436 | | |
2437 | | if (layerinfo->pgconn) |
2438 | | PQfinish(layerinfo->pgconn); |
2439 | | delete layerinfo; |
2440 | | return MS_FAILURE; |
2441 | | } |
2442 | | |
2443 | | /* Register to receive notifications from the database. */ |
2444 | | PQsetNoticeProcessor(layerinfo->pgconn, postresqlNoticeHandler, |
2445 | | (void *)layer); |
2446 | | |
2447 | | /* Save this connection in the pool for later. */ |
2448 | | msConnPoolRegister(layer, layerinfo->pgconn, msPostGISCloseConnection); |
2449 | | } else { |
2450 | | /* Connection in the pool should be tested to see if backend is alive. */ |
2451 | | if (PQstatus(layerinfo->pgconn) != CONNECTION_OK) { |
2452 | | /* Uh oh, bad connection. Can we reset it? */ |
2453 | | PQreset(layerinfo->pgconn); |
2454 | | if (PQstatus(layerinfo->pgconn) != CONNECTION_OK) { |
2455 | | /* Nope, time to bail out. */ |
2456 | | msSetError(MS_QUERYERR, |
2457 | | "PostgreSQL database connection. Check server logs for more " |
2458 | | "details", |
2459 | | "msPostGISLayerOpen()"); |
2460 | | msDebug("PostgreSQL database connection gone bad (%s) in " |
2461 | | "msPostGISLayerOpen()", |
2462 | | PQerrorMessage(layerinfo->pgconn)); |
2463 | | delete layerinfo; |
2464 | | /* FIXME: we should also release the connection from the pool in this |
2465 | | * case, but it is stale... for the time being we do not release it so |
2466 | | * it can never be used again. If this happens multiple times there will |
2467 | | * be a leak... */ |
2468 | | return MS_FAILURE; |
2469 | | } |
2470 | | } |
2471 | | } |
2472 | | |
2473 | | /* Get the PostGIS version number from the database */ |
2474 | | layerinfo->version = msPostGISRetrieveVersion(layerinfo->pgconn); |
2475 | | if (layerinfo->version == MS_FAILURE) { |
2476 | | msConnPoolRelease(layer, layerinfo->pgconn); |
2477 | | delete layerinfo; |
2478 | | return MS_FAILURE; |
2479 | | } |
2480 | | if (layer->debug) |
2481 | | msDebug("msPostGISLayerOpen: Got PostGIS version %d.\n", |
2482 | | layerinfo->version); |
2483 | | |
2484 | | const char *force2d_processing = msLayerGetProcessingKey(layer, "FORCE2D"); |
2485 | | if (force2d_processing && !strcasecmp(force2d_processing, "no")) { |
2486 | | layerinfo->force2d = MS_FALSE; |
2487 | | } else if (force2d_processing && !strcasecmp(force2d_processing, "yes")) { |
2488 | | layerinfo->force2d = MS_TRUE; |
2489 | | } |
2490 | | if (layer->debug) |
2491 | | msDebug("msPostGISLayerOpen: Forcing 2D geometries: %s.\n", |
2492 | | (layerinfo->force2d) ? "yes" : "no"); |
2493 | | |
2494 | | /* Save the layerinfo in the layerObj. */ |
2495 | | layer->layerinfo = (void *)layerinfo; |
2496 | | |
2497 | | return MS_SUCCESS; |
2498 | | #else |
2499 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
2500 | 0 | "msPostGISLayerOpen()"); |
2501 | 0 | return MS_FAILURE; |
2502 | 0 | #endif |
2503 | 0 | } |
2504 | | |
2505 | | /* |
2506 | | ** msPostGISLayerClose() |
2507 | | ** |
2508 | | ** Registered vtable->LayerClose function. |
2509 | | */ |
2510 | 0 | static int msPostGISLayerClose(layerObj *layer) { |
2511 | | #ifdef USE_POSTGIS |
2512 | | |
2513 | | if (layer->debug) { |
2514 | | msDebug("msPostGISLayerClose called: %s\n", layer->data); |
2515 | | } |
2516 | | |
2517 | | if (layer->layerinfo) { |
2518 | | msPostGISFreeLayerInfo(layer); |
2519 | | } |
2520 | | |
2521 | | return MS_SUCCESS; |
2522 | | #else |
2523 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
2524 | 0 | "msPostGISLayerClose()"); |
2525 | 0 | return MS_FAILURE; |
2526 | 0 | #endif |
2527 | 0 | } |
2528 | | |
2529 | | /* |
2530 | | ** msPostGISLayerIsOpen() |
2531 | | ** |
2532 | | ** Registered vtable->LayerIsOpen function. |
2533 | | */ |
2534 | 0 | static int msPostGISLayerIsOpen(layerObj *layer) { |
2535 | | #ifdef USE_POSTGIS |
2536 | | |
2537 | | if (layer->debug) { |
2538 | | msDebug("msPostGISLayerIsOpen called.\n"); |
2539 | | } |
2540 | | |
2541 | | if (layer->layerinfo) |
2542 | | return MS_TRUE; |
2543 | | else |
2544 | | return MS_FALSE; |
2545 | | #else |
2546 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
2547 | 0 | "msPostGISLayerIsOpen()"); |
2548 | 0 | return MS_FAILURE; |
2549 | 0 | #endif |
2550 | 0 | } |
2551 | | |
2552 | | /* |
2553 | | ** msPostGISLayerFreeItemInfo() |
2554 | | ** |
2555 | | ** Registered vtable->LayerFreeItemInfo function. |
2556 | | */ |
2557 | 0 | static void msPostGISLayerFreeItemInfo(layerObj *layer) { |
2558 | | #ifdef USE_POSTGIS |
2559 | | if (layer->debug) { |
2560 | | msDebug("msPostGISLayerFreeItemInfo called.\n"); |
2561 | | } |
2562 | | |
2563 | | if (layer->iteminfo) { |
2564 | | free(layer->iteminfo); |
2565 | | } |
2566 | | layer->iteminfo = nullptr; |
2567 | | #endif |
2568 | 0 | } |
2569 | | |
2570 | | /* |
2571 | | ** msPostGISLayerInitItemInfo() |
2572 | | ** |
2573 | | ** Registered vtable->LayerInitItemInfo function. |
2574 | | ** Our iteminfo is list of indexes from 1..numitems. |
2575 | | */ |
2576 | 0 | static int msPostGISLayerInitItemInfo(layerObj *layer) { |
2577 | | #ifdef USE_POSTGIS |
2578 | | if (layer->debug) { |
2579 | | msDebug("msPostGISLayerInitItemInfo called.\n"); |
2580 | | } |
2581 | | |
2582 | | if (layer->numitems == 0) { |
2583 | | return MS_SUCCESS; |
2584 | | } |
2585 | | |
2586 | | if (layer->iteminfo) { |
2587 | | free(layer->iteminfo); |
2588 | | } |
2589 | | |
2590 | | layer->iteminfo = msSmallMalloc(sizeof(int) * layer->numitems); |
2591 | | if (!layer->iteminfo) { |
2592 | | msSetError(MS_MEMERR, "Out of memory.", "msPostGISLayerInitItemInfo()"); |
2593 | | return MS_FAILURE; |
2594 | | } |
2595 | | |
2596 | | int *itemindexes = (int *)layer->iteminfo; |
2597 | | for (int i = 0; i < layer->numitems; i++) { |
2598 | | itemindexes[i] = |
2599 | | i; /* Last item is always the geometry. The rest are non-geometry. */ |
2600 | | } |
2601 | | |
2602 | | return MS_SUCCESS; |
2603 | | #else |
2604 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
2605 | 0 | "msPostGISLayerInitItemInfo()"); |
2606 | 0 | return MS_FAILURE; |
2607 | 0 | #endif |
2608 | 0 | } |
2609 | | |
2610 | | #ifdef USE_POSTGIS |
2611 | | static std::vector<const char *> buildBindValues(layerObj *layer) { |
2612 | | /* try to get the first bind value */ |
2613 | | const char *bind_value = msLookupHashTable(&layer->bindvals, "1"); |
2614 | | std::vector<const char *> layer_bind_values; |
2615 | | while (bind_value != nullptr) { |
2616 | | /* put the bind value on the stack */ |
2617 | | layer_bind_values.push_back(bind_value); |
2618 | | /* get the bind_value */ |
2619 | | bind_value = msLookupHashTable( |
2620 | | &layer->bindvals, |
2621 | | std::to_string(static_cast<size_t>(layer_bind_values.size()) + 1) |
2622 | | .c_str()); |
2623 | | } |
2624 | | return layer_bind_values; |
2625 | | } |
2626 | | |
2627 | | static PGresult *runPQexecParamsWithBindSubstitution(layerObj *layer, |
2628 | | const char *strSQL, |
2629 | | int binary) { |
2630 | | PGresult *pgresult = nullptr; |
2631 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
2632 | | |
2633 | | const auto layer_bind_values = buildBindValues(layer); |
2634 | | |
2635 | | if (!layer_bind_values.empty()) { |
2636 | | pgresult = PQexecParams(layerinfo->pgconn, strSQL, |
2637 | | static_cast<int>(layer_bind_values.size()), nullptr, |
2638 | | layer_bind_values.data(), nullptr, nullptr, binary); |
2639 | | } else { |
2640 | | pgresult = PQexecParams(layerinfo->pgconn, strSQL, 0, nullptr, nullptr, |
2641 | | nullptr, nullptr, binary); |
2642 | | } |
2643 | | |
2644 | | return pgresult; |
2645 | | } |
2646 | | #endif |
2647 | | |
2648 | | /* |
2649 | | ** msPostGISLayerWhichShapes() |
2650 | | ** |
2651 | | ** Registered vtable->LayerWhichShapes function. |
2652 | | */ |
2653 | | // cppcheck-suppress passedByValue |
2654 | | static int msPostGISLayerWhichShapes(layerObj *layer, rectObj rect, |
2655 | 0 | int isQuery) { |
2656 | 0 | (void)isQuery; |
2657 | | #ifdef USE_POSTGIS |
2658 | | assert(layer != nullptr); |
2659 | | assert(layer->layerinfo != nullptr); |
2660 | | |
2661 | | if (layer->debug) { |
2662 | | msDebug("msPostGISLayerWhichShapes called.\n"); |
2663 | | } |
2664 | | |
2665 | | /* Fill out layerinfo with our current DATA state. */ |
2666 | | if (msPostGISParseData(layer) != MS_SUCCESS) { |
2667 | | return MS_FAILURE; |
2668 | | } |
2669 | | |
2670 | | /* |
2671 | | ** This comes *after* parsedata, because parsedata fills in |
2672 | | ** layer->layerinfo. |
2673 | | */ |
2674 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
2675 | | |
2676 | | /* Build a SQL query based on our current state. */ |
2677 | | const std::string strSQL = |
2678 | | msPostGISBuildSQL(layer, &rect, nullptr, nullptr, -1); |
2679 | | if (strSQL.empty()) { |
2680 | | msSetError(MS_QUERYERR, "Failed to build query SQL.", |
2681 | | "msPostGISLayerWhichShapes()"); |
2682 | | return MS_FAILURE; |
2683 | | } |
2684 | | |
2685 | | if (layer->debug) { |
2686 | | msDebug("msPostGISLayerWhichShapes query: %s\n", strSQL.c_str()); |
2687 | | } |
2688 | | |
2689 | | PGresult *pgresult = runPQexecParamsWithBindSubstitution( |
2690 | | layer, strSQL.c_str(), RESULTSET_TYPE); |
2691 | | |
2692 | | if (layer->debug > 1) { |
2693 | | msDebug("msPostGISLayerWhichShapes query status: %s (%d)\n", |
2694 | | PQresStatus(PQresultStatus(pgresult)), PQresultStatus(pgresult)); |
2695 | | } |
2696 | | |
2697 | | /* Something went wrong. */ |
2698 | | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) { |
2699 | | msDebug("msPostGISLayerWhichShapes(): Error (%s) executing query: %s\n", |
2700 | | PQerrorMessage(layerinfo->pgconn), strSQL.c_str()); |
2701 | | msSetError(MS_QUERYERR, "Error executing query. Check server logs", |
2702 | | "msPostGISLayerWhichShapes()"); |
2703 | | if (pgresult) { |
2704 | | PQclear(pgresult); |
2705 | | } |
2706 | | return MS_FAILURE; |
2707 | | } |
2708 | | |
2709 | | if (layer->debug) { |
2710 | | msDebug("msPostGISLayerWhichShapes got %d records in result.\n", |
2711 | | PQntuples(pgresult)); |
2712 | | } |
2713 | | |
2714 | | /* Clean any existing pgresult before storing current one. */ |
2715 | | if (layerinfo->pgresult) |
2716 | | PQclear(layerinfo->pgresult); |
2717 | | layerinfo->pgresult = pgresult; |
2718 | | |
2719 | | layerinfo->sql = strSQL; |
2720 | | |
2721 | | layerinfo->rownum = 0; |
2722 | | |
2723 | | return MS_SUCCESS; |
2724 | | #else |
2725 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
2726 | 0 | "msPostGISLayerWhichShapes()"); |
2727 | 0 | return MS_FAILURE; |
2728 | 0 | #endif |
2729 | 0 | } |
2730 | | |
2731 | | /* |
2732 | | ** msPostGISLayerNextShape() |
2733 | | ** |
2734 | | ** Registered vtable->LayerNextShape function. |
2735 | | */ |
2736 | 0 | static int msPostGISLayerNextShape(layerObj *layer, shapeObj *shape) { |
2737 | | #ifdef USE_POSTGIS |
2738 | | if (layer->debug) { |
2739 | | msDebug("msPostGISLayerNextShape called.\n"); |
2740 | | } |
2741 | | |
2742 | | assert(layer != nullptr); |
2743 | | assert(layer->layerinfo != nullptr); |
2744 | | |
2745 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
2746 | | |
2747 | | shape->type = MS_SHAPE_NULL; |
2748 | | |
2749 | | /* |
2750 | | ** Roll through pgresult until we hit non-null shape (usually right away). |
2751 | | */ |
2752 | | while (shape->type == MS_SHAPE_NULL) { |
2753 | | if (layerinfo->rownum < PQntuples(layerinfo->pgresult)) { |
2754 | | /* Retrieve this shape, cursor access mode. */ |
2755 | | msPostGISReadShape(layer, shape); |
2756 | | if (shape->type != MS_SHAPE_NULL) { |
2757 | | (layerinfo->rownum)++; /* move to next shape */ |
2758 | | return MS_SUCCESS; |
2759 | | } else { |
2760 | | (layerinfo->rownum)++; /* move to next shape */ |
2761 | | } |
2762 | | } else { |
2763 | | return MS_DONE; |
2764 | | } |
2765 | | } |
2766 | | |
2767 | | /* Found nothing, clean up and exit. */ |
2768 | | msFreeShape(shape); |
2769 | | |
2770 | | return MS_FAILURE; |
2771 | | #else |
2772 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
2773 | 0 | "msPostGISLayerNextShape()"); |
2774 | 0 | return MS_FAILURE; |
2775 | 0 | #endif |
2776 | 0 | } |
2777 | | |
2778 | | /* |
2779 | | ** msPostGISLayerGetShape() |
2780 | | ** |
2781 | | */ |
2782 | | // cppcheck-suppress passedByValue |
2783 | | static int msPostGISLayerGetShapeCount(layerObj *layer, rectObj rect, |
2784 | 0 | projectionObj *rectProjection) { |
2785 | | #ifdef USE_POSTGIS |
2786 | | int rectSRID = -1; |
2787 | | rectObj searchrectInLayerProj = rect; |
2788 | | const rectObj rectInvalid = MS_INIT_INVALID_RECT; |
2789 | | bool bIsValidRect = memcmp(&rect, &rectInvalid, sizeof(rect)) != 0; |
2790 | | |
2791 | | assert(layer != nullptr); |
2792 | | assert(layer->layerinfo != nullptr); |
2793 | | |
2794 | | if (layer->debug) { |
2795 | | msDebug("msPostGISLayerGetShapeCount called.\n"); |
2796 | | } |
2797 | | |
2798 | | // Special processing if the specified projection for the rect is different |
2799 | | // from the layer projection We want to issue a WHERE that includes |
2800 | | // ((the_geom && rect_reprojected_in_layer_SRID) AND NOT |
2801 | | // ST_Disjoint(ST_Transform(the_geom, rect_SRID), rect)) |
2802 | | if (bIsValidRect && |
2803 | | (rectProjection != NULL && layer->project && |
2804 | | msProjectionsDiffer(&(layer->projection), rectProjection))) { |
2805 | | // If we cannot guess the EPSG code of the rectProjection, we cannot |
2806 | | // use ST_Transform, so fallback on slow implementation |
2807 | | if (rectProjection->numargs < 1 || |
2808 | | strncasecmp(rectProjection->args[0], |
2809 | | "init=epsg:", strlen("init=epsg:")) != 0) { |
2810 | | if (layer->debug) { |
2811 | | msDebug("msPostGISLayerGetShapeCount(): cannot find EPSG code of " |
2812 | | "rectProjection. Falling back on client-side feature count.\n"); |
2813 | | } |
2814 | | return LayerDefaultGetShapeCount(layer, rect, rectProjection); |
2815 | | } |
2816 | | |
2817 | | // Reproject the passed rect into the layer projection and get |
2818 | | // the SRID from the rectProjection |
2819 | | msProjectRect( |
2820 | | rectProjection, &(layer->projection), |
2821 | | &searchrectInLayerProj); /* project the searchrect to source coords */ |
2822 | | rectSRID = atoi(rectProjection->args[0] + strlen("init=epsg:")); |
2823 | | } |
2824 | | |
2825 | | msLayerTranslateFilter(layer, &layer->filter, layer->filteritem); |
2826 | | |
2827 | | /* Fill out layerinfo with our current DATA state. */ |
2828 | | if (msPostGISParseData(layer) != MS_SUCCESS) { |
2829 | | return -1; |
2830 | | } |
2831 | | |
2832 | | /* |
2833 | | ** This comes *after* parsedata, because parsedata fills in |
2834 | | ** layer->layerinfo. |
2835 | | */ |
2836 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
2837 | | |
2838 | | /* Build a SQL query based on our current state. */ |
2839 | | const std::string strSQL = msPostGISBuildSQL(layer, &searchrectInLayerProj, |
2840 | | nullptr, &rect, rectSRID); |
2841 | | if (strSQL.empty()) { |
2842 | | msSetError(MS_QUERYERR, "Failed to build query SQL.", |
2843 | | "msPostGISLayerGetShapeCount()"); |
2844 | | return -1; |
2845 | | } |
2846 | | |
2847 | | std::string strSQLCount = "SELECT COUNT(*) FROM ("; |
2848 | | strSQLCount += strSQL; |
2849 | | strSQLCount += ") msQuery"; |
2850 | | |
2851 | | if (layer->debug) { |
2852 | | msDebug("msPostGISLayerGetShapeCount query: %s\n", strSQLCount.c_str()); |
2853 | | } |
2854 | | |
2855 | | PGresult *pgresult = |
2856 | | runPQexecParamsWithBindSubstitution(layer, strSQLCount.c_str(), 0); |
2857 | | |
2858 | | if (layer->debug > 1) { |
2859 | | msDebug("msPostGISLayerWhichShapes query status: %s (%d)\n", |
2860 | | PQresStatus(PQresultStatus(pgresult)), PQresultStatus(pgresult)); |
2861 | | } |
2862 | | |
2863 | | /* Something went wrong. */ |
2864 | | if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) { |
2865 | | msDebug("msPostGISLayerGetShapeCount(): Error (%s) executing query: %s. " |
2866 | | "Falling back to client-side evaluation\n", |
2867 | | PQerrorMessage(layerinfo->pgconn), strSQLCount.c_str()); |
2868 | | if (pgresult) { |
2869 | | PQclear(pgresult); |
2870 | | } |
2871 | | return LayerDefaultGetShapeCount(layer, rect, rectProjection); |
2872 | | } |
2873 | | |
2874 | | const int nCount = atoi(PQgetvalue(pgresult, 0, 0)); |
2875 | | |
2876 | | if (layer->debug) { |
2877 | | msDebug("msPostGISLayerWhichShapes return: %d.\n", nCount); |
2878 | | } |
2879 | | PQclear(pgresult); |
2880 | | |
2881 | | return nCount; |
2882 | | #else |
2883 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
2884 | 0 | "msPostGISLayerGetShapeCount()"); |
2885 | 0 | return -1; |
2886 | 0 | #endif |
2887 | 0 | } |
2888 | | |
2889 | | /* |
2890 | | ** msPostGISLayerGetShape() |
2891 | | ** |
2892 | | ** Registered vtable->LayerGetShape function. For pulling from a prepared and |
2893 | | ** undisposed result set. |
2894 | | */ |
2895 | | static int msPostGISLayerGetShape(layerObj *layer, shapeObj *shape, |
2896 | 0 | resultObj *record) { |
2897 | | #ifdef USE_POSTGIS |
2898 | | |
2899 | | long shapeindex = record->shapeindex; |
2900 | | int resultindex = record->resultindex; |
2901 | | |
2902 | | assert(layer != nullptr); |
2903 | | assert(layer->layerinfo != nullptr); |
2904 | | |
2905 | | if (layer->debug) { |
2906 | | msDebug("msPostGISLayerGetShape called for record = %i\n", resultindex); |
2907 | | } |
2908 | | |
2909 | | /* If resultindex is set, fetch the shape from the resultcache, otherwise |
2910 | | * fetch it from the DB */ |
2911 | | if (resultindex >= 0) { |
2912 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
2913 | | |
2914 | | /* Check the validity of the open result. */ |
2915 | | PGresult *pgresult = layerinfo->pgresult; |
2916 | | if (!pgresult) { |
2917 | | msSetError(MS_MISCERR, "PostgreSQL result set is null.", |
2918 | | "msPostGISLayerGetShape()"); |
2919 | | return MS_FAILURE; |
2920 | | } |
2921 | | ExecStatusType status = PQresultStatus(pgresult); |
2922 | | if (layer->debug > 1) { |
2923 | | msDebug("msPostGISLayerGetShape query status: %s (%d)\n", |
2924 | | PQresStatus(status), (int)status); |
2925 | | } |
2926 | | if (!(status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK)) { |
2927 | | msSetError(MS_MISCERR, "PostgreSQL result set is not ready.", |
2928 | | "msPostGISLayerGetShape()"); |
2929 | | return MS_FAILURE; |
2930 | | } |
2931 | | |
2932 | | /* Check the validity of the requested record number. */ |
2933 | | if (resultindex >= PQntuples(pgresult)) { |
2934 | | msDebug("msPostGISLayerGetShape got request for (%d) but only has %d " |
2935 | | "tuples.\n", |
2936 | | resultindex, PQntuples(pgresult)); |
2937 | | msSetError(MS_MISCERR, "Got request larger than result set.", |
2938 | | "msPostGISLayerGetShape()"); |
2939 | | return MS_FAILURE; |
2940 | | } |
2941 | | |
2942 | | layerinfo->rownum = resultindex; /* Only return one result. */ |
2943 | | |
2944 | | /* We don't know the shape type until we read the geometry. */ |
2945 | | shape->type = MS_SHAPE_NULL; |
2946 | | |
2947 | | /* Return the shape, cursor access mode. */ |
2948 | | msPostGISReadShape(layer, shape); |
2949 | | |
2950 | | return (shape->type == MS_SHAPE_NULL) ? MS_FAILURE : MS_SUCCESS; |
2951 | | } else { /* no resultindex, fetch the shape from the DB */ |
2952 | | int num_tuples; |
2953 | | |
2954 | | /* Fill out layerinfo with our current DATA state. */ |
2955 | | if (msPostGISParseData(layer) != MS_SUCCESS) { |
2956 | | return MS_FAILURE; |
2957 | | } |
2958 | | |
2959 | | /* |
2960 | | ** This comes *after* parsedata, because parsedata fills in |
2961 | | ** layer->layerinfo. |
2962 | | */ |
2963 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
2964 | | |
2965 | | /* Build a SQL query based on our current state. */ |
2966 | | const std::string strSQL = |
2967 | | msPostGISBuildSQL(layer, nullptr, &shapeindex, nullptr, -1); |
2968 | | if (strSQL.empty()) { |
2969 | | msSetError(MS_QUERYERR, "Failed to build query SQL.", |
2970 | | "msPostGISLayerGetShape()"); |
2971 | | return MS_FAILURE; |
2972 | | } |
2973 | | |
2974 | | if (layer->debug) { |
2975 | | msDebug("msPostGISLayerGetShape query: %s\n", strSQL.c_str()); |
2976 | | } |
2977 | | |
2978 | | PGresult *pgresult = runPQexecParamsWithBindSubstitution( |
2979 | | layer, strSQL.c_str(), RESULTSET_TYPE); |
2980 | | |
2981 | | /* Something went wrong. */ |
2982 | | if ((!pgresult) || (PQresultStatus(pgresult) != PGRES_TUPLES_OK)) { |
2983 | | msDebug("msPostGISLayerGetShape(): Error (%s) executing SQL: %s\n", |
2984 | | PQerrorMessage(layerinfo->pgconn), strSQL.c_str()); |
2985 | | msSetError(MS_QUERYERR, "Error executing SQL. Check server logs.", |
2986 | | "msPostGISLayerGetShape()"); |
2987 | | |
2988 | | if (pgresult) { |
2989 | | PQclear(pgresult); |
2990 | | } |
2991 | | return MS_FAILURE; |
2992 | | } |
2993 | | |
2994 | | /* Clean any existing pgresult before storing current one. */ |
2995 | | if (layerinfo->pgresult) |
2996 | | PQclear(layerinfo->pgresult); |
2997 | | layerinfo->pgresult = pgresult; |
2998 | | |
2999 | | /* Clean any existing SQL before storing current. */ |
3000 | | layerinfo->sql = strSQL; |
3001 | | |
3002 | | layerinfo->rownum = 0; /* Only return one result. */ |
3003 | | |
3004 | | /* We don't know the shape type until we read the geometry. */ |
3005 | | shape->type = MS_SHAPE_NULL; |
3006 | | |
3007 | | num_tuples = PQntuples(pgresult); |
3008 | | if (layer->debug) { |
3009 | | msDebug("msPostGISLayerGetShape number of records: %d\n", num_tuples); |
3010 | | } |
3011 | | |
3012 | | if (num_tuples > 0) { |
3013 | | /* Get shape in random access mode. */ |
3014 | | msPostGISReadShape(layer, shape); |
3015 | | } |
3016 | | |
3017 | | return (shape->type == MS_SHAPE_NULL) |
3018 | | ? MS_FAILURE |
3019 | | : ((num_tuples > 0) ? MS_SUCCESS : MS_DONE); |
3020 | | } |
3021 | | #else |
3022 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
3023 | 0 | "msPostGISLayerGetShape()"); |
3024 | 0 | return MS_FAILURE; |
3025 | 0 | #endif |
3026 | 0 | } |
3027 | | |
3028 | | /********************************************************************** |
3029 | | * msPostGISPassThroughFieldDefinitions() |
3030 | | * |
3031 | | * Pass the field definitions through to the layer metadata in the |
3032 | | * "gml_[item]_{type,width,precision}" set of metadata items for |
3033 | | * defining fields. |
3034 | | **********************************************************************/ |
3035 | | |
3036 | | #ifdef USE_POSTGIS |
3037 | | static void msPostGISPassThroughFieldDefinitions(layerObj *layer, |
3038 | | PGresult *pgresult) |
3039 | | |
3040 | | { |
3041 | | const int numitems = PQnfields(pgresult); |
3042 | | msPostGISLayerInfo *layerinfo = |
3043 | | static_cast<msPostGISLayerInfo *>(layer->layerinfo); |
3044 | | |
3045 | | for (int i = 0; i < numitems; i++) { |
3046 | | const char *gml_type = "Character"; |
3047 | | const char *item = PQfname(pgresult, i); |
3048 | | std::string gml_width; |
3049 | | std::string gml_precision; |
3050 | | |
3051 | | /* skip geometry column */ |
3052 | | if (item == layerinfo->geomcolumn) |
3053 | | continue; |
3054 | | |
3055 | | const int oid = PQftype(pgresult, i); |
3056 | | const int fmod = PQfmod(pgresult, i); |
3057 | | |
3058 | | if ((oid == BPCHAROID || oid == VARCHAROID) && fmod >= 4) { |
3059 | | gml_width = std::to_string(fmod - 4); |
3060 | | |
3061 | | } else if (oid == BOOLOID) { |
3062 | | gml_type = "Boolean"; |
3063 | | |
3064 | | } else if (oid == INT2OID) { |
3065 | | gml_type = "Integer"; |
3066 | | gml_width = '5'; |
3067 | | |
3068 | | } else if (oid == INT4OID) { |
3069 | | gml_type = "Integer"; |
3070 | | |
3071 | | } else if (oid == INT8OID) { |
3072 | | gml_type = "Long"; |
3073 | | |
3074 | | } else if (oid == FLOAT4OID || oid == FLOAT8OID) { |
3075 | | gml_type = "Real"; |
3076 | | |
3077 | | } else if (oid == NUMERICOID) { |
3078 | | gml_type = "Real"; |
3079 | | |
3080 | | if (fmod >= 4 && ((fmod - 4) & 0xFFFF) == 0) { |
3081 | | gml_type = "Integer"; |
3082 | | gml_width = std::to_string((fmod - 4) >> 16); |
3083 | | } else if (fmod >= 4) { |
3084 | | gml_width = std::to_string((fmod - 4) >> 16); |
3085 | | gml_precision = std::to_string((fmod - 4) & 0xFFFF); |
3086 | | } |
3087 | | } else if (oid == DATEOID) { |
3088 | | gml_type = "Date"; |
3089 | | } else if (oid == TIMEOID || oid == TIMETZOID) { |
3090 | | gml_type = "Time"; |
3091 | | } else if (oid == TIMESTAMPOID || oid == TIMESTAMPTZOID) { |
3092 | | gml_type = "DateTime"; |
3093 | | } |
3094 | | |
3095 | | msUpdateGMLFieldMetadata(layer, item, gml_type, gml_width.c_str(), |
3096 | | gml_precision.c_str(), 0); |
3097 | | } |
3098 | | } |
3099 | | #endif /* defined(USE_POSTGIS) */ |
3100 | | |
3101 | | /* |
3102 | | ** msPostGISLayerGetItems() |
3103 | | ** |
3104 | | ** Registered vtable->LayerGetItems function. Query the database for |
3105 | | ** column information about the requested layer. Rather than look in |
3106 | | ** system tables, we just run a zero-cost query and read out of the |
3107 | | ** result header. |
3108 | | */ |
3109 | 0 | static int msPostGISLayerGetItems(layerObj *layer) { |
3110 | | #ifdef USE_POSTGIS |
3111 | | rectObj rect; |
3112 | | |
3113 | | /* A useless rectangle for our useless query */ |
3114 | | rect.minx = rect.miny = rect.maxx = rect.maxy = 0.0; |
3115 | | |
3116 | | assert(layer != nullptr); |
3117 | | assert(layer->layerinfo != nullptr); |
3118 | | |
3119 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
3120 | | |
3121 | | assert(layerinfo->pgconn); |
3122 | | |
3123 | | if (layer->debug) { |
3124 | | msDebug("msPostGISLayerGetItems called.\n"); |
3125 | | } |
3126 | | |
3127 | | /* Fill out layerinfo with our current DATA state. */ |
3128 | | if (msPostGISParseData(layer) != MS_SUCCESS) { |
3129 | | return MS_FAILURE; |
3130 | | } |
3131 | | |
3132 | | layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
3133 | | |
3134 | | /* |
3135 | | ** Both the "table" and "(select ...) as sub" cases can be handled with the |
3136 | | ** same SQL. |
3137 | | */ |
3138 | | std::string sql("select * from "); |
3139 | | sql += msPostGISReplaceBoxToken(layer, &rect, layerinfo->fromsource.c_str()); |
3140 | | sql += " where false limit 0"; |
3141 | | |
3142 | | if (layer->debug) { |
3143 | | msDebug("msPostGISLayerGetItems executing SQL: %s\n", sql.c_str()); |
3144 | | } |
3145 | | |
3146 | | PGresult *pgresult = |
3147 | | runPQexecParamsWithBindSubstitution(layer, sql.c_str(), 0); |
3148 | | |
3149 | | if ((!pgresult) || (PQresultStatus(pgresult) != PGRES_TUPLES_OK)) { |
3150 | | msDebug("msPostGISLayerGetItems(): Error (%s) executing SQL: %s\n", |
3151 | | PQerrorMessage(layerinfo->pgconn), sql.c_str()); |
3152 | | msSetError(MS_QUERYERR, "Error executing SQL. Check server logs", |
3153 | | "msPostGISLayerGetItems()"); |
3154 | | if (pgresult) { |
3155 | | PQclear(pgresult); |
3156 | | } |
3157 | | return MS_FAILURE; |
3158 | | } |
3159 | | |
3160 | | layer->numitems = PQnfields(pgresult) - |
3161 | | 1; /* don't include the geometry column (last entry)*/ |
3162 | | layer->items = static_cast<char **>(msSmallMalloc( |
3163 | | sizeof(char *) * |
3164 | | (layer->numitems + |
3165 | | 1))); /* +1 in case there is a problem finding geometry column */ |
3166 | | |
3167 | | bool found_geom = false; /* haven't found the geom field */ |
3168 | | int item_num = 0; |
3169 | | |
3170 | | for (int t = 0; t < PQnfields(pgresult); t++) { |
3171 | | const char *col = PQfname(pgresult, t); |
3172 | | if (col != layerinfo->geomcolumn) { |
3173 | | /* this isn't the geometry column */ |
3174 | | layer->items[item_num] = msStrdup(col); |
3175 | | item_num++; |
3176 | | } else { |
3177 | | found_geom = true; |
3178 | | } |
3179 | | } |
3180 | | |
3181 | | /* |
3182 | | ** consider populating the field definitions in metadata. |
3183 | | */ |
3184 | | const char *value = msOWSLookupMetadata(&(layer->metadata), "G", "types"); |
3185 | | if (value != nullptr && strcasecmp(value, "auto") == 0) |
3186 | | msPostGISPassThroughFieldDefinitions(layer, pgresult); |
3187 | | |
3188 | | /* |
3189 | | ** Cleanup |
3190 | | */ |
3191 | | PQclear(pgresult); |
3192 | | |
3193 | | if (!found_geom) { |
3194 | | msSetError(MS_QUERYERR, |
3195 | | "Tried to find the geometry column in the database, but " |
3196 | | "couldn't find it. Is it mis-capitalized? '%s'", |
3197 | | "msPostGISLayerGetItems()", layerinfo->geomcolumn.c_str()); |
3198 | | return MS_FAILURE; |
3199 | | } |
3200 | | |
3201 | | return msPostGISLayerInitItemInfo(layer); |
3202 | | #else |
3203 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
3204 | 0 | "msPostGISLayerGetItems()"); |
3205 | 0 | return MS_FAILURE; |
3206 | 0 | #endif |
3207 | 0 | } |
3208 | | |
3209 | | #ifdef USE_POSTGIS |
3210 | | static std::string |
3211 | | addTableNameAndFilterToSelectFrom(layerObj *layer, |
3212 | | const std::string &selectFrom) { |
3213 | | auto layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
3214 | | /* if we have !BOX! substitution then we use just the table name */ |
3215 | | std::string f_table_name; |
3216 | | if (strstr(layerinfo->fromsource.c_str(), BOXTOKEN)) |
3217 | | f_table_name = msPostGISFindTableName(layerinfo->fromsource.c_str()); |
3218 | | else |
3219 | | f_table_name = layerinfo->fromsource; |
3220 | | |
3221 | | std::string strSQL = selectFrom; |
3222 | | strSQL += f_table_name; |
3223 | | |
3224 | | /* Handle a translated filter (RFC91). */ |
3225 | | if (layer->filter.native_string) { |
3226 | | strSQL += " WHERE ("; |
3227 | | strSQL += layer->filter.native_string; |
3228 | | strSQL += ')'; |
3229 | | } |
3230 | | |
3231 | | /* Handle a native filter set as a PROCESSING option (#5001). */ |
3232 | | const char *native_filter = msLayerGetProcessingKey(layer, "NATIVE_FILTER"); |
3233 | | if (native_filter) { |
3234 | | if (layer->filter.native_string) |
3235 | | strSQL += " AND ("; |
3236 | | else |
3237 | | strSQL += " WHERE ("; |
3238 | | strSQL += native_filter; |
3239 | | strSQL += ')'; |
3240 | | } |
3241 | | return strSQL; |
3242 | | } |
3243 | | #endif |
3244 | | |
3245 | | /* |
3246 | | ** msPostGISLayerGetExtent() |
3247 | | ** |
3248 | | ** Registered vtable->LayerGetExtent function. Query the database for |
3249 | | ** the extent of the requested layer. |
3250 | | */ |
3251 | 0 | static int msPostGISLayerGetExtent(layerObj *layer, rectObj *extent) { |
3252 | | #ifdef USE_POSTGIS |
3253 | | if (layer->debug) { |
3254 | | msDebug("msPostGISLayerGetExtent called.\n"); |
3255 | | } |
3256 | | |
3257 | | assert(layer->layerinfo != nullptr); |
3258 | | |
3259 | | if (msPostGISParseData(layer) != MS_SUCCESS) { |
3260 | | return MS_FAILURE; |
3261 | | } |
3262 | | |
3263 | | auto layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
3264 | | const std::string strSQL(addTableNameAndFilterToSelectFrom( |
3265 | | layer, "SELECT ST_Extent(" + layerinfo->geomcolumn + ") FROM ")); |
3266 | | |
3267 | | if (layer->debug) { |
3268 | | msDebug("msPostGISLayerGetExtent executing SQL: %s\n", strSQL.c_str()); |
3269 | | } |
3270 | | |
3271 | | /* executing the query */ |
3272 | | PGresult *pgresult = |
3273 | | runPQexecParamsWithBindSubstitution(layer, strSQL.c_str(), 0); |
3274 | | |
3275 | | if ((!pgresult) || (PQresultStatus(pgresult) != PGRES_TUPLES_OK)) { |
3276 | | msDebug("Error executing SQL: (%s) in msPostGISLayerGetExtent()", |
3277 | | PQerrorMessage(layerinfo->pgconn)); |
3278 | | msSetError(MS_MISCERR, "Error executing SQL. Check server logs.", |
3279 | | "msPostGISLayerGetExtent()"); |
3280 | | if (pgresult) |
3281 | | PQclear(pgresult); |
3282 | | |
3283 | | return MS_FAILURE; |
3284 | | } |
3285 | | |
3286 | | /* process results */ |
3287 | | if (PQntuples(pgresult) < 1) { |
3288 | | msSetError(MS_MISCERR, "msPostGISLayerGetExtent: No results found.", |
3289 | | "msPostGISLayerGetExtent()"); |
3290 | | PQclear(pgresult); |
3291 | | return MS_FAILURE; |
3292 | | } |
3293 | | |
3294 | | if (PQgetisnull(pgresult, 0, 0)) { |
3295 | | msSetError(MS_MISCERR, "msPostGISLayerGetExtent: Null result returned.", |
3296 | | "msPostGISLayerGetExtent()"); |
3297 | | PQclear(pgresult); |
3298 | | return MS_FAILURE; |
3299 | | } |
3300 | | |
3301 | | if (sscanf(PQgetvalue(pgresult, 0, 0), "BOX(%lf %lf,%lf %lf)", &extent->minx, |
3302 | | &extent->miny, &extent->maxx, &extent->maxy) != 4) { |
3303 | | msSetError(MS_MISCERR, "Failed to process result data.", |
3304 | | "msPostGISLayerGetExtent()"); |
3305 | | PQclear(pgresult); |
3306 | | return MS_FAILURE; |
3307 | | } |
3308 | | |
3309 | | /* cleanup */ |
3310 | | PQclear(pgresult); |
3311 | | |
3312 | | return MS_SUCCESS; |
3313 | | #else |
3314 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
3315 | 0 | "msPostGISLayerGetExtent()"); |
3316 | 0 | return MS_FAILURE; |
3317 | 0 | #endif |
3318 | 0 | } |
3319 | | |
3320 | | /* |
3321 | | ** msPostGISLayerGetNumFeatures() |
3322 | | ** |
3323 | | ** Registered vtable->LayerGetNumFeatures function. Query the database for |
3324 | | ** the feature count of the requested layer. |
3325 | | */ |
3326 | 0 | static int msPostGISLayerGetNumFeatures(layerObj *layer) { |
3327 | | #ifdef USE_POSTGIS |
3328 | | if (layer->debug) { |
3329 | | msDebug("msPostGISLayerGetNumFeatures called.\n"); |
3330 | | } |
3331 | | |
3332 | | assert(layer->layerinfo != nullptr); |
3333 | | |
3334 | | if (msPostGISParseData(layer) != MS_SUCCESS) { |
3335 | | return -1; |
3336 | | } |
3337 | | |
3338 | | auto layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
3339 | | const std::string strSQL( |
3340 | | addTableNameAndFilterToSelectFrom(layer, "SELECT count(*) FROM ")); |
3341 | | if (layer->debug) { |
3342 | | msDebug("msPostGISLayerGetNumFeatures executing SQL: %s\n", strSQL.c_str()); |
3343 | | } |
3344 | | |
3345 | | /* executing the query */ |
3346 | | PGresult *pgresult = |
3347 | | runPQexecParamsWithBindSubstitution(layer, strSQL.c_str(), 0); |
3348 | | |
3349 | | if ((!pgresult) || (PQresultStatus(pgresult) != PGRES_TUPLES_OK)) { |
3350 | | msDebug("Error executing SQL: (%s) in msPostGISLayerGetNumFeatures()", |
3351 | | PQerrorMessage(layerinfo->pgconn)); |
3352 | | msSetError(MS_MISCERR, "Error executing SQL. Check server logs.", |
3353 | | "msPostGISLayerGetNumFeatures()"); |
3354 | | if (pgresult) |
3355 | | PQclear(pgresult); |
3356 | | |
3357 | | return -1; |
3358 | | } |
3359 | | |
3360 | | /* process results */ |
3361 | | if (PQntuples(pgresult) < 1) { |
3362 | | msSetError(MS_MISCERR, "msPostGISLayerGetNumFeatures: No results found.", |
3363 | | "msPostGISLayerGetNumFeatures()"); |
3364 | | PQclear(pgresult); |
3365 | | return -1; |
3366 | | } |
3367 | | |
3368 | | if (PQgetisnull(pgresult, 0, 0)) { |
3369 | | msSetError(MS_MISCERR, |
3370 | | "msPostGISLayerGetNumFeatures: Null result returned.", |
3371 | | "msPostGISLayerGetNumFeatures()"); |
3372 | | PQclear(pgresult); |
3373 | | return -1; |
3374 | | } |
3375 | | |
3376 | | const char *tmp = PQgetvalue(pgresult, 0, 0); |
3377 | | int result = 0; |
3378 | | if (tmp) { |
3379 | | result = strtol(tmp, nullptr, 10); |
3380 | | } |
3381 | | |
3382 | | /* cleanup */ |
3383 | | PQclear(pgresult); |
3384 | | |
3385 | | return result; |
3386 | | #else |
3387 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
3388 | 0 | "msPostGISLayerGetNumFeatures()"); |
3389 | 0 | return -1; |
3390 | 0 | #endif |
3391 | 0 | } |
3392 | | |
3393 | | #ifdef USE_POSTGIS |
3394 | | /* |
3395 | | * make sure that the timestring is complete and acceptable |
3396 | | * to the date_trunc function : |
3397 | | * - if the resolution is year (2004) or month (2004-01), |
3398 | | * a complete string for time would be 2004-01-01 |
3399 | | * - if the resolluion is hour or minute (2004-01-01 15), a |
3400 | | * complete time is 2004-01-01 15:00:00 |
3401 | | */ |
3402 | | static int postgresTimeStampForTimeString(const char *timestring, char *dest, |
3403 | | size_t destsize) { |
3404 | | int nlength = strlen(timestring); |
3405 | | int timeresolution = msTimeGetResolution(timestring); |
3406 | | int bNoDate = (*timestring == 'T'); |
3407 | | if (timeresolution < 0) |
3408 | | return MS_FALSE; |
3409 | | |
3410 | | switch (timeresolution) { |
3411 | | case TIME_RESOLUTION_YEAR: |
3412 | | if (timestring[nlength - 1] != '-') { |
3413 | | snprintf(dest, destsize, "date '%s-01-01'", timestring); |
3414 | | } else { |
3415 | | snprintf(dest, destsize, "date '%s01-01'", timestring); |
3416 | | } |
3417 | | break; |
3418 | | case TIME_RESOLUTION_MONTH: |
3419 | | if (timestring[nlength - 1] != '-') { |
3420 | | snprintf(dest, destsize, "date '%s-01'", timestring); |
3421 | | } else { |
3422 | | snprintf(dest, destsize, "date '%s01'", timestring); |
3423 | | } |
3424 | | break; |
3425 | | case TIME_RESOLUTION_DAY: |
3426 | | snprintf(dest, destsize, "date '%s'", timestring); |
3427 | | break; |
3428 | | case TIME_RESOLUTION_HOUR: |
3429 | | if (timestring[nlength - 1] != ':') { |
3430 | | if (bNoDate) |
3431 | | snprintf(dest, destsize, "time '%s:00:00'", timestring); |
3432 | | else |
3433 | | snprintf(dest, destsize, "timestamp '%s:00:00'", timestring); |
3434 | | } else { |
3435 | | if (bNoDate) |
3436 | | snprintf(dest, destsize, "time '%s00:00'", timestring); |
3437 | | else |
3438 | | snprintf(dest, destsize, "timestamp '%s00:00'", timestring); |
3439 | | } |
3440 | | break; |
3441 | | case TIME_RESOLUTION_MINUTE: |
3442 | | if (timestring[nlength - 1] != ':') { |
3443 | | if (bNoDate) |
3444 | | snprintf(dest, destsize, "time '%s:00'", timestring); |
3445 | | else |
3446 | | snprintf(dest, destsize, "timestamp '%s:00'", timestring); |
3447 | | } else { |
3448 | | if (bNoDate) |
3449 | | snprintf(dest, destsize, "time '%s00'", timestring); |
3450 | | else |
3451 | | snprintf(dest, destsize, "timestamp '%s00'", timestring); |
3452 | | } |
3453 | | break; |
3454 | | case TIME_RESOLUTION_SECOND: |
3455 | | if (bNoDate) |
3456 | | snprintf(dest, destsize, "time '%s'", timestring); |
3457 | | else |
3458 | | snprintf(dest, destsize, "timestamp '%s'", timestring); |
3459 | | break; |
3460 | | default: |
3461 | | return MS_FAILURE; |
3462 | | } |
3463 | | return MS_SUCCESS; |
3464 | | } |
3465 | | |
3466 | | /* |
3467 | | * create a postgresql where clause for the given timestring, taking into |
3468 | | * account the resolution (e.g. second, day, month...) of the given timestring |
3469 | | * we apply the date_trunc function on the given timestring and not on the time |
3470 | | * column in order for postgres to take advantage of an eventual index on the |
3471 | | * time column |
3472 | | * |
3473 | | * the generated sql is |
3474 | | * |
3475 | | * ( |
3476 | | * timecol >= date_trunc(timestring,resolution) |
3477 | | * and |
3478 | | * timecol < date_trunc(timestring,resolution) + interval '1 resolution' |
3479 | | * ) |
3480 | | */ |
3481 | | static int createPostgresTimeCompareEquals(const char *timestring, char *dest, |
3482 | | size_t destsize) { |
3483 | | int timeresolution = msTimeGetResolution(timestring); |
3484 | | char timeStamp[100]; |
3485 | | const char *interval; |
3486 | | if (timeresolution < 0) |
3487 | | return MS_FALSE; |
3488 | | |
3489 | | postgresTimeStampForTimeString(timestring, timeStamp, 100); |
3490 | | |
3491 | | switch (timeresolution) { |
3492 | | case TIME_RESOLUTION_YEAR: |
3493 | | interval = "year"; |
3494 | | break; |
3495 | | case TIME_RESOLUTION_MONTH: |
3496 | | interval = "month"; |
3497 | | break; |
3498 | | case TIME_RESOLUTION_DAY: |
3499 | | interval = "day"; |
3500 | | break; |
3501 | | case TIME_RESOLUTION_HOUR: |
3502 | | interval = "hour"; |
3503 | | break; |
3504 | | case TIME_RESOLUTION_MINUTE: |
3505 | | interval = "minute"; |
3506 | | break; |
3507 | | case TIME_RESOLUTION_SECOND: |
3508 | | interval = "second"; |
3509 | | break; |
3510 | | default: |
3511 | | return MS_FAILURE; |
3512 | | } |
3513 | | snprintf(dest, destsize, |
3514 | | " between date_trunc('%s',%s) and date_trunc('%s',%s) + interval '1 " |
3515 | | "%s' - interval '1 second'", |
3516 | | interval, timeStamp, interval, timeStamp, interval); |
3517 | | return MS_SUCCESS; |
3518 | | } |
3519 | | |
3520 | | static int createPostgresTimeCompareGreaterThan(const char *timestring, |
3521 | | char *dest, size_t destsize) { |
3522 | | int timeresolution = msTimeGetResolution(timestring); |
3523 | | char timestamp[100]; |
3524 | | const char *interval; |
3525 | | if (timeresolution < 0) |
3526 | | return MS_FALSE; |
3527 | | |
3528 | | postgresTimeStampForTimeString(timestring, timestamp, 100); |
3529 | | |
3530 | | switch (timeresolution) { |
3531 | | case TIME_RESOLUTION_YEAR: |
3532 | | interval = "year"; |
3533 | | break; |
3534 | | case TIME_RESOLUTION_MONTH: |
3535 | | interval = "month"; |
3536 | | break; |
3537 | | case TIME_RESOLUTION_DAY: |
3538 | | interval = "day"; |
3539 | | break; |
3540 | | case TIME_RESOLUTION_HOUR: |
3541 | | interval = "hour"; |
3542 | | break; |
3543 | | case TIME_RESOLUTION_MINUTE: |
3544 | | interval = "minute"; |
3545 | | break; |
3546 | | case TIME_RESOLUTION_SECOND: |
3547 | | interval = "second"; |
3548 | | break; |
3549 | | default: |
3550 | | return MS_FAILURE; |
3551 | | } |
3552 | | |
3553 | | snprintf(dest, destsize, "date_trunc('%s',%s)", interval, timestamp); |
3554 | | return MS_SUCCESS; |
3555 | | } |
3556 | | |
3557 | | /* |
3558 | | * create a postgresql where clause for the range given by the two input |
3559 | | * timestring, taking into account the resolution (e.g. second, day, month...) |
3560 | | * of each of the given timestrings (both timestrings can have different |
3561 | | * resolutions, although I don't know if that's a valid TIME range we apply the |
3562 | | * date_trunc function on the given timestrings and not on the time column in |
3563 | | * order for postgres to take advantage of an eventual index on the time column |
3564 | | * |
3565 | | * the generated sql is |
3566 | | * |
3567 | | * ( |
3568 | | * timecol >= date_trunc(mintimestring,minresolution) |
3569 | | * and |
3570 | | * timecol < date_trunc(maxtimestring,maxresolution) + interval '1 |
3571 | | * maxresolution' |
3572 | | * ) |
3573 | | */ |
3574 | | static int createPostgresTimeCompareLessThan(const char *timestring, char *dest, |
3575 | | size_t destsize) { |
3576 | | int timeresolution = msTimeGetResolution(timestring); |
3577 | | char timestamp[100]; |
3578 | | const char *interval; |
3579 | | if (timeresolution < 0) |
3580 | | return MS_FALSE; |
3581 | | |
3582 | | postgresTimeStampForTimeString(timestring, timestamp, 100); |
3583 | | |
3584 | | switch (timeresolution) { |
3585 | | case TIME_RESOLUTION_YEAR: |
3586 | | interval = "year"; |
3587 | | break; |
3588 | | case TIME_RESOLUTION_MONTH: |
3589 | | interval = "month"; |
3590 | | break; |
3591 | | case TIME_RESOLUTION_DAY: |
3592 | | interval = "day"; |
3593 | | break; |
3594 | | case TIME_RESOLUTION_HOUR: |
3595 | | interval = "hour"; |
3596 | | break; |
3597 | | case TIME_RESOLUTION_MINUTE: |
3598 | | interval = "minute"; |
3599 | | break; |
3600 | | case TIME_RESOLUTION_SECOND: |
3601 | | interval = "second"; |
3602 | | break; |
3603 | | default: |
3604 | | return MS_FAILURE; |
3605 | | } |
3606 | | snprintf(dest, destsize, |
3607 | | "(date_trunc('%s',%s) + interval '1 %s' - interval '1 second')", |
3608 | | interval, timestamp, interval); |
3609 | | return MS_SUCCESS; |
3610 | | } |
3611 | | #endif |
3612 | | |
3613 | 0 | static char *msPostGISEscapeSQLParam(layerObj *layer, const char *pszString) { |
3614 | | #ifdef USE_POSTGIS |
3615 | | char *pszEscapedStr = nullptr; |
3616 | | |
3617 | | if (layer && pszString) { |
3618 | | if (!msPostGISLayerIsOpen(layer)) |
3619 | | msPostGISLayerOpen(layer); |
3620 | | |
3621 | | assert(layer->layerinfo != nullptr); |
3622 | | |
3623 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
3624 | | size_t nSrcLen = strlen(pszString); |
3625 | | pszEscapedStr = (char *)msSmallMalloc(2 * nSrcLen + 1); |
3626 | | int nError = 0; |
3627 | | PQescapeStringConn(layerinfo->pgconn, pszEscapedStr, pszString, nSrcLen, |
3628 | | &nError); |
3629 | | if (nError != 0) { |
3630 | | free(pszEscapedStr); |
3631 | | pszEscapedStr = nullptr; |
3632 | | } |
3633 | | } |
3634 | | return pszEscapedStr; |
3635 | | #else |
3636 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
3637 | 0 | "msPostGISEscapeSQLParam()"); |
3638 | 0 | return NULL; |
3639 | 0 | #endif |
3640 | 0 | } |
3641 | | |
3642 | 0 | static void msPostGISEnablePaging(layerObj *layer, int value) { |
3643 | | #ifdef USE_POSTGIS |
3644 | | if (layer->debug) { |
3645 | | msDebug("msPostGISEnablePaging called.\n"); |
3646 | | } |
3647 | | |
3648 | | if (!msPostGISLayerIsOpen(layer)) { |
3649 | | if (msPostGISLayerOpen(layer) != MS_SUCCESS) { |
3650 | | return; |
3651 | | } |
3652 | | } |
3653 | | |
3654 | | assert(layer->layerinfo != nullptr); |
3655 | | |
3656 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
3657 | | layerinfo->paging = value; |
3658 | | |
3659 | | #else |
3660 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
3661 | 0 | "msPostGISEnablePaging()"); |
3662 | 0 | #endif |
3663 | 0 | return; |
3664 | 0 | } |
3665 | | |
3666 | 0 | static int msPostGISGetPaging(layerObj *layer) { |
3667 | | #ifdef USE_POSTGIS |
3668 | | if (layer->debug) { |
3669 | | msDebug("msPostGISGetPaging called.\n"); |
3670 | | } |
3671 | | |
3672 | | if (!msPostGISLayerIsOpen(layer)) |
3673 | | return MS_TRUE; |
3674 | | |
3675 | | assert(layer->layerinfo != nullptr); |
3676 | | |
3677 | | msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo; |
3678 | | return layerinfo->paging; |
3679 | | #else |
3680 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
3681 | 0 | "msPostGISEnablePaging()"); |
3682 | 0 | return MS_FAILURE; |
3683 | 0 | #endif |
3684 | 0 | } |
3685 | | |
3686 | | /* |
3687 | | ** msPostGISLayerTranslateFilter() |
3688 | | ** |
3689 | | ** Registered vtable->LayerTranslateFilter function. |
3690 | | */ |
3691 | | static int msPostGISLayerTranslateFilter(layerObj *layer, expressionObj *filter, |
3692 | 0 | char *filteritem) { |
3693 | | #ifdef USE_POSTGIS |
3694 | | tokenListNodeObjPtr node = nullptr; |
3695 | | |
3696 | | std::string native_string; |
3697 | | |
3698 | | int comparisonToken = -1; |
3699 | | int bindingToken = -1; |
3700 | | |
3701 | | msPostGISLayerInfo *layerinfo = |
3702 | | static_cast<msPostGISLayerInfo *>(layer->layerinfo); |
3703 | | |
3704 | | if (!filter->string) |
3705 | | return MS_SUCCESS; /* not an error, just nothing to do */ |
3706 | | |
3707 | | // fprintf(stderr, "input: %s, %s, %d\n", filter->string, filteritem, |
3708 | | // filter->type); |
3709 | | |
3710 | | /* |
3711 | | ** FILTERs use MapServer syntax *only* (#5001). |
3712 | | */ |
3713 | | if (filter->type == MS_STRING && filter->string && |
3714 | | filteritem) { /* item/value pair - add escaping */ |
3715 | | |
3716 | | // check if field is numeric and avoid converting to string |
3717 | | // as this prevents indexes being used |
3718 | | bool is_numeric = msLayerPropertyIsNumeric(layer, filteritem); |
3719 | | |
3720 | | char *stresc = msLayerEscapePropertyName(layer, filteritem); |
3721 | | if (filter->flags & MS_EXP_INSENSITIVE) { |
3722 | | native_string += "upper("; |
3723 | | native_string += stresc; |
3724 | | native_string += "::text) = upper("; |
3725 | | } else { |
3726 | | native_string += stresc; |
3727 | | if (!is_numeric) { |
3728 | | native_string += "::text"; |
3729 | | } |
3730 | | native_string += " = "; |
3731 | | } |
3732 | | msFree(stresc); |
3733 | | |
3734 | | /* don't have a type for the righthand literal so assume it's a string and |
3735 | | * we quote */ |
3736 | | stresc = msPostGISEscapeSQLParam(layer, filter->string); |
3737 | | if (!is_numeric) { |
3738 | | native_string += '\''; |
3739 | | native_string += stresc; |
3740 | | native_string += '\''; |
3741 | | } else { |
3742 | | native_string += stresc; |
3743 | | } |
3744 | | msFree(stresc); |
3745 | | |
3746 | | if (filter->flags & MS_EXP_INSENSITIVE) |
3747 | | native_string += ")"; |
3748 | | } else if (filter->type == MS_REGEX && filter->string && |
3749 | | filteritem) { /* item/regex pair - add escaping */ |
3750 | | |
3751 | | char *stresc = msLayerEscapePropertyName(layer, filteritem); |
3752 | | native_string += stresc; |
3753 | | if (filter->flags & MS_EXP_INSENSITIVE) { |
3754 | | native_string += "::text ~* "; |
3755 | | } else { |
3756 | | native_string += "::text ~ "; |
3757 | | } |
3758 | | msFree(stresc); |
3759 | | |
3760 | | stresc = msPostGISEscapeSQLParam(layer, filter->string); |
3761 | | native_string += '\''; |
3762 | | native_string += stresc; |
3763 | | native_string += '\''; |
3764 | | msFree(stresc); |
3765 | | } else if (filter->type == MS_EXPRESSION) { |
3766 | | int ieq_expected = MS_FALSE; |
3767 | | |
3768 | | if (msPostGISParseData(layer) != MS_SUCCESS) |
3769 | | return MS_FAILURE; |
3770 | | |
3771 | | if (layer->debug >= 2) |
3772 | | msDebug("msPostGISLayerTranslateFilter. String: %s.\n", filter->string); |
3773 | | if (!filter->tokens) |
3774 | | return MS_SUCCESS; |
3775 | | if (layer->debug >= 2) |
3776 | | msDebug( |
3777 | | "msPostGISLayerTranslateFilter. There are tokens to process... \n"); |
3778 | | |
3779 | | node = filter->tokens; |
3780 | | while (node != nullptr) { |
3781 | | |
3782 | | /* |
3783 | | ** Do any token caching/tracking here, easier to have it in one place. |
3784 | | */ |
3785 | | if (node->token == MS_TOKEN_BINDING_TIME) { |
3786 | | bindingToken = node->token; |
3787 | | } else if (node->token == MS_TOKEN_COMPARISON_EQ || |
3788 | | node->token == MS_TOKEN_COMPARISON_IEQ || |
3789 | | node->token == MS_TOKEN_COMPARISON_NE || |
3790 | | node->token == MS_TOKEN_COMPARISON_GT || |
3791 | | node->token == MS_TOKEN_COMPARISON_GE || |
3792 | | node->token == MS_TOKEN_COMPARISON_LT || |
3793 | | node->token == MS_TOKEN_COMPARISON_LE || |
3794 | | node->token == MS_TOKEN_COMPARISON_IN) { |
3795 | | comparisonToken = node->token; |
3796 | | } |
3797 | | |
3798 | | switch (node->token) { |
3799 | | |
3800 | | /* literal tokens */ |
3801 | | case MS_TOKEN_LITERAL_BOOLEAN: |
3802 | | if (node->tokenval.dblval == MS_TRUE) |
3803 | | native_string += "TRUE"; |
3804 | | else |
3805 | | native_string += "FALSE"; |
3806 | | break; |
3807 | | case MS_TOKEN_LITERAL_NUMBER: { |
3808 | | if (node->tokenval.dblval >= INT_MIN && |
3809 | | node->tokenval.dblval <= INT_MAX && |
3810 | | node->tokenval.dblval == (int)node->tokenval.dblval) |
3811 | | native_string += std::to_string((int)node->tokenval.dblval); |
3812 | | else { |
3813 | | char buffer[32]; |
3814 | | snprintf(buffer, sizeof(buffer), "%.18g", node->tokenval.dblval); |
3815 | | native_string += buffer; |
3816 | | } |
3817 | | break; |
3818 | | } |
3819 | | case MS_TOKEN_LITERAL_STRING: |
3820 | | |
3821 | | if (comparisonToken == MS_TOKEN_COMPARISON_IN) { /* issue 5490 */ |
3822 | | int nstrings = 0; |
3823 | | char **strings = msStringSplit(node->tokenval.strval, ',', &nstrings); |
3824 | | if (nstrings > 0) { |
3825 | | native_string += "("; |
3826 | | for (int i = 0; i < nstrings; i++) { |
3827 | | if (i != 0) |
3828 | | native_string += ","; |
3829 | | char *stresc = msPostGISEscapeSQLParam(layer, strings[i]); |
3830 | | native_string += '\''; |
3831 | | native_string += stresc; |
3832 | | native_string += '\''; |
3833 | | msFree(stresc); |
3834 | | } |
3835 | | native_string += ")"; |
3836 | | } |
3837 | | |
3838 | | msFreeCharArray(strings, nstrings); |
3839 | | } else { |
3840 | | const char *strbegin; |
3841 | | const char *strend; |
3842 | | if (comparisonToken == MS_TOKEN_COMPARISON_IEQ) { |
3843 | | strbegin = "lower('"; |
3844 | | strend = "')"; |
3845 | | } else { |
3846 | | strbegin = "'"; |
3847 | | strend = "'"; |
3848 | | } |
3849 | | char *stresc = msPostGISEscapeSQLParam(layer, node->tokenval.strval); |
3850 | | native_string += strbegin; |
3851 | | native_string += stresc; |
3852 | | native_string += strend; |
3853 | | msFree(stresc); |
3854 | | } |
3855 | | |
3856 | | break; |
3857 | | case MS_TOKEN_LITERAL_TIME: { |
3858 | | char *snippet = (char *)msSmallMalloc(512); |
3859 | | if (comparisonToken == MS_TOKEN_COMPARISON_EQ) { // TODO: support != |
3860 | | createPostgresTimeCompareEquals(node->tokensrc, snippet, 512); |
3861 | | } else if (comparisonToken == MS_TOKEN_COMPARISON_GT || |
3862 | | comparisonToken == MS_TOKEN_COMPARISON_GE) { |
3863 | | createPostgresTimeCompareGreaterThan(node->tokensrc, snippet, 512); |
3864 | | } else if (comparisonToken == MS_TOKEN_COMPARISON_LT || |
3865 | | comparisonToken == MS_TOKEN_COMPARISON_LE) { |
3866 | | createPostgresTimeCompareLessThan(node->tokensrc, snippet, 512); |
3867 | | } else { |
3868 | | msFree(snippet); |
3869 | | goto cleanup; |
3870 | | } |
3871 | | |
3872 | | comparisonToken = -1; |
3873 | | bindingToken = -1; /* reset */ |
3874 | | native_string += snippet; |
3875 | | msFree(snippet); |
3876 | | break; |
3877 | | } |
3878 | | case MS_TOKEN_LITERAL_SHAPE: { |
3879 | | char *wkt = msShapeToWKT(node->tokenval.shpval); |
3880 | | native_string += "ST_GeomFromText('"; |
3881 | | native_string += wkt; |
3882 | | msFree(wkt); |
3883 | | native_string += "'"; |
3884 | | if (!layerinfo->srid.empty()) { |
3885 | | native_string += ","; |
3886 | | native_string += layerinfo->srid; |
3887 | | } |
3888 | | native_string += ")"; |
3889 | | break; |
3890 | | } |
3891 | | |
3892 | | /* data binding tokens */ |
3893 | | case MS_TOKEN_BINDING_TIME: |
3894 | | case MS_TOKEN_BINDING_DOUBLE: |
3895 | | case MS_TOKEN_BINDING_INTEGER: |
3896 | | case MS_TOKEN_BINDING_STRING: { |
3897 | | const char *strbegin = ""; |
3898 | | const char *strend = ""; |
3899 | | if (node->token == MS_TOKEN_BINDING_STRING && |
3900 | | node->next->token == MS_TOKEN_COMPARISON_IEQ) { |
3901 | | strbegin = "lower("; |
3902 | | strend = "::text)"; |
3903 | | ieq_expected = MS_TRUE; |
3904 | | } else if (node->token == MS_TOKEN_BINDING_STRING || |
3905 | | node->next->token == MS_TOKEN_COMPARISON_RE || |
3906 | | node->next->token == MS_TOKEN_COMPARISON_IRE) |
3907 | | strend = "::text"; /* explicit cast necessary for certain operators */ |
3908 | | |
3909 | | char *stresc = |
3910 | | msLayerEscapePropertyName(layer, node->tokenval.bindval.item); |
3911 | | native_string += strbegin; |
3912 | | native_string += stresc; |
3913 | | native_string += strend; |
3914 | | msFree(stresc); |
3915 | | break; |
3916 | | } |
3917 | | case MS_TOKEN_BINDING_SHAPE: |
3918 | | native_string += layerinfo->geomcolumn; |
3919 | | break; |
3920 | | case MS_TOKEN_BINDING_MAP_CELLSIZE: { |
3921 | | char buffer[32]; |
3922 | | snprintf(buffer, sizeof(buffer), "%.18g", layer->map->cellsize); |
3923 | | native_string += buffer; |
3924 | | break; |
3925 | | } |
3926 | | |
3927 | | /* spatial comparison tokens */ |
3928 | | case MS_TOKEN_COMPARISON_INTERSECTS: |
3929 | | case MS_TOKEN_COMPARISON_DISJOINT: |
3930 | | case MS_TOKEN_COMPARISON_TOUCHES: |
3931 | | case MS_TOKEN_COMPARISON_OVERLAPS: |
3932 | | case MS_TOKEN_COMPARISON_CROSSES: |
3933 | | case MS_TOKEN_COMPARISON_WITHIN: |
3934 | | case MS_TOKEN_COMPARISON_CONTAINS: |
3935 | | case MS_TOKEN_COMPARISON_EQUALS: |
3936 | | case MS_TOKEN_COMPARISON_DWITHIN: { |
3937 | | if (node->next->token != '(') |
3938 | | goto cleanup; |
3939 | | native_string += "st_"; |
3940 | | const char *str = msExpressionTokenToString(node->token); |
3941 | | if (str == nullptr) |
3942 | | goto cleanup; |
3943 | | native_string += str; |
3944 | | break; |
3945 | | } |
3946 | | |
3947 | | /* functions */ |
3948 | | case MS_TOKEN_FUNCTION_LENGTH: |
3949 | | case MS_TOKEN_FUNCTION_AREA: |
3950 | | case MS_TOKEN_FUNCTION_BUFFER: |
3951 | | case MS_TOKEN_FUNCTION_DIFFERENCE: { |
3952 | | native_string += "st_"; |
3953 | | const char *str = msExpressionTokenToString(node->token); |
3954 | | if (str == nullptr) |
3955 | | goto cleanup; |
3956 | | native_string += str; |
3957 | | break; |
3958 | | } |
3959 | | |
3960 | | case MS_TOKEN_COMPARISON_IEQ: |
3961 | | if (ieq_expected) { |
3962 | | native_string += "="; |
3963 | | ieq_expected = MS_FALSE; |
3964 | | } else { |
3965 | | goto cleanup; |
3966 | | } |
3967 | | break; |
3968 | | |
3969 | | /* unsupported tokens */ |
3970 | | case MS_TOKEN_COMPARISON_BEYOND: |
3971 | | case MS_TOKEN_FUNCTION_TOSTRING: |
3972 | | case MS_TOKEN_FUNCTION_ROUND: |
3973 | | case MS_TOKEN_FUNCTION_SIMPLIFY: |
3974 | | case MS_TOKEN_FUNCTION_SIMPLIFYPT: |
3975 | | case MS_TOKEN_FUNCTION_GENERALIZE: |
3976 | | goto cleanup; |
3977 | | break; |
3978 | | |
3979 | | default: { |
3980 | | /* by default accept the general token to string conversion */ |
3981 | | |
3982 | | if (node->token == MS_TOKEN_COMPARISON_EQ && node->next != nullptr && |
3983 | | node->next->token == MS_TOKEN_LITERAL_TIME) |
3984 | | break; /* skip, handled with the next token */ |
3985 | | if (bindingToken == MS_TOKEN_BINDING_TIME && |
3986 | | (node->token == MS_TOKEN_COMPARISON_EQ || |
3987 | | node->token == MS_TOKEN_COMPARISON_NE)) |
3988 | | break; /* skip, handled elsewhere */ |
3989 | | if (node->token == MS_TOKEN_COMPARISON_EQ && node->next != nullptr && |
3990 | | node->next->token == MS_TOKEN_LITERAL_STRING && |
3991 | | strcmp(node->next->tokenval.strval, "_MAPSERVER_NULL_") == 0) { |
3992 | | native_string += " IS NULL"; |
3993 | | node = node->next; |
3994 | | break; |
3995 | | } |
3996 | | |
3997 | | const char *str = msExpressionTokenToString(node->token); |
3998 | | if (str == nullptr) |
3999 | | goto cleanup; |
4000 | | native_string += str; |
4001 | | break; |
4002 | | } |
4003 | | } |
4004 | | |
4005 | | node = node->next; |
4006 | | } |
4007 | | } |
4008 | | |
4009 | | filter->native_string = msStrdup(native_string.c_str()); |
4010 | | |
4011 | | // fprintf(stderr, "output: %s\n", filter->native_string); |
4012 | | |
4013 | | return MS_SUCCESS; |
4014 | | |
4015 | | cleanup: |
4016 | | msSetError(MS_MISCERR, "Translation to native SQL failed.", |
4017 | | "msPostGISLayerTranslateFilter()"); |
4018 | | return MS_FAILURE; |
4019 | | #else |
4020 | 0 | msSetError(MS_MISCERR, "PostGIS support is not available.", |
4021 | 0 | "msPostGISLayerTranslateFilter()"); |
4022 | 0 | return MS_FAILURE; |
4023 | 0 | #endif |
4024 | 0 | } |
4025 | | |
4026 | 0 | int msPostGISLayerInitializeVirtualTable(layerObj *layer) { |
4027 | 0 | assert(layer != nullptr); |
4028 | 0 | assert(layer->vtable != nullptr); |
4029 | | |
4030 | 0 | layer->vtable->LayerTranslateFilter = msPostGISLayerTranslateFilter; |
4031 | |
|
4032 | 0 | layer->vtable->LayerInitItemInfo = msPostGISLayerInitItemInfo; |
4033 | 0 | layer->vtable->LayerFreeItemInfo = msPostGISLayerFreeItemInfo; |
4034 | 0 | layer->vtable->LayerOpen = msPostGISLayerOpen; |
4035 | 0 | layer->vtable->LayerIsOpen = msPostGISLayerIsOpen; |
4036 | 0 | layer->vtable->LayerWhichShapes = msPostGISLayerWhichShapes; |
4037 | 0 | layer->vtable->LayerNextShape = msPostGISLayerNextShape; |
4038 | 0 | layer->vtable->LayerGetShape = msPostGISLayerGetShape; |
4039 | 0 | layer->vtable->LayerGetShapeCount = msPostGISLayerGetShapeCount; |
4040 | 0 | layer->vtable->LayerClose = msPostGISLayerClose; |
4041 | 0 | layer->vtable->LayerGetItems = msPostGISLayerGetItems; |
4042 | 0 | layer->vtable->LayerGetExtent = msPostGISLayerGetExtent; |
4043 | 0 | layer->vtable->LayerApplyFilterToLayer = msLayerApplyCondSQLFilterToLayer; |
4044 | | /* layer->vtable->LayerGetAutoStyle, not supported for this layer */ |
4045 | | /* layer->vtable->LayerCloseConnection = msPostGISLayerClose; */ |
4046 | | // layer->vtable->LayerSetTimeFilter = msPostGISLayerSetTimeFilter; |
4047 | 0 | layer->vtable->LayerSetTimeFilter = msLayerMakeBackticsTimeFilter; |
4048 | | /* layer->vtable->LayerCreateItems, use default */ |
4049 | 0 | layer->vtable->LayerGetNumFeatures = msPostGISLayerGetNumFeatures; |
4050 | | |
4051 | | /* layer->vtable->LayerGetAutoProjection, use default*/ |
4052 | |
|
4053 | 0 | layer->vtable->LayerEscapeSQLParam = msPostGISEscapeSQLParam; |
4054 | 0 | layer->vtable->LayerEnablePaging = msPostGISEnablePaging; |
4055 | 0 | layer->vtable->LayerGetPaging = msPostGISGetPaging; |
4056 | |
|
4057 | 0 | return MS_SUCCESS; |
4058 | 0 | } |