Coverage Report

Created: 2025-06-13 06:29

/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, &center, &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
}