• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MapServer / MapServer / 26710430429

31 May 2026 10:44AM UTC coverage: 42.443% (+0.002%) from 42.441%
26710430429

push

github

web-flow
PostGIS: make sure identifier value is numeric when the declared type is numeric too (#7516)

Fixes https://github.com/MapServer/MapServer/security/advisories/GHSA-xp29-8wp5-wc3p

0 of 7 new or added lines in 1 file covered. (0.0%)

337 existing lines in 2 files now uncovered.

64665 of 152357 relevant lines covered (42.44%)

27398.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.65
/src/mappostgis.cpp
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
#include <assert.h>
58
#include <string.h>
59
#include <math.h>
60
#include "mapserver.h"
61
#include "maptime.h"
62
#include "mappostgis.h"
63
#include "mapows.h"
64

65
#include <vector>
66

67
#define FP_EPSILON 1e-12
68
#define FP_EQ(a, b) (fabs((a) - (b)) < FP_EPSILON)
69
#define FP_LEFT -1
70
#define FP_RIGHT 1
71
#define FP_COLINEAR 0
72

73
#define SEGMENT_ANGLE 10.0
74
#define SEGMENT_MINPOINTS 10
75

76
#define WKBZOFFSET_NONISO 0x80000000
77
#define WKBMOFFSET_NONISO 0x40000000
78

79
#define HAS_Z 0x1
80
#define HAS_M 0x2
81

82
#if TRANSFER_ENCODING == 256
83
#define RESULTSET_TYPE 1
84
#else
85
#define RESULTSET_TYPE 0
86
#endif
87

88
/* These are the OIDs for some builtin types, as returned by PQftype(). */
89
/* They were copied from pg_type.h in src/include/catalog/pg_type.h */
90

91
#ifndef BOOLOID
92
#define BOOLOID 16
93
#define BYTEAOID 17
94
#define CHAROID 18
95
#define NAMEOID 19
96
#define INT8OID 20
97
#define INT2OID 21
98
#define INT2VECTOROID 22
99
#define INT4OID 23
100
#define REGPROCOID 24
101
#define TEXTOID 25
102
#define OIDOID 26
103
#define TIDOID 27
104
#define XIDOID 28
105
#define CIDOID 29
106
#define OIDVECTOROID 30
107
#define FLOAT4OID 700
108
#define FLOAT8OID 701
109
#define INT4ARRAYOID 1007
110
#define TEXTARRAYOID 1009
111
#define BPCHARARRAYOID 1014
112
#define VARCHARARRAYOID 1015
113
#define FLOAT4ARRAYOID 1021
114
#define FLOAT8ARRAYOID 1022
115
#define BPCHAROID 1042
116
#define VARCHAROID 1043
117
#define DATEOID 1082
118
#define TIMEOID 1083
119
#define TIMETZOID 1266
120
#define TIMESTAMPOID 1114
121
#define TIMESTAMPTZOID 1184
122
#define NUMERICOID 1700
123
#endif
124

125
#ifdef USE_POSTGIS
126

127
static int wkbConvGeometryToShape(wkbObj *w, shapeObj *shape);
128
static int arcStrokeCircularString(wkbObj *w, double segment_angle,
129
                                   lineObj *line, int nZMFlag);
130

131
/*
132
** msPostGISCloseConnection()
133
**
134
** Handler registered with msConnPoolRegister so that Mapserver
135
** can clean up open connections during a shutdown.
136
*/
137
static void msPostGISCloseConnection(void *pgconn) {
529✔
138
  PQfinish((PGconn *)pgconn);
529✔
139
}
529✔
140

141
/*
142
** msPostGISCreateLayerInfo()
143
*/
144
static msPostGISLayerInfo *msPostGISCreateLayerInfo(void) {
555✔
145
  msPostGISLayerInfo *layerinfo = new msPostGISLayerInfo;
555✔
146
  layerinfo->paging = MS_TRUE;
555✔
147
  layerinfo->force2d = MS_FALSE;
148
  return layerinfo;
555✔
149
}
150

151
/*
152
** msPostGISFreeLayerInfo()
153
*/
154
static void msPostGISFreeLayerInfo(layerObj *layer) {
554✔
155
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
554✔
156
  if (layerinfo->pgresult)
554✔
157
    PQclear(layerinfo->pgresult);
222✔
158
  if (layerinfo->pgconn)
554✔
159
    msConnPoolRelease(layer, layerinfo->pgconn);
554✔
160
  delete layerinfo;
554✔
161
  layer->layerinfo = nullptr;
554✔
162
}
554✔
163

164
/*
165
** postgresqlNoticeHandler()
166
**
167
** Propagate messages from the database to the Mapserver log,
168
** set in PQsetNoticeProcessor during layer open.
169
*/
170
static void postresqlNoticeHandler(void *arg, const char *message) {
×
171
  layerObj *lp = (layerObj *)arg;
172

173
  if (lp->debug) {
×
174
    msDebug("%s\n", message);
×
175
  }
176
}
×
177

178
/*
179
** Expandable pointObj array. The lineObj unfortunately
180
** is not useful for this purpose, so we have this one.
181
*/
182
static std::vector<pointObj> pointArrayNew(int maxpoints) {
×
183
  auto v = std::vector<pointObj>();
×
184
  v.reserve(maxpoints);
×
185
  return v;
×
186
}
×
187

188
/*
189
** Add a pointObj to the pointObjArray.
190
*/
191
static void pointArrayAddPoint(std::vector<pointObj> &v, const pointObj &p) {
192
  v.push_back(p);
×
193
}
×
194

195
/*
196
** Pass an input type number through the PostGIS version
197
** type map array to handle the pre-2.0 incorrect WKB types
198
*/
199

200
static int wkbTypeMap(wkbObj *w, int type, int *pnZMFlag) {
11,872✔
201
  *pnZMFlag = 0;
11,872✔
202
  /* PostGIS >= 2 : ISO SQL/MM style Z types ? */
203
  if (type >= 1000 && type < 2000) {
11,872✔
204
    type -= 1000;
104✔
205
    *pnZMFlag = HAS_Z;
104✔
206
  }
207
  /* PostGIS >= 2 : ISO SQL/MM style M types ? */
208
  else if (type >= 2000 && type < 3000) {
11,768✔
209
    type -= 2000;
×
210
    *pnZMFlag = HAS_M;
×
211
  }
212
  /* PostGIS >= 2 : ISO SQL/MM style ZM types ? */
213
  else if (type >= 3000 && type < 4000) {
11,768✔
214
    type -= 3000;
×
215
    *pnZMFlag = HAS_Z | HAS_M;
×
216
  }
217
  /* PostGIS 1.X EWKB : Extended WKB Z or ZM ? */
218
  else if ((type & WKBZOFFSET_NONISO) != 0) {
11,768✔
219
    if ((type & WKBMOFFSET_NONISO) != 0)
×
220
      *pnZMFlag = HAS_Z | HAS_M;
×
221
    else
222
      *pnZMFlag = HAS_Z;
×
223
    type &= 0x00FFFFFF;
×
224
  }
225
  /* PostGIS 1.X EWKB: Extended WKB M ? */
226
  else if ((type & WKBMOFFSET_NONISO) != 0) {
11,768✔
227
    *pnZMFlag = HAS_M;
×
228
    type &= 0x00FFFFFF;
×
229
  }
230
  if (type >= 0 && type < WKB_TYPE_COUNT)
11,872✔
231
    return w->typemap[type];
11,872✔
232
  else
233
    return 0;
234
}
235

236
/*
237
** Read the WKB type number from a wkbObj without
238
** advancing the read pointer.
239
*/
240
static int wkbType(wkbObj *w, int *pnZMFlag) {
241
  int t;
242
  memcpy(&t, (w->ptr + 1), sizeof(int));
5,936✔
243
  return wkbTypeMap(w, t, pnZMFlag);
5,936✔
244
}
245

246
/*
247
** Read the type number of the first element of a
248
** collection without advancing the read pointer.
249
*/
250
static int wkbCollectionSubType(wkbObj *w, int *pnZMFlag) {
251
  int t;
252
  memcpy(&t, (w->ptr + 1 + 4 + 4 + 1), sizeof(int));
253
  return wkbTypeMap(w, t, pnZMFlag);
×
254
}
255

256
/*
257
** Read one byte from the WKB and advance the read pointer
258
*/
259
static char wkbReadChar(wkbObj *w) {
260
  char c;
261
  memcpy(&c, w->ptr, sizeof(char));
5,936✔
262
  w->ptr += sizeof(char);
5,936✔
263
  return c;
264
}
265

266
/*
267
** Read one integer from the WKB and advance the read pointer.
268
** We assume the endianness of the WKB is the same as this machine.
269
*/
270
static inline int wkbReadInt(wkbObj *w) {
271
  int i;
272
  memcpy(&i, w->ptr, sizeof(int));
×
273
  w->ptr += sizeof(int);
10,662✔
274
  return i;
275
}
276

277
/*
278
** Read one pointObj (two doubles) from the WKB and advance the read pointer.
279
** We assume the endianness of the WKB is the same as this machine.
280
*/
281
static inline void wkbReadPointP(wkbObj *w, pointObj *p, int nZMFlag) {
185,094✔
282
  memcpy(&(p->x), w->ptr, sizeof(double));
185,094✔
283
  w->ptr += sizeof(double);
185,094✔
284
  memcpy(&(p->y), w->ptr, sizeof(double));
185,094✔
285
  w->ptr += sizeof(double);
185,094✔
286
  if (nZMFlag & HAS_Z) {
185,094✔
287
    memcpy(&(p->z), w->ptr, sizeof(double));
156✔
288
    w->ptr += sizeof(double);
156✔
289
  } else {
290
    p->z = 0;
184,938✔
291
  }
292
  if (nZMFlag & HAS_M) {
185,094✔
293
    memcpy(&(p->m), w->ptr, sizeof(double));
×
294
    w->ptr += sizeof(double);
×
295
  } else {
296
    p->m = 0;
185,094✔
297
  }
298
}
185,094✔
299

300
/*
301
** Read one pointObj (two doubles) from the WKB and advance the read pointer.
302
** We assume the endianness of the WKB is the same as this machine.
303
*/
304
static inline pointObj wkbReadPoint(wkbObj *w, int nZMFlag) {
305
  pointObj p;
306
  wkbReadPointP(w, &p, nZMFlag);
1,557✔
307
  return p;
308
}
309

310
/*
311
** Read a "point array" and return an allocated lineObj.
312
** A point array is a WKB fragment that starts with a
313
** point count, which is followed by that number of doubles * 2.
314
** Linestrings, circular strings, polygon rings, all show this
315
** form.
316
*/
317
static void wkbReadLine(wkbObj *w, lineObj *line, int nZMFlag) {
2,208✔
318
  pointObj p;
319
  const int npoints = wkbReadInt(w);
320
  if (npoints > (int)((w->size - (w->ptr - w->wkb)) / 16))
2,208✔
321
    return;
×
322

323
  line->numpoints = npoints;
2,208✔
324
  line->point = (pointObj *)msSmallMalloc(npoints * sizeof(pointObj));
2,208✔
325
  for (int i = 0; i < npoints; i++) {
185,745✔
326
    wkbReadPointP(w, &p, nZMFlag);
183,537✔
327
    line->point[i] = p;
183,537✔
328
  }
329
}
330

331
/*
332
** Advance the read pointer past a geometry without returning any
333
** values. Used for skipping un-drawable elements in a collection.
334
*/
335
static void wkbSkipGeometry(wkbObj *w) {
×
336
  /*endian = */ wkbReadChar(w);
337
  int nZMFlag;
338
  const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag);
×
339
  const int nCoordDim = 2 + (((nZMFlag & HAS_Z) != 0) ? 1 : 0) +
×
340
                        (((nZMFlag & HAS_M) != 0) ? 1 : 0);
×
341
  switch (type) {
×
342
  case WKB_POINT:
×
343
    w->ptr += nCoordDim * sizeof(double);
×
344
    break;
×
345
  case WKB_CIRCULARSTRING:
346
  case WKB_LINESTRING: {
347
    const int npoints = wkbReadInt(w);
348
    w->ptr += sizeof(double) * npoints * nCoordDim;
×
349
    break;
×
350
  }
351
  case WKB_POLYGON: {
352
    const int nrings = wkbReadInt(w);
353
    if (nrings > (int)((w->size - (w->ptr - w->wkb)) / 4))
×
354
      return;
×
355
    for (int i = 0; i < nrings; i++) {
×
356
      const int npoints = wkbReadInt(w);
357
      w->ptr += sizeof(double) * npoints * nCoordDim;
×
358
    }
359
    break;
360
  }
361
  case WKB_MULTIPOINT:
362
  case WKB_MULTILINESTRING:
363
  case WKB_MULTIPOLYGON:
364
  case WKB_GEOMETRYCOLLECTION:
365
  case WKB_COMPOUNDCURVE:
366
  case WKB_CURVEPOLYGON:
367
  case WKB_MULTICURVE:
368
  case WKB_MULTISURFACE: {
369
    const int ngeoms = wkbReadInt(w);
370
    if (ngeoms > (int)((w->size - (w->ptr - w->wkb)) / 4))
×
371
      return;
372
    for (int i = 0; i < ngeoms; i++) {
×
373
      wkbSkipGeometry(w);
×
374
    }
375
    break;
376
  }
377
  }
378
}
379

380
/*
381
** Convert a WKB point to a shapeObj, advancing the read pointer as we go.
382
*/
383
static int wkbConvPointToShape(wkbObj *w, shapeObj *shape) {
1,557✔
384
  /*endian = */ wkbReadChar(w);
385
  int nZMFlag;
386
  const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag);
1,557✔
387

388
  if (type != WKB_POINT)
1,557✔
389
    return MS_FAILURE;
390

391
  if (!(shape->type == MS_SHAPE_POINT))
1,557✔
392
    return MS_FAILURE;
393
  lineObj line;
394
  line.numpoints = 1;
1,557✔
395
  line.point = (pointObj *)msSmallMalloc(sizeof(pointObj));
1,557✔
396
  line.point[0] = wkbReadPoint(w, nZMFlag);
1,557✔
397
  msAddLineDirectly(shape, &line);
1,557✔
398
  return MS_SUCCESS;
399
}
400

401
/*
402
** Convert a WKB line string to a shapeObj, advancing the read pointer as we go.
403
*/
404
static int wkbConvLineStringToShape(wkbObj *w, shapeObj *shape) {
1,861✔
405
  /*endian = */ wkbReadChar(w);
406
  int nZMFlag;
407
  const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag);
1,861✔
408

409
  if (type != WKB_LINESTRING)
1,861✔
410
    return MS_FAILURE;
411

412
  lineObj line;
413
  wkbReadLine(w, &line, nZMFlag);
1,861✔
414
  msAddLineDirectly(shape, &line);
1,861✔
415

416
  return MS_SUCCESS;
417
}
418

419
/*
420
** Convert a WKB polygon to a shapeObj, advancing the read pointer as we go.
421
*/
422
static int wkbConvPolygonToShape(wkbObj *w, shapeObj *shape) {
339✔
423
  /*endian = */ wkbReadChar(w);
424
  int nZMFlag;
425
  const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag);
339✔
426

427
  if (type != WKB_POLYGON)
339✔
428
    return MS_FAILURE;
429

430
  /* How many rings? */
431
  const int nrings = wkbReadInt(w);
432
  if (nrings > (int)((w->size - (w->ptr - w->wkb)) / 4))
339✔
433
    return MS_FAILURE;
434

435
  /* Add each ring to the shape */
436
  lineObj line;
437
  for (int i = 0; i < nrings; i++) {
686✔
438
    wkbReadLine(w, &line, nZMFlag);
347✔
439
    msAddLineDirectly(shape, &line);
347✔
440
  }
441

442
  return MS_SUCCESS;
443
}
444

445
/*
446
** Convert a WKB curve polygon to a shapeObj, advancing the read pointer as we
447
*go.
448
** The arc portions of the rings will be stroked to linestrings as they
449
** are read by the underlying circular string handling.
450
*/
451
static int wkbConvCurvePolygonToShape(wkbObj *w, shapeObj *shape) {
×
452
  const int was_poly = (shape->type == MS_SHAPE_POLYGON);
×
453

454
  /*endian = */ wkbReadChar(w);
455
  int nZMFlag;
456
  const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag);
×
457
  if (type != WKB_CURVEPOLYGON)
×
458
    return MS_FAILURE;
459

460
  const int ncomponents = wkbReadInt(w);
461
  if (ncomponents > (int)((w->size - (w->ptr - w->wkb)) / 4))
×
462
    return MS_FAILURE;
463

464
  /* Lower the allowed dimensionality so we can
465
   *  catch the linear ring components */
466
  shape->type = MS_SHAPE_LINE;
×
467

468
  int failures = 0;
469
  for (int i = 0; i < ncomponents; i++) {
×
470
    if (wkbConvGeometryToShape(w, shape) == MS_FAILURE) {
×
471
      wkbSkipGeometry(w);
×
472
      failures++;
×
473
    }
474
  }
475

476
  /* Go back to expected dimensionality */
477
  if (was_poly)
×
478
    shape->type = MS_SHAPE_POLYGON;
×
479

480
  if (failures == ncomponents)
×
481
    return MS_FAILURE;
482
  else
483
    return MS_SUCCESS;
484
}
485

486
/*
487
** Convert a WKB circular string to a shapeObj, advancing the read pointer as we
488
*go.
489
** Arcs will be stroked to linestrings.
490
*/
491
static int wkbConvCircularStringToShape(wkbObj *w, shapeObj *shape) {
×
492
  /*endian = */ wkbReadChar(w);
493
  int nZMFlag;
494
  const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag);
×
495

496
  if (type != WKB_CIRCULARSTRING)
×
497
    return MS_FAILURE;
498

499
  lineObj line = {0, nullptr};
×
500
  /* Stroke the string into a point array */
501
  if (arcStrokeCircularString(w, SEGMENT_ANGLE, &line, nZMFlag) == MS_FAILURE) {
×
502
    if (line.point)
×
503
      free(line.point);
×
504
    return MS_FAILURE;
×
505
  }
506

507
  /* Fill in the lineObj */
508
  if (line.numpoints > 0) {
×
509
    msAddLine(shape, &line);
×
510
    if (line.point)
×
511
      free(line.point);
×
512
  }
513

514
  return MS_SUCCESS;
515
}
516

517
/*
518
** Compound curves need special handling. First we load
519
** each component of the curve on the a lineObj in a shape.
520
** Then we merge those lineObjs into a single lineObj. This
521
** allows compound curves to serve as closed rings in
522
** curve polygons.
523
*/
524
static int wkbConvCompoundCurveToShape(wkbObj *w, shapeObj *shape) {
×
525
  /*endian = */ wkbReadChar(w);
526
  int nZMFlag;
527
  const int type = wkbTypeMap(w, wkbReadInt(w), &nZMFlag);
×
528

529
  /* Init our shape buffer */
530
  shapeObj shapebuf;
×
531
  msInitShape(&shapebuf);
×
532

533
  if (type != WKB_COMPOUNDCURVE)
×
534
    return MS_FAILURE;
535

536
  /* How many components in the compound curve? */
537
  const int ncomponents = wkbReadInt(w);
538
  if (ncomponents > (int)((w->size - (w->ptr - w->wkb)) / 4))
×
539
    return MS_FAILURE;
540

541
  /* We'll load each component onto a line in a shape */
542
  for (int i = 0; i < ncomponents; i++)
×
543
    wkbConvGeometryToShape(w, &shapebuf);
×
544

545
  /* Do nothing on empty */
546
  if (shapebuf.numlines == 0)
×
547
    return MS_FAILURE;
548

549
  /* Count the total number of points */
550
  int npoints = 0;
551
  for (int i = 0; i < shapebuf.numlines; i++)
×
552
    npoints += shapebuf.line[i].numpoints;
×
553

554
  /* Do nothing on empty */
555
  if (npoints == 0)
×
556
    return MS_FAILURE;
557

558
  lineObj line;
559
  line.numpoints = npoints;
×
560
  line.point = (pointObj *)msSmallMalloc(sizeof(pointObj) * npoints);
×
561

562
  /* Copy in the points */
563
  npoints = 0;
564
  for (int i = 0; i < shapebuf.numlines; i++) {
×
565
    for (int j = 0; j < shapebuf.line[i].numpoints; j++) {
×
566
      /* Don't add a start point that duplicates an endpoint */
567
      if (j == 0 && i > 0 &&
×
568
          memcmp(&(line.point[npoints - 1]), &(shapebuf.line[i].point[j]),
×
569
                 sizeof(pointObj)) == 0) {
570
        continue;
×
571
      }
572
      line.point[npoints++] = shapebuf.line[i].point[j];
×
573
    }
574
  }
575
  line.numpoints = npoints;
×
576

577
  /* Clean up */
578
  msFreeShape(&shapebuf);
×
579

580
  /* Fill in the lineObj */
581
  msAddLineDirectly(shape, &line);
×
582

583
  return MS_SUCCESS;
584
}
×
585

586
/*
587
** Convert a WKB collection string to a shapeObj, advancing the read pointer as
588
*we go.
589
** Many WKB types (MultiPoint, MultiLineString, MultiPolygon, MultiSurface,
590
** MultiCurve, GeometryCollection) can be treated identically as collections
591
** (they start with endian, type number and count of sub-elements, then provide
592
*the
593
** subelements as WKB) so are handled with this one function.
594
*/
595
static int wkbConvCollectionToShape(wkbObj *w, shapeObj *shape) {
2,179✔
596
  /*endian = */ wkbReadChar(w);
597
  int nZMFlag;
598
  /*type = */ wkbTypeMap(w, wkbReadInt(w), &nZMFlag);
2,179✔
599
  const int ncomponents = wkbReadInt(w);
600
  if (ncomponents > (int)((w->size - (w->ptr - w->wkb)) / 4))
2,179✔
601
    return MS_FAILURE;
602

603
  /*
604
   * If we can draw any portion of the collection, we will,
605
   * but if all the components fail, we will draw nothing.
606
   */
607
  int failures = 0;
608
  for (int i = 0; i < ncomponents; i++) {
4,370✔
609
    if (wkbConvGeometryToShape(w, shape) == MS_FAILURE) {
2,191✔
610
      wkbSkipGeometry(w);
×
611
      failures++;
×
612
    }
613
  }
614
  if (failures == ncomponents || ncomponents == 0)
2,179✔
615
    return MS_FAILURE;
616
  else
617
    return MS_SUCCESS;
618
}
619

620
/*
621
** Generic handler to switch to the appropriate function for the WKB type.
622
** Note that we also handle switching here to avoid processing shapes
623
** we will be unable to draw. Example: we can't draw point features as
624
** a MS_SHAPE_LINE layer, so if the type is WKB_POINT and the layer is
625
** MS_SHAPE_LINE, we exit before converting.
626
*/
627
static int wkbConvGeometryToShape(wkbObj *w, shapeObj *shape) {
5,936✔
628
  int nZMFlag;
629
  const int wkbtype = wkbType(w, &nZMFlag); /* Peak at the type number */
630

631
  switch (wkbtype) {
5,936✔
632
    /* Recurse into anonymous collections */
633
  case WKB_GEOMETRYCOLLECTION:
×
634
    return wkbConvCollectionToShape(w, shape);
×
635
    /* Handle area types */
636
  case WKB_POLYGON:
339✔
637
    return wkbConvPolygonToShape(w, shape);
339✔
638
  case WKB_MULTIPOLYGON:
327✔
639
    return wkbConvCollectionToShape(w, shape);
327✔
640
  case WKB_CURVEPOLYGON:
×
641
    return wkbConvCurvePolygonToShape(w, shape);
×
642
  case WKB_MULTISURFACE:
×
643
    return wkbConvCollectionToShape(w, shape);
×
644
  }
645

646
  /* We can't convert any of the following types into polygons */
647
  if (shape->type == MS_SHAPE_POLYGON)
5,270✔
648
    return MS_FAILURE;
649

650
  /* Handle linear types */
651
  switch (wkbtype) {
5,270✔
652
  case WKB_LINESTRING:
1,861✔
653
    return wkbConvLineStringToShape(w, shape);
1,861✔
654
  case WKB_CIRCULARSTRING:
×
655
    return wkbConvCircularStringToShape(w, shape);
×
656
  case WKB_COMPOUNDCURVE:
×
657
    return wkbConvCompoundCurveToShape(w, shape);
×
658
  case WKB_MULTILINESTRING:
1,848✔
659
    return wkbConvCollectionToShape(w, shape);
1,848✔
660
  case WKB_MULTICURVE:
×
661
    return wkbConvCollectionToShape(w, shape);
×
662
  }
663

664
  /* We can't convert any of the following types into lines */
665
  if (shape->type == MS_SHAPE_LINE)
1,561✔
666
    return MS_FAILURE;
667

668
  /* Handle point types */
669
  switch (wkbtype) {
1,561✔
670
  case WKB_POINT:
1,557✔
671
    return wkbConvPointToShape(w, shape);
1,557✔
672
  case WKB_MULTIPOINT:
4✔
673
    return wkbConvCollectionToShape(w, shape);
4✔
674
  }
675

676
  /* This is a WKB type we don't know about! */
677
  return MS_FAILURE;
678
}
679

680
/*
681
** What side of p1->p2 is q on?
682
*/
683
static inline int arcSegmentSide(const pointObj &p1, const pointObj &p2,
684
                                 const pointObj &q) {
685
  double side = ((q.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (q.y - p1.y));
×
686
  if (FP_EQ(side, 0.0)) {
×
687
    return FP_COLINEAR;
688
  } else {
689
    if (side < 0.0)
×
690
      return FP_LEFT;
691
    else
692
      return FP_RIGHT;
×
693
  }
694
}
695

696
/*
697
** Calculate the center of the circle defined by three points.
698
** Using matrix approach from
699
*http://mathforum.org/library/drmath/view/55239.html
700
*/
701
static int arcCircleCenter(const pointObj &p1, const pointObj &p2,
×
702
                           const pointObj &p3, pointObj *center,
703
                           double *radius) {
704
  pointObj c{}; // initialize
705
  double r;
706

707
  /* Circle is closed, so p2 must be opposite p1 & p3. */
708
  if ((fabs(p1.x - p3.x) < FP_EPSILON) && (fabs(p1.y - p3.y) < FP_EPSILON)) {
×
709
    c.x = p1.x + (p2.x - p1.x) / 2.0;
×
710
    c.y = p1.y + (p2.y - p1.y) / 2.0;
×
711
    r = sqrt(pow(c.x - p1.x, 2.0) + pow(c.y - p1.y, 2.0));
×
712
  }
713
  /* There is no circle here, the points are actually co-linear */
714
  else if (arcSegmentSide(p1, p3, p2) == FP_COLINEAR) {
715
    return MS_FAILURE;
716
  }
717
  /* Calculate the center and radius. */
718
  else {
719

720
    /* Radius */
721
    const double dx21 = p2.x - p1.x;
722
    const double dy21 = p2.y - p1.y;
723
    const double dx31 = p3.x - p1.x;
724
    const double dy31 = p3.y - p1.y;
725

726
    const double h21 = pow(dx21, 2.0) + pow(dy21, 2.0);
×
727
    const double h31 = pow(dx31, 2.0) + pow(dy31, 2.0);
×
728

729
    /* 2 * |Cross product|, d<0 means clockwise and d>0 counterclockwise
730
     * sweeping angle */
731
    const double d = 2 * (dx21 * dy31 - dx31 * dy21);
×
732

733
    c.x = p1.x + (h21 * dy31 - h31 * dy21) / d;
×
734
    c.y = p1.y - (h21 * dx31 - h31 * dx21) / d;
×
735
    r = sqrt(pow(c.x - p1.x, 2) + pow(c.y - p1.y, 2));
×
736
  }
737

738
  if (radius)
×
739
    *radius = r;
×
740
  if (center)
×
741
    *center = c;
×
742

743
  return MS_SUCCESS;
744
}
745

746
/*
747
** Write a stroked version of the circle defined by three points into a
748
** point buffer. The segment_angle (degrees) is the coverage of each stroke
749
*segment,
750
** and depending on whether this is the first arc in a circularstring,
751
** you might want to include_first
752
*/
753
static int arcStrokeCircle(const pointObj &p1, const pointObj &p2,
×
754
                           const pointObj &p3, double segment_angle,
755
                           int include_first, std::vector<pointObj> &pa) {
756
  const int side =
757
      arcSegmentSide(p1, p3, p2); /* What side of p1,p3 is the middle point? */
758
  int is_closed = MS_FALSE;
759

760
  /* We need to know if we're dealing with a circle early */
761
  if (FP_EQ(p1.x, p3.x) && FP_EQ(p1.y, p3.y))
×
762
    is_closed = MS_TRUE;
763

764
  /* Check if the "arc" is actually straight */
765
  if (!is_closed && side == FP_COLINEAR) {
×
766
    /* We just need to write in the end points */
767
    if (include_first)
×
768
      pointArrayAddPoint(pa, p1);
769
    pointArrayAddPoint(pa, p3);
770
    return MS_SUCCESS;
×
771
  }
772

773
  /* We should always be able to find the center of a non-linear arc */
774
  pointObj center; /* Center of our circular arc */
775
  double radius;   /* Radius of our circular arc */
776
  if (arcCircleCenter(p1, p2, p3, &center, &radius) == MS_FAILURE)
×
777
    return MS_FAILURE;
778

779
  /* Calculate the angles relative to center that our three points represent */
780
  const double a1 = atan2(p1.y - center.y, p1.x - center.x);
×
781
  /* UNUSED
782
  a2 = atan2(p2.y - center.y, p2.x - center.x);
783
   */
784
  const double a3 = atan2(p3.y - center.y, p3.x - center.x);
×
785
  double segment_angle_r =
×
786
      M_PI * segment_angle / 180.0; /* Segment angle in radians */
×
787

788
  double sweep_angle_r; /* Total angular size of our circular arc in radians */
789
  /* Closed-circle case, we sweep the whole circle! */
790
  if (is_closed) {
×
791
    sweep_angle_r = 2.0 * M_PI;
792
  }
793
  /* Clockwise sweep direction */
794
  else if (side == FP_LEFT) {
×
795
    if (a3 > a1) /* Wrapping past 180? */
×
796
      sweep_angle_r = a1 + (2.0 * M_PI - a3);
×
797
    else
798
      sweep_angle_r = a1 - a3;
×
799
  }
800
  /* Counter-clockwise sweep direction */
801
  else if (side == FP_RIGHT) {
×
802
    if (a3 > a1) /* Wrapping past 180? */
×
803
      sweep_angle_r = a3 - a1;
×
804
    else
805
      sweep_angle_r = a3 + (2.0 * M_PI - a1);
×
806
  } else
807
    sweep_angle_r = 0.0;
808

809
  /* We don't have enough resolution, let's invert our strategy. */
810
  if ((sweep_angle_r / segment_angle_r) < SEGMENT_MINPOINTS) {
×
811
    segment_angle_r = sweep_angle_r / (SEGMENT_MINPOINTS + 1);
×
812
  }
813

814
  /* We don't have enough resolution to stroke this arc,
815
   *  so just join the start to the end. */
816
  if (sweep_angle_r < segment_angle_r) {
×
817
    if (include_first)
×
818
      pointArrayAddPoint(pa, p1);
819
    pointArrayAddPoint(pa, p3);
820
    return MS_SUCCESS;
×
821
  }
822

823
  /* How many edges to generate (we add the final edge
824
   *  by sticking on the last point */
825
  int num_edges = floor(sweep_angle_r / fabs(segment_angle_r));
×
826

827
  /* Go backwards (negative angular steps) if we are stroking clockwise */
828
  if (side == FP_LEFT)
×
829
    segment_angle_r *= -1;
×
830

831
  /* What point should we start with? */
832
  double current_angle_r; /* What angle are we generating now (radians)? */
833
  if (include_first) {
×
834
    current_angle_r = a1;
835
  } else {
836
    current_angle_r = a1 + segment_angle_r;
×
837
    num_edges--;
×
838
  }
839

840
  /* For each edge, increment or decrement by our segment angle */
841
  for (int i = 0; i < num_edges; i++) {
×
842
    if (segment_angle_r > 0.0 && current_angle_r > M_PI)
×
843
      current_angle_r -= 2 * M_PI;
×
844
    if (segment_angle_r < 0.0 && current_angle_r < -1 * M_PI)
×
845
      current_angle_r -= 2 * M_PI;
×
846
    pointObj p;
847
    p.x = center.x + radius * cos(current_angle_r);
×
848
    p.y = center.y + radius * sin(current_angle_r);
×
849
    p.z = 0;
×
850
    p.m = 0;
×
851
    pointArrayAddPoint(pa, p);
852
    current_angle_r += segment_angle_r;
×
853
  }
854

855
  /* Add the last point */
856
  pointArrayAddPoint(pa, p3);
857
  return MS_SUCCESS;
858
}
859

860
/*
861
** This function does not actually take WKB as input, it takes the
862
** WKB starting from the numpoints integer. Each three-point edge
863
** is stroked into a linestring and appended into the lineObj
864
** argument.
865
*/
866
static int arcStrokeCircularString(wkbObj *w, double segment_angle,
×
867
                                   lineObj *line, int nZMFlag) {
868
  if (!w || !line)
×
869
    return MS_FAILURE;
870

871
  const int npoints = wkbReadInt(w);
872
  const int nedges = npoints / 2;
×
873

874
  /* All CircularStrings have an odd number of points */
875
  if (npoints < 3 || npoints % 2 != 1)
×
876
    return MS_FAILURE;
877

878
  /* Make a large guess at how much space we'll need */
879
  auto pa = pointArrayNew(nedges * 180 / segment_angle);
×
880

881
  pointObj p1, p2, p3;
882
  wkbReadPointP(w, &p3, nZMFlag);
×
883

884
  /* Fill out the point array with stroked arcs */
885
  int edge = 0;
886
  while (edge < nedges) {
×
887
    p1 = p3;
×
888
    wkbReadPointP(w, &p2, nZMFlag);
×
889
    wkbReadPointP(w, &p3, nZMFlag);
×
890
    if (arcStrokeCircle(p1, p2, p3, segment_angle, edge ? 0 : 1, pa) ==
×
891
        MS_FAILURE) {
892
      return MS_FAILURE;
893
    }
894
    edge++;
×
895
  }
896

897
  /* Copy the point array into the line */
898
  line->numpoints = static_cast<int>(pa.size());
×
899
  line->point = (pointObj *)msSmallMalloc(line->numpoints * sizeof(pointObj));
×
900
  memcpy(line->point, pa.data(), line->numpoints * sizeof(pointObj));
×
901

902
  return MS_SUCCESS;
×
903
}
×
904

905
/*
906
** For LAYER types that are not the usual ones (charts,
907
** annotations, etc) we will convert to a shape type
908
** that "makes sense" given the WKB input. We do this
909
** by peaking at the type number of the first collection
910
** sub-element.
911
*/
912
static int msPostGISFindBestType(wkbObj *w, shapeObj *shape) {
×
913
  /* What kind of geometry is this? */
914
  int nZMFlag;
915
  int wkbtype = wkbType(w, &nZMFlag);
916

917
  /* Generic collection, we need to look a little deeper. */
918
  if (wkbtype == WKB_GEOMETRYCOLLECTION)
×
919
    wkbtype = wkbCollectionSubType(w, &nZMFlag);
920

921
  switch (wkbtype) {
×
922
  case WKB_POLYGON:
×
923
  case WKB_CURVEPOLYGON:
924
  case WKB_MULTIPOLYGON:
925
    shape->type = MS_SHAPE_POLYGON;
×
926
    break;
×
927
  case WKB_LINESTRING:
×
928
  case WKB_CIRCULARSTRING:
929
  case WKB_COMPOUNDCURVE:
930
  case WKB_MULTICURVE:
931
  case WKB_MULTILINESTRING:
932
    shape->type = MS_SHAPE_LINE;
×
933
    break;
×
934
  case WKB_POINT:
×
935
  case WKB_MULTIPOINT:
936
    shape->type = MS_SHAPE_POINT;
×
937
    break;
×
938
  default:
939
    return MS_FAILURE;
940
  }
941

942
  return wkbConvGeometryToShape(w, shape);
×
943
}
944

945
/*
946
** Get the PostGIS version number from the database as integer.
947
** Versions are multiplied out as with PgSQL: 1.5.2 -> 10502, 2.0.0 -> 20000.
948
*/
949
static int msPostGISRetrieveVersion(PGconn *pgconn) {
554✔
950
  static const char *sql = "SELECT postgis_version()";
951
  if (!pgconn) {
554✔
952
    msSetError(MS_QUERYERR, "No open connection.",
×
953
               "msPostGISRetrieveVersion()");
954
    return MS_FAILURE;
×
955
  }
956

957
  PGresult *pgresult =
958
      PQexecParams(pgconn, sql, 0, nullptr, nullptr, nullptr, nullptr, 0);
554✔
959

960
  if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) {
554✔
961
    msDebug("Error executing SQL: (%s) in msPostGISRetrieveVersion()", sql);
×
962
    msSetError(MS_QUERYERR, "Error executing SQL. check server logs.",
×
963
               "msPostGISRetrieveVersion()");
964
    return MS_FAILURE;
×
965
  }
966

967
  if (PQgetisnull(pgresult, 0, 0)) {
554✔
968
    PQclear(pgresult);
×
969
    msSetError(MS_QUERYERR, "Null result returned.",
×
970
               "msPostGISRetrieveVersion()");
971
    return MS_FAILURE;
×
972
  }
973

974
  std::string strVersion = PQgetvalue(pgresult, 0, 0);
554✔
975
  PQclear(pgresult);
554✔
976

977
  char *ptr = &strVersion[0];
978
  char *strParts[3] = {nullptr, nullptr, nullptr};
554✔
979
  int j = 0;
980
  strParts[j++] = &strVersion[0];
554✔
981
  while (*ptr != '\0' && j < 3) {
2,216✔
982
    if (*ptr == '.') {
2,216✔
983
      *ptr = '\0';
554✔
984
      strParts[j++] = ptr + 1;
554✔
985
    }
986
    if (*ptr == ' ') {
2,216✔
987
      *ptr = '\0';
554✔
988
      break;
554✔
989
    }
990
    ptr++;
1,662✔
991
  }
992

993
  int version = 0;
994
  int factor = 10000;
995
  for (int i = 0; i < j; i++) {
1,662✔
996
    version += factor * atoi(strParts[i]);
1,108✔
997
    factor = factor / 100;
1,108✔
998
  }
999

1000
  return version;
1001
}
1002

1003
/*
1004
** Get the PostgreSQL server version number from the database as integer.
1005
** 12.7.1 ==> 120701
1006
*/
1007
static int msPostGISRetrievePostgreSQLVersion(PGconn *pgconn) {
1✔
1008
  static const char *sql = "SELECT version()";
1009
  if (!pgconn) {
1✔
1010
    msSetError(MS_QUERYERR, "No open connection.",
×
1011
               "msPostGISRetrievePostgreSQLVersion()");
1012
    return MS_FAILURE;
×
1013
  }
1014

1015
  PGresult *pgresult =
1016
      PQexecParams(pgconn, sql, 0, nullptr, nullptr, nullptr, nullptr, 0);
1✔
1017

1018
  if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) {
1✔
1019
    msDebug("Error executing SQL: (%s) in msPostGISRetrievePostgreSQLVersion()",
×
1020
            sql);
1021
    msSetError(MS_QUERYERR, "Error executing SQL. check server logs.",
×
1022
               "msPostGISRetrievePostgreSQLVersion()");
1023
    return MS_FAILURE;
×
1024
  }
1025

1026
  if (PQgetisnull(pgresult, 0, 0)) {
1✔
1027
    PQclear(pgresult);
×
1028
    msSetError(MS_QUERYERR, "Null result returned.",
×
1029
               "msPostGISRetrievePostgreSQLVersion()");
1030
    return MS_FAILURE;
×
1031
  }
1032

1033
  std::string strVersion = PQgetvalue(pgresult, 0, 0);
1✔
1034
  PQclear(pgresult);
1✔
1035

1036
  // Skip leading "PostgreSQL " (or other vendorized name)
1037
  auto nPos = strVersion.find(' ');
1✔
1038
  if (nPos == std::string::npos)
1✔
1039
    return 0;
1040

1041
  char *ptr = &strVersion[nPos + 1];
1✔
1042
  char *strParts[3] = {nullptr, nullptr, nullptr};
1✔
1043
  int j = 0;
1044
  strParts[j++] = ptr;
1✔
1045
  while (*ptr != '\0' && j < 3) {
6✔
1046
    if (*ptr == '.') {
6✔
1047
      *ptr = '\0';
1✔
1048
      strParts[j++] = ptr + 1;
1✔
1049
    }
1050
    if (*ptr == ' ') {
6✔
1051
      *ptr = '\0';
1✔
1052
      break;
1✔
1053
    }
1054
    ptr++;
5✔
1055
  }
1056

1057
  int version = 0;
1058
  int factor = 10000;
1059
  for (int i = 0; i < j; i++) {
3✔
1060
    version += factor * atoi(strParts[i]);
2✔
1061
    factor = factor / 100;
2✔
1062
  }
1063

1064
  return version;
1065
}
1066

1067
/*
1068
** msPostGISRetrievePK()
1069
**
1070
** Find out that the primary key is for this layer.
1071
** The layerinfo->fromsource must already be populated and
1072
** must not be a subquery.
1073
*/
1074
static int msPostGISRetrievePK(layerObj *layer) {
3✔
1075
  char *sql = nullptr;
1076

1077
  if (layer->debug) {
3✔
1078
    msDebug("msPostGISRetrievePK called.\n");
×
1079
  }
1080

1081
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
3✔
1082

1083
  if (layerinfo->pgconn == nullptr) {
3✔
1084
    msSetError(MS_QUERYERR, "Layer does not have a postgis connection.",
×
1085
               "msPostGISRetrievePK()");
1086
    return MS_FAILURE;
×
1087
  }
1088

1089
  {
1090
    /* Attempt to separate fromsource into schema.table */
1091
    std::string schema;
1092
    std::string table;
1093
    const auto pos_sep = layerinfo->fromsource.find('.');
3✔
1094
    if (pos_sep != std::string::npos) {
3✔
1095
      schema = layerinfo->fromsource.substr(0, pos_sep);
×
1096
      table = layerinfo->fromsource.substr(pos_sep + 1);
×
1097

1098
      if (layer->debug) {
×
1099
        msDebug("msPostGISRetrievePK(): Found schema %s, table %s.\n",
×
1100
                schema.c_str(), table.c_str());
1101
      }
1102
    }
1103
    /*
1104
    ** PostgreSQL v7.3 and later treat primary keys as constraints.
1105
    ** We only support single column primary keys, so multicolumn
1106
    ** pks are explicitly excluded from the query.
1107
    */
1108
    if (!schema.empty()) {
3✔
1109
      static const char *v73sql =
1110
          "select attname from pg_attribute, pg_constraint, pg_class, "
1111
          "pg_namespace where pg_constraint.conrelid = pg_class.oid and "
1112
          "pg_class.oid = pg_attribute.attrelid and pg_constraint.contype = "
1113
          "'p' and pg_constraint.conkey[1] = pg_attribute.attnum and "
1114
          "pg_class.relname = '%s' and pg_class.relnamespace = "
1115
          "pg_namespace.oid and pg_namespace.nspname = '%s' and "
1116
          "pg_constraint.conkey[2] is null";
1117
      const size_t nSize = schema.size() + table.size() + strlen(v73sql) + 1;
×
1118
      sql = (char *)msSmallMalloc(nSize);
×
1119
      snprintf(sql, nSize, v73sql, table.c_str(), schema.c_str());
×
1120
    } else {
1121
      static const char *v73sql =
1122
          "select attname from pg_attribute, pg_constraint, pg_class where "
1123
          "pg_constraint.conrelid = pg_class.oid and pg_class.oid = "
1124
          "pg_attribute.attrelid and pg_constraint.contype = 'p' and "
1125
          "pg_constraint.conkey[1] = pg_attribute.attnum and pg_class.relname "
1126
          "= '%s' and pg_table_is_visible(pg_class.oid) and "
1127
          "pg_constraint.conkey[2] is null";
1128
      const size_t nSize = layerinfo->fromsource.size() + strlen(v73sql) + 1;
3✔
1129
      sql = (char *)msSmallMalloc(nSize);
3✔
1130
      snprintf(sql, nSize, v73sql, layerinfo->fromsource.c_str());
3✔
1131
    }
1132
  }
1133

1134
  if (layer->debug > 1) {
3✔
1135
    msDebug("msPostGISRetrievePK: %s\n", sql);
×
1136
  }
1137

1138
  layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
3✔
1139

1140
  if (layerinfo->pgconn == nullptr) {
3✔
1141
    msSetError(MS_QUERYERR, "Layer does not have a postgis connection.",
×
1142
               "msPostGISRetrievePK()");
1143
    free(sql);
×
1144
    return MS_FAILURE;
×
1145
  }
1146

1147
  PGresult *pgresult = PQexecParams(layerinfo->pgconn, sql, 0, nullptr, nullptr,
3✔
1148
                                    nullptr, nullptr, 0);
1149
  if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) {
3✔
1150
    msSetError(MS_QUERYERR, "%s", "msPostGISRetrievePK()",
×
1151
               (std::string("Error executing SQL: ") + sql).c_str());
×
1152
    return MS_FAILURE;
×
1153
  }
1154

1155
  if (PQntuples(pgresult) < 1) {
3✔
1156
    if (layer->debug) {
1✔
1157
      msDebug("msPostGISRetrievePK: No results found.\n");
×
1158
    }
1159
    PQclear(pgresult);
1✔
1160
    free(sql);
1✔
1161
    return MS_FAILURE;
1✔
1162
  }
1163
  if (PQntuples(pgresult) > 1) {
2✔
1164
    if (layer->debug) {
×
1165
      msDebug("msPostGISRetrievePK: Multiple results found.\n");
×
1166
    }
1167
    PQclear(pgresult);
×
1168
    free(sql);
×
1169
    return MS_FAILURE;
×
1170
  }
1171

1172
  if (PQgetisnull(pgresult, 0, 0)) {
2✔
1173
    if (layer->debug) {
×
1174
      msDebug("msPostGISRetrievePK: Null result returned.\n");
×
1175
    }
1176
    PQclear(pgresult);
×
1177
    free(sql);
×
1178
    return MS_FAILURE;
×
1179
  }
1180

1181
  layerinfo->uid = PQgetvalue(pgresult, 0, 0);
2✔
1182

1183
  PQclear(pgresult);
2✔
1184
  free(sql);
2✔
1185
  return MS_SUCCESS;
2✔
1186
}
1187

1188
/*
1189
** msPostGISParseData()
1190
**
1191
** Parse the DATA string for geometry column name, table name,
1192
** unique id column, srid, and SQL string.
1193
*/
1194
static int msPostGISParseData(layerObj *layer) {
838✔
1195
  assert(layer != nullptr);
1196
  assert(layer->layerinfo != nullptr);
1197

1198
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)(layer->layerinfo);
838✔
1199

1200
  if (layer->debug) {
838✔
1201
    msDebug("msPostGISParseData called.\n");
×
1202
  }
1203

1204
  if (!layer->data) {
838✔
1205
    msSetError(
×
1206
        MS_QUERYERR,
1207
        "Missing DATA clause. DATA statement must contain 'geometry_column "
1208
        "from table_name' or 'geometry_column from (sub-query) as sub'.",
1209
        "msPostGISParseData()");
1210
    return MS_FAILURE;
×
1211
  }
1212

1213
  std::string data(layer->data);
838✔
1214
  for (char &ch : data) {
78,200✔
1215
    if (ch == '\t' || ch == '\r' || ch == '\n') {
77,362✔
1216
      ch = ' ';
20✔
1217
    }
1218
  }
1219

1220
  /*
1221
  ** Clean up any existing strings first, as we will be populating these fields.
1222
  */
1223
  layerinfo->srid.clear();
1224
  layerinfo->uid.clear();
1225
  layerinfo->geomcolumn.clear();
1226
  layerinfo->fromsource.clear();
1227

1228
  /*
1229
  ** Look for the optional ' using ' clauses.
1230
  */
1231
  const char *pos_srid = nullptr;
1232
  const char *pos_uid = nullptr;
1233
  const char *pos_use_1st = nullptr;
1234
  const char *pos_use_2nd = nullptr;
1235
  {
1236
    const char *tmp = strcasestr(data.c_str(), " using ");
838✔
1237
    while (tmp) {
2,432✔
1238
      pos_use_1st = pos_use_2nd;
1239
      pos_use_2nd = tmp + 1;
1,594✔
1240
      tmp = strcasestr(tmp + 1, " using ");
1,594✔
1241
    }
1242
  }
1243

1244
  /*
1245
  ** What clause appear after 2nd 'using', if set?
1246
  */
1247
  if (pos_use_2nd) {
838✔
1248
    const char *tmp;
1249
    for (tmp = pos_use_2nd + 5; *tmp == ' '; tmp++)
1,668✔
1250
      ;
1251
    if (strncasecmp(tmp, "unique ", 7) == 0)
834✔
1252
      for (pos_uid = tmp + 7; *pos_uid == ' '; pos_uid++)
444✔
1253
        ;
1254
    if (strncasecmp(tmp, "srid=", 5) == 0)
834✔
1255
      pos_srid = tmp + 5;
389✔
1256
  };
1257

1258
  /*
1259
  ** What clause appear after 1st 'using', if set?
1260
  */
1261
  if (pos_use_1st) {
838✔
1262
    const char *tmp;
1263
    for (tmp = pos_use_1st + 5; *tmp == ' '; tmp++)
1,520✔
1264
      ;
1265
    if (strncasecmp(tmp, "unique ", 7) == 0) {
760✔
1266
      if (pos_uid) {
387✔
1267
        msSetError(MS_QUERYERR,
×
1268
                   "Error parsing PostGIS DATA variable. Too many 'USING "
1269
                   "UNIQUE' found! %s",
1270
                   "msPostGISParseData()", layer->data);
1271
        return MS_FAILURE;
1272
      };
1273
      for (pos_uid = tmp + 7; *pos_uid == ' '; pos_uid++)
387✔
1274
        ;
1275
    };
1276
    if (strncasecmp(tmp, "srid=", 5) == 0) {
760✔
1277
      if (pos_srid) {
373✔
1278
        msSetError(MS_QUERYERR,
×
1279
                   "Error parsing PostGIS DATA variable. Too many 'USING SRID' "
1280
                   "found! %s",
1281
                   "msPostGISParseData()", layer->data);
1282
        return MS_FAILURE;
1283
      }
1284
      pos_srid = tmp + 5;
373✔
1285
    }
1286
  }
1287

1288
  /*
1289
  ** Look for the optional ' using unique ID' string first.
1290
  */
1291
  if (pos_uid) {
838✔
1292
    /* Find the end of this case 'using unique ftab_id using srid=33' */
1293
    const char *tmp = strstr(pos_uid, " ");
1294
    /* Find the end of this case 'using srid=33 using unique ftab_id' */
1295
    if (!tmp) {
831✔
1296
      tmp = pos_uid + strlen(pos_uid);
444✔
1297
    }
1298
    layerinfo->uid.assign(pos_uid, tmp - pos_uid);
831✔
1299
    msStringTrim(layerinfo->uid);
831✔
1300
  }
1301

1302
  /*
1303
  ** Look for the optional ' using srid=333 ' string next.
1304
  */
1305
  if (pos_srid) {
838✔
1306
    const int slength = strspn(pos_srid, "-0123456789");
762✔
1307
    if (!slength) {
762✔
1308
      msSetError(MS_QUERYERR,
1✔
1309
                 "Error parsing PostGIS DATA variable. You specified 'USING "
1310
                 "SRID' but didn't have any numbers! %s",
1311
                 "msPostGISParseData()", layer->data);
1312
      return MS_FAILURE;
1313
    } else {
1314
      layerinfo->srid.assign(pos_srid, slength);
761✔
1315
      msStringTrim(layerinfo->srid);
761✔
1316
    }
1317
  }
1318

1319
  /*
1320
   * This is a little hack so the rest of the code works.
1321
   * pos_opt should point to the start of the optional blocks.
1322
   *
1323
   * If they are both set, return the smaller one.
1324
   * If pos_use_1st set, then it smaller.
1325
   * If one or none is set, return the larger one.
1326
   */
1327
  const char *pos_opt = pos_use_1st ? pos_use_1st : pos_use_2nd;
837✔
1328
  /* No pos_opt? Move it to the end of the string. */
1329
  if (!pos_opt) {
78✔
1330
    pos_opt = data.c_str() + data.size();
4✔
1331
  }
1332
  /* Back after the last non-space character. */
1333
  while ((pos_opt > data.c_str()) && (*(pos_opt - 1) == ' ')) {
1,670✔
1334
    --pos_opt;
833✔
1335
  }
1336

1337
  /*
1338
  ** Scan for the 'geometry from table' or 'geometry from () as foo' clause.
1339
  */
1340

1341
  /* Find the first non-white character to start from */
1342
  const char *pos_geom;
1343
  for (pos_geom = data.c_str(); *pos_geom == ' '; pos_geom++) {
837✔
1344
  }
1345

1346
  /* Find the end of the geom column name */
1347
  const char *pos_scn = strcasestr(data.c_str(), " from ");
837✔
1348
  if (!pos_scn) {
837✔
1349
    msSetError(MS_QUERYERR,
1✔
1350
               "Error parsing PostGIS DATA variable. Must contain 'geometry "
1351
               "from table' or 'geometry from (subselect) as foo'. %s",
1352
               "msPostGISParseData()", layer->data);
1353
    return MS_FAILURE;
1354
  }
1355

1356
  /* Copy the geometry column name */
1357
  layerinfo->geomcolumn.assign(pos_geom, pos_scn - pos_geom);
836✔
1358
  msStringTrim(layerinfo->geomcolumn);
836✔
1359

1360
  /* Copy the table name or sub-select clause */
1361
  for (pos_scn += 6; *pos_scn == ' '; pos_scn++)
836✔
1362
    ;
1363
  if (pos_opt - pos_scn < 1) {
836✔
1364
    msSetError(MS_QUERYERR,
×
1365
               "Error parsing PostGIS DATA variable.  Must contain 'geometry "
1366
               "from table' or 'geometry from (subselect) as foo'. %s",
1367
               "msPostGISParseData()", layer->data);
1368
    return MS_FAILURE;
1369
  };
1370
  layerinfo->fromsource.assign(layer->data + (pos_scn - data.c_str()),
836✔
1371
                               pos_opt - pos_scn);
1372
  msStringTrim(layerinfo->fromsource);
836✔
1373

1374
  /* Something is wrong, our goemetry column and table references are not there.
1375
   */
1376
  if (layerinfo->fromsource.empty() || layerinfo->geomcolumn.empty()) {
836✔
1377
    msSetError(MS_QUERYERR,
×
1378
               "Error parsing PostGIS DATA variable.  Must contain 'geometry "
1379
               "from table' or 'geometry from (subselect) as foo'. %s",
1380
               "msPostGISParseData()", layer->data);
1381
    return MS_FAILURE;
1382
  }
1383

1384
  /*
1385
  ** We didn't find a ' using unique ' in the DATA string so try and find a
1386
  ** primary key on the table.
1387
  */
1388
  if (layerinfo->uid.empty()) {
836✔
1389
    if (strstr(layerinfo->fromsource.c_str(), " ")) {
6✔
1390
      msSetError(
3✔
1391
          MS_QUERYERR,
1392
          "Error parsing PostGIS DATA variable.  You must specify 'using "
1393
          "unique' when supplying a subselect in the data definition.",
1394
          "msPostGISParseData()");
1395
      return MS_FAILURE;
1396
    }
1397
    if (msPostGISRetrievePK(layer) != MS_SUCCESS) {
3✔
1398
      if (layerinfo->pgconn &&
2✔
1399
          msPostGISRetrievePostgreSQLVersion(layerinfo->pgconn) < 120000) {
1✔
1400
        /* For PostgreSQL < 12: No user specified unique id so we will use the
1401
         * PostgreSQL oid */
1402
        layerinfo->uid = "oid";
×
1403
      } else {
1404
        msSetError(MS_QUERYERR,
1✔
1405
                   "Error parsing PostGIS DATA variable. "
1406
                   "No primary key was found. "
1407
                   "You must specify 'using unique'.",
1408
                   "msPostGISParseData()");
1409
        return MS_FAILURE;
1410
      }
1411
    }
1412
  }
1413

1414
  if (layer->debug) {
832✔
1415
    msDebug("msPostGISParseData: unique_column=%s, srid=%s, "
×
1416
            "geom_column_name=%s, table_name=%s\n",
1417
            layerinfo->uid.c_str(), layerinfo->srid.c_str(),
1418
            layerinfo->geomcolumn.c_str(), layerinfo->fromsource.c_str());
1419
  }
1420
  return MS_SUCCESS;
1421
}
1422

1423
#if TRANSFER_ENCODING == 16
1424

1425
// This is dead code given current settings in mappostgis.h
1426

1427
/*
1428
** Decode a hex character.
1429
*/
1430
static const unsigned char msPostGISHexDecodeChar[256] = {
1431
    /* not Hex characters */
1432
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1433
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1434
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1435
    /* 0-9 */
1436
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
1437
    /* not Hex characters */
1438
    64, 64, 64, 64, 64, 64, 64,
1439
    /* A-F */
1440
    10, 11, 12, 13, 14, 15,
1441
    /* not Hex characters */
1442
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1443
    64, 64, 64, 64, 64, 64, 64,
1444
    /* a-f */
1445
    10, 11, 12, 13, 14, 15, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1446
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1447
    /* not Hex characters (upper 128 characters) */
1448
    64, 64, 64, 64, 64, 64, 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, 64, 64, 64, 64, 64, 64, 64,
1450
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
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};
1455

1456
/*
1457
** Decode hex string "src" (null terminated)
1458
** into "dest" (not null terminated).
1459
** Returns length of decoded array or 0 on failure.
1460
*/
1461
static int msPostGISHexDecode(unsigned char *dest, const char *src,
1462
                              int srclen) {
1463

1464
  if (src && *src && (srclen % 2 == 0)) {
1465

1466
    unsigned char *p = dest;
1467
    int i;
1468

1469
    for (i = 0; i < srclen; i += 2) {
1470
      const unsigned char c1 = src[i];
1471
      const unsigned char c2 = src[i + 1];
1472
      const unsigned char b1 = msPostGISHexDecodeChar[c1];
1473
      const unsigned char b2 = msPostGISHexDecodeChar[c2];
1474

1475
      *p++ = (b1 << 4) | b2;
1476
    }
1477
    return (p - dest);
1478
  }
1479
  return 0;
1480
}
1481

1482
/*
1483
** Decode a base64 character.
1484
*/
1485
static const unsigned char msPostGISBase64DecodeChar[256] = {
1486
    /* not Base64 characters */
1487
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1488
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1489
    64, 64, 64, 64, 64,
1490
    /*  +  */
1491
    62,
1492
    /* not Base64 characters */
1493
    64, 64, 64,
1494
    /*  /  */
1495
    63,
1496
    /* 0-9 */
1497
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
1498
    /* not Base64 characters */
1499
    64, 64, 64, 64, 64, 64, 64,
1500
    /* A-Z */
1501
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
1502
    21, 22, 23, 24, 25,
1503
    /* not Base64 characters */
1504
    64, 64, 64, 64, 64, 64,
1505
    /* a-z */
1506
    26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
1507
    45, 46, 47, 48, 49, 50, 51,
1508
    /* not Base64 characters */
1509
    64, 64, 64, 64, 64,
1510
    /* not Base64 characters (upper 128 characters) */
1511
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1512
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
1513
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
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};
1518

1519
/*
1520
** Decode base64 string "src" (null terminated)
1521
** into "dest" (not null terminated).
1522
** Returns length of decoded array or 0 on failure.
1523
*/
1524
static int msPostGISBase64Decode(unsigned char *dest, const char *src,
1525
                                 int srclen) {
1526

1527
  if (src && *src) {
1528

1529
    unsigned char *p = dest;
1530
    int i, j, k;
1531
    unsigned char *buf =
1532
        (unsigned char *)calloc(srclen + 1, sizeof(unsigned char));
1533

1534
    /* Drop illegal chars first */
1535
    for (i = 0, j = 0; src[i]; i++) {
1536
      unsigned char c = src[i];
1537
      if ((msPostGISBase64DecodeChar[c] != 64) || (c == '=')) {
1538
        buf[j++] = c;
1539
      }
1540
    }
1541

1542
    for (k = 0; k < j; k += 4) {
1543
      unsigned char c1 = 'A', c2 = 'A', c3 = 'A', c4 = 'A';
1544
      unsigned char b1 = 0, b2 = 0, b3 = 0, b4 = 0;
1545

1546
      c1 = buf[k];
1547

1548
      if (k + 1 < j) {
1549
        c2 = buf[k + 1];
1550
      }
1551
      if (k + 2 < j) {
1552
        c3 = buf[k + 2];
1553
      }
1554
      if (k + 3 < j) {
1555
        c4 = buf[k + 3];
1556
      }
1557

1558
      b1 = msPostGISBase64DecodeChar[c1];
1559
      b2 = msPostGISBase64DecodeChar[c2];
1560
      b3 = msPostGISBase64DecodeChar[c3];
1561
      b4 = msPostGISBase64DecodeChar[c4];
1562

1563
      *p++ = ((b1 << 2) | (b2 >> 4));
1564
      if (c3 != '=') {
1565
        *p++ = (((b2 & 0xf) << 4) | (b3 >> 2));
1566
      }
1567
      if (c4 != '=') {
1568
        *p++ = (((b3 & 0x3) << 6) | b4);
1569
      }
1570
    }
1571
    free(buf);
1572
    return (p - dest);
1573
  }
1574
  return 0;
1575
}
1576

1577
#endif
1578

1579
/*
1580
** msPostGISBuildSQLBox()
1581
**
1582
** Returns malloc'ed char* that must be freed by caller.
1583
*/
1584
static char *msPostGISBuildSQLBox(layerObj *layer, const rectObj *rect,
256✔
1585
                                  const char *strSRID) {
1586

1587
  char *strBox = nullptr;
1588
  size_t sz;
1589

1590
  if (layer->debug) {
256✔
1591
    msDebug("msPostGISBuildSQLBox called.\n");
×
1592
  }
1593

1594
  const bool bIsPoint = rect->minx == rect->maxx && rect->miny == rect->maxy;
256✔
1595

1596
  if (strSRID) {
256✔
1597
    static const char *strBoxTemplate =
1598
        "ST_GeomFromText('POLYGON((%.15g %.15g,%.15g %.15g,%.15g %.15g,%.15g "
1599
        "%.15g,%.15g %.15g))',%s)";
1600
    static const char *strBoxTemplatePoint =
1601
        "ST_GeomFromText('POINT(%.15g %.15g)',%s)";
1602
    /* 10 doubles + 1 integer + template characters */
1603
    sz = 10 * 22 + strlen(strSRID) + strlen(strBoxTemplate);
256✔
1604
    strBox = (char *)msSmallMalloc(sz + 1); /* add space for terminating NULL */
256✔
1605
    if ((bIsPoint && sz <= static_cast<size_t>(
9✔
1606
                               snprintf(strBox, sz, strBoxTemplatePoint,
9✔
1607
                                        rect->minx, rect->miny, strSRID))) ||
256✔
1608
        (!bIsPoint &&
247✔
1609
         sz <= static_cast<size_t>(snprintf(
247✔
1610
                   strBox, sz, strBoxTemplate, rect->minx, rect->miny,
1611
                   rect->minx, rect->maxy, rect->maxx, rect->maxy, rect->maxx,
247✔
1612
                   rect->miny, rect->minx, rect->miny, strSRID)))) {
247✔
1613
      msSetError(MS_MISCERR, "Bounding box digits truncated.",
×
1614
                 "msPostGISBuildSQLBox");
1615
      return nullptr;
×
1616
    }
1617
  } else {
1618
    static const char *strBoxTemplate =
1619
        "ST_GeomFromText('POLYGON((%.15g %.15g,%.15g %.15g,%.15g %.15g,%.15g "
1620
        "%.15g,%.15g %.15g))')";
1621
    static const char *strBoxTemplatePoint =
1622
        "ST_GeomFromText('POINT(%.15g %.15g)')";
1623
    /* 10 doubles + template characters */
1624
    sz = 10 * 22 + strlen(strBoxTemplate);
×
1625
    strBox = (char *)msSmallMalloc(sz + 1); /* add space for terminating NULL */
×
1626
    if ((bIsPoint &&
×
1627
         sz <= static_cast<size_t>(snprintf(strBox, sz, strBoxTemplatePoint,
×
1628
                                            rect->minx, rect->miny))) ||
×
1629
        (!bIsPoint &&
×
1630
         sz <= static_cast<size_t>(
×
1631
                   snprintf(strBox, sz, strBoxTemplate, rect->minx, rect->miny,
×
1632
                            rect->minx, rect->maxy, rect->maxx, rect->maxy,
×
1633
                            rect->maxx, rect->miny, rect->minx, rect->miny)))) {
×
1634
      msSetError(MS_MISCERR, "Bounding box digits truncated.",
×
1635
                 "msPostGISBuildSQLBox");
1636
      return nullptr;
×
1637
    }
1638
  }
1639

1640
  return strBox;
1641
}
1642

1643
/*
1644
** msPostGISBuildSQLItems()
1645
**
1646
** Returns malloc'ed char* that must be freed by caller.
1647
*/
1648
static std::string msPostGISBuildSQLItems(layerObj *layer) {
243✔
1649

1650
  const char *strEndian = nullptr;
1651
  if (layer->debug) {
243✔
1652
    msDebug("msPostGISBuildSQLItems called.\n");
×
1653
  }
1654

1655
  assert(layer->layerinfo != nullptr);
1656

1657
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
243✔
1658

1659
  if (layerinfo->geomcolumn.empty()) {
243✔
1660
    msSetError(MS_MISCERR, "layerinfo->geomcolumn is not initialized.",
×
1661
               "msPostGISBuildSQLItems()");
1662
    return std::string();
1663
  }
1664

1665
  /*
1666
  ** Get the server to transform the geometry into our
1667
  ** native endian before transmitting it to us..
1668
  */
1669
  if (layerinfo->endian == LITTLE_ENDIAN) {
243✔
1670
    strEndian = "NDR";
1671
  } else {
1672
    strEndian = "XDR";
1673
  }
1674

1675
  std::string strGeom;
1676
  {
1677
    /*
1678
    ** We transfer the geometry from server to client as a
1679
    ** hex or base64 encoded WKB byte-array. We will have to decode this
1680
    ** data once we get it. Forcing to 2D (via the AsBinary function
1681
    ** which includes a 2D force in it) removes ordinates we don't
1682
    ** need, saving transfer and encode/decode time.
1683
    */
1684
    const char *force2d = "";
1685
#if TRANSFER_ENCODING == 64
1686
    const char *strGeomTemplate =
1687
        "encode(ST_AsBinary(%s(\"%s\"),'%s'),'base64') as geom,\"%s\"";
1688
#elif TRANSFER_ENCODING == 256
1689
    const char *strGeomTemplate =
1690
        "ST_AsBinary(%s(\"%s\"),'%s') as geom,\"%s\"::text";
1691
#else
1692
    const char *strGeomTemplate =
1693
        "encode(ST_AsBinary(%s(\"%s\"),'%s'),'hex') as geom,\"%s\"";
1694
#endif
1695
    if (layerinfo->force2d) {
243✔
1696
      if (layerinfo->version >= 20100)
×
1697
        force2d = "ST_Force2D";
1698
      else
1699
        force2d = "ST_Force_2D";
1700
    } else if (layerinfo->version < 20000) {
243✔
1701
      /* Use AsEWKB() to get 3D */
1702
#if TRANSFER_ENCODING == 64
1703
      strGeomTemplate =
1704
          "encode(AsEWKB(%s(\"%s\"),'%s'),'base64') as geom,\"%s\"";
1705
#elif TRANSFER_ENCODING == 256
1706
      strGeomTemplate = "AsEWKB(%s(\"%s\"),'%s') as geom,\"%s\"::text";
1707
#else
1708
      strGeomTemplate = "encode(AsEWKB(%s(\"%s\"),'%s'),'hex') as geom,\"%s\"";
1709
#endif
1710
    }
1711
    strGeom.resize(strlen(strGeomTemplate) + strlen(force2d) +
243✔
1712
                   strlen(strEndian) + layerinfo->geomcolumn.size() +
243✔
1713
                   layerinfo->uid.size());
1714
    snprintf(&strGeom[0], strGeom.size(), strGeomTemplate, force2d,
1715
             layerinfo->geomcolumn.c_str(), strEndian, layerinfo->uid.c_str());
1716
    strGeom.resize(strlen(strGeom.data()));
243✔
1717
  }
1718

1719
  if (layer->debug > 1) {
243✔
1720
    msDebug("msPostGISBuildSQLItems: %d items requested.\n", layer->numitems);
×
1721
  }
1722

1723
  /*
1724
  ** Not requesting items? We just need geometry and unique id.
1725
  */
1726
  std::string strItems;
1727
  /*
1728
  ** Build SQL to pull all the items.
1729
  */
1730
  for (int t = 0; t < layer->numitems; t++) {
1,932✔
1731
    strItems += "\"";
1732
    strItems += layer->items[t];
1,689✔
1733
#if TRANSFER_ENCODING == 256
1734
    strItems += "\"::text,";
1735
#else
1736
    strItems += "\",";
1737
#endif
1738
  }
1739
  strItems += strGeom;
1740

1741
  return strItems;
243✔
1742
}
1743

1744
/*
1745
** msPostGISFindTableName()
1746
*/
1747
static std::string msPostGISFindTableName(const char *fromsource) {
31✔
1748
  std::string f_table_name;
1749
  const char *pos = strchr(fromsource, ' ');
1750

1751
  if (!pos) {
31✔
1752
    /* target table is one word */
1753
    f_table_name = fromsource;
1754
  } else {
1755
    /* target table is hiding in sub-select clause */
1756
    pos = strcasestr(fromsource, " from ");
31✔
1757
    if (pos) {
31✔
1758
      pos += 6; /* should be start of table name */
31✔
1759
      const char *pos_paren = strstr(pos, ")"); /* first ) after table name */
1760
      const char *pos_space =
1761
          strstr(pos, " "); /* first space after table name */
1762
      if (pos_space < pos_paren) {
31✔
1763
        /* found space first */
1764
        f_table_name.assign(pos, pos_space - pos);
31✔
1765
      } else {
1766
        /* found ) first */
1767
        f_table_name.assign(pos, pos_paren - pos);
×
1768
      }
1769
    }
1770
  }
1771
  return f_table_name;
31✔
1772
}
1773

1774
/*
1775
** msPostGISBuildSQLSRID()
1776
*/
1777
static std::string msPostGISBuildSQLSRID(layerObj *layer) {
244✔
1778

1779
  std::string strSRID;
1780

1781
  if (layer->debug) {
244✔
1782
    msDebug("msPostGISBuildSQLSRID called.\n");
×
1783
  }
1784

1785
  assert(layer->layerinfo != nullptr);
1786

1787
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
244✔
1788

1789
  /* An SRID was already provided in the DATA line. */
1790
  if (!layerinfo->srid.empty()) {
244✔
1791
    strSRID = layerinfo->srid;
215✔
1792
    if (layer->debug > 1) {
215✔
1793
      msDebug("msPostGISBuildSQLSRID: SRID provided (%s)\n", strSRID.c_str());
×
1794
    }
1795
  }
1796
  /*
1797
  ** No SRID in data line, so extract target table from the 'fromsource'.
1798
  ** Either of form "thetable" (one word) or "(select ... from thetable)"
1799
  ** or "(select ... from thetable where ...)".
1800
  */
1801
  else {
1802
    if (layer->debug > 1) {
29✔
1803
      msDebug("msPostGISBuildSQLSRID: Building find_srid line.\n");
×
1804
    }
1805

1806
    strSRID = "find_srid('','";
1807
    strSRID += msPostGISFindTableName(layerinfo->fromsource.c_str());
58✔
1808
    strSRID += "','";
1809
    strSRID += layerinfo->geomcolumn;
1810
    strSRID += "')";
1811
  }
1812
  return strSRID;
244✔
1813
}
1814

1815
/*
1816
** msPostGISReplaceBoxToken()
1817
**
1818
** Convert a fromsource data statement into something usable by replacing the
1819
*!BOX! token.
1820
*/
1821
static std::string msPostGISReplaceBoxToken(layerObj *layer,
622✔
1822
                                            const rectObj *rect,
1823
                                            const char *fromsource) {
1824
  std::string result(fromsource);
622✔
1825

1826
  if (layer->debug > 1) {
622✔
1827
    msDebug("msPostGISReplaceBoxToken called.\n");
×
1828
  }
1829

1830
  if (strstr(fromsource, BOXTOKEN) && rect) {
622✔
1831
    char *strBox = nullptr;
1832

1833
    /* We see to set the SRID on the box, but to what SRID? */
1834
    const std::string strSRID = msPostGISBuildSQLSRID(layer);
1✔
1835
    if (strSRID.empty()) {
1✔
1836
      return std::string();
1837
    }
1838

1839
    /* Create a suitable SQL string from the rectangle and SRID. */
1840
    strBox = msPostGISBuildSQLBox(layer, rect, strSRID.c_str());
1✔
1841
    if (!strBox) {
1✔
1842
      msSetError(MS_MISCERR, "Unable to build box SQL.",
×
1843
                 "msPostGISReplaceBoxToken()");
1844
      return std::string();
1845
    }
1846

1847
    /* Do the substitution. */
1848
    size_t pos = 0;
1849
    while (true) {
1850
      pos = result.find(BOXTOKEN, pos);
1851
      if (pos == std::string::npos) {
2✔
1852
        break;
1853
      }
1854
      const auto resultAfter(result.substr(pos + BOXTOKENLENGTH));
1✔
1855
      result.resize(pos);
1856
      result += strBox;
1857
      result += resultAfter;
1858
    }
1✔
1859

1860
    free(strBox);
1✔
1861
  }
1862
  return result;
622✔
1863
}
1864

1865
/*
1866
** msPostGISBuildSQLFrom()
1867
*/
1868
static std::string msPostGISBuildSQLFrom(layerObj *layer, const rectObj *rect) {
243✔
1869
  if (layer->debug) {
243✔
1870
    msDebug("msPostGISBuildSQLFrom called.\n");
×
1871
  }
1872

1873
  assert(layer->layerinfo != nullptr);
1874

1875
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
243✔
1876

1877
  if (layerinfo->fromsource.empty()) {
243✔
1878
    msSetError(MS_MISCERR, "Layerinfo->fromsource is not initialized.",
×
1879
               "msPostGISBuildSQLFrom()");
1880
    return std::string();
1881
  }
1882

1883
  /*
1884
  ** If there's a '!BOX!' in our source we need to substitute the
1885
  ** current rectangle for it...
1886
  */
1887
  return msPostGISReplaceBoxToken(layer, rect, layerinfo->fromsource.c_str());
243✔
1888
}
1889

1890
/*
1891
** msPostGISBuildSQLWhere()
1892
*/
1893
static std::string msPostGISBuildSQLWhere(layerObj *layer, const rectObj *rect,
243✔
1894
                                          const long *uid,
1895
                                          const rectObj *rectInOtherSRID,
1896
                                          int otherSRID) {
1897
  if (layer->debug) {
243✔
1898
    msDebug("msPostGISBuildSQLWhere called.\n");
×
1899
  }
1900

1901
  assert(layer->layerinfo != nullptr);
1902

1903
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
243✔
1904

1905
  if (layerinfo->fromsource.empty()) {
243✔
1906
    msSetError(MS_MISCERR, "Layerinfo->fromsource is not initialized.",
×
1907
               "msPostGISBuildSQLWhere()");
1908
    return std::string();
1909
  }
1910

1911
  const rectObj rectInvalid = MS_INIT_INVALID_RECT;
243✔
1912
  bool bIsValidRect =
1913
      (rect && memcmp(rect, &rectInvalid, sizeof(rectInvalid)) != 0);
243✔
1914

1915
  /* Populate strRect, if necessary. */
1916
  std::string strRect;
1917
  if (bIsValidRect) {
243✔
1918

1919
    if (rect && !layerinfo->geomcolumn.empty()) {
238✔
1920
      /* We see to set the SRID on the box, but to what SRID? */
1921
      const std::string strSRID = msPostGISBuildSQLSRID(layer);
238✔
1922
      if (strSRID.empty()) {
238✔
1923
        return std::string();
1924
      }
1925

1926
      char *strBox = msPostGISBuildSQLBox(layer, rect, strSRID.c_str());
238✔
1927

1928
      if (!strBox) {
238✔
1929
        msSetError(MS_MISCERR, "Unable to build box SQL.",
×
1930
                   "msPostGISBuildSQLWhere()");
1931
        return std::string();
1932
      }
1933

1934
      if (strSRID.find("find_srid(") == std::string::npos) {
238✔
1935
        // If the SRID is known, we can safely use ST_Intersects()
1936
        // otherwise if find_srid() would return 0, ST_Intersects() would not
1937
        // work at all, which breaks the msautotest/query/query_postgis.map
1938
        // tests, related to bdry_counpy2 layer that has no SRID
1939
        if (layerinfo->version >= 20500) {
209✔
1940
          strRect = "ST_Intersects(\"";
1941
          strRect += layerinfo->geomcolumn;
1942
          strRect += "\", ";
1943
          strRect += strBox;
1944
          strRect += ')';
1945
        } else {
1946
          // ST_Intersects() before PostGIS 2.5 doesn't support collections
1947
          // See
1948
          // https://github.com/MapServer/MapServer/pull/6355#issuecomment-877355007
1949
          strRect = "(\"";
1950
          strRect += layerinfo->geomcolumn;
1951
          strRect += "\" && ";
1952
          strRect += strBox;
1953
          strRect += ") AND ST_Distance(\"";
1954
          strRect += layerinfo->geomcolumn;
1955
          strRect += "\", ";
1956
          strRect += strBox;
1957
          strRect += ") = 0";
1958
        }
1959
      } else {
1960
        strRect = '"';
1961
        strRect += layerinfo->geomcolumn;
1962
        strRect += "\" && ";
1963
        strRect += strBox;
1964
      }
1965
      free(strBox);
238✔
1966

1967
      /* Combine with other rectangle  expressed in another SRS */
1968
      /* (generally equivalent to the above in current code paths) */
1969
      if (rectInOtherSRID != nullptr && otherSRID > 0) {
238✔
1970
        strBox = msPostGISBuildSQLBox(layer, rectInOtherSRID,
12✔
1971
                                      std::to_string(otherSRID).c_str());
12✔
1972
        if (!strBox) {
12✔
1973
          msSetError(MS_MISCERR, "Unable to build box SQL.",
×
1974
                     "msPostGISBuildSQLWhere()");
1975
          return std::string();
×
1976
        }
1977

1978
        std::string strRectOtherSRID = "ST_Intersects(ST_Transform(\"";
12✔
1979
        strRectOtherSRID += layerinfo->geomcolumn;
1980
        strRectOtherSRID += "\",";
1981
        strRectOtherSRID += std::to_string(otherSRID);
24✔
1982
        strRectOtherSRID += "),";
1983
        strRectOtherSRID += strBox;
1984
        strRectOtherSRID += ')';
1985

1986
        free(strBox);
12✔
1987

1988
        std::string strTmp = "((";
12✔
1989
        strTmp += strRect;
1990
        strTmp += ") AND ";
1991
        strTmp += strRectOtherSRID;
1992
        strTmp += ')';
1993

1994
        strRect = std::move(strTmp);
12✔
1995
      } else if (rectInOtherSRID != nullptr && otherSRID < 0) {
226✔
1996
        const std::string strSRID = msPostGISBuildSQLSRID(layer);
5✔
1997
        if (strSRID.empty()) {
5✔
1998
          return std::string();
1999
        }
2000
        char *strBox =
2001
            msPostGISBuildSQLBox(layer, rectInOtherSRID, strSRID.c_str());
5✔
2002

2003
        if (!strBox) {
5✔
2004
          msSetError(MS_MISCERR, "Unable to build box SQL.",
×
2005
                     "msPostGISBuildSQLWhere()");
2006
          return std::string();
2007
        }
2008

2009
        std::string strRectOtherSRID = "ST_Intersects(\"";
5✔
2010
        strRectOtherSRID += layerinfo->geomcolumn;
2011
        strRectOtherSRID += "\",";
2012
        strRectOtherSRID += strBox;
2013
        strRectOtherSRID += ')';
2014

2015
        free(strBox);
5✔
2016

2017
        std::string strTmp = "((";
5✔
2018
        strTmp += strRect;
2019
        strTmp += ") AND ";
2020
        strTmp += strRectOtherSRID;
2021
        strTmp += ')';
2022

2023
        strRect = std::move(strTmp);
5✔
2024
      }
2025
    }
2026
  }
2027

2028
  std::string strWhere;
2029
  if (bIsValidRect && !strRect.empty()) {
243✔
2030
    strWhere += strRect;
2031
  }
2032

2033
  /* Handle a translated filter (RFC91). */
2034
  if (layer->filter.native_string) {
243✔
2035
    if (!strWhere.empty()) {
149✔
2036
      strWhere += " AND ";
2037
    }
2038
    strWhere += '(';
2039
    strWhere += layer->filter.native_string;
149✔
2040
    strWhere += ')';
2041
  }
2042

2043
  /* Handle a native filter set as a PROCESSING option (#5001). */
2044
  const char *native_filter = msLayerGetProcessingKey(layer, "NATIVE_FILTER");
243✔
2045
  if (native_filter) {
243✔
2046
    if (!strWhere.empty()) {
7✔
2047
      strWhere += " AND ";
2048
    }
2049

2050
    strWhere += '(';
2051
    strWhere += native_filter;
2052
    strWhere += ')';
2053
  }
2054

2055
  if (uid) {
243✔
2056
    if (!strWhere.empty()) {
2✔
2057
      strWhere += " AND ";
2058
    }
2059

2060
    bool is_numeric = msLayerPropertyIsNumeric(layer, layerinfo->uid.c_str());
2✔
2061
    if (is_numeric) {
2✔
2062
      strWhere += layerinfo->uid;
2063
      strWhere += " = ";
2064
      strWhere += *uid;
×
2065
    } else {
2066
      strWhere += '"';
2067
      strWhere += layerinfo->uid;
2068
      strWhere += "\" = ";
2069
      strWhere += std::to_string(*uid);
4✔
2070
    }
2071
  }
2072

2073
  if (strWhere.empty()) {
243✔
2074
    // return all records
2075
    strWhere = "true";
2076
  }
2077

2078
  if (layer->sortBy.nProperties > 0) {
243✔
2079
    char *pszTmp = msLayerBuildSQLOrderBy(layer);
1✔
2080
    strWhere += " ORDER BY ";
2081
    strWhere += pszTmp;
2082
    msFree(pszTmp);
1✔
2083
  }
2084

2085
  if (layerinfo->paging && layer->maxfeatures >= 0) {
243✔
2086
    strWhere += " LIMIT ";
2087
    strWhere += std::to_string(layer->maxfeatures);
46✔
2088
  }
2089

2090
  /* Populate strOffset, if necessary. */
2091
  if (layerinfo->paging && layer->startindex > 0) {
243✔
2092
    strWhere += " OFFSET ";
2093
    strWhere += std::to_string(layer->startindex - 1);
8✔
2094
  }
2095

2096
  return strWhere;
243✔
2097
}
2098

2099
/*
2100
** msPostGISBuildSQL()
2101
**
2102
** rect is the search rectangle in layer SRS. It can be set to NULL
2103
** uid can be set to NULL
2104
** rectInOtherSRID is an additional rectangle potentially in another SRS. It can
2105
*be set to NULL.
2106
** Only used if rect != NULL
2107
** otherSRID is the SRID of the additional rectangle. It can be set to -1 if
2108
** rectInOtherSRID is in the SRID of the layer.
2109
*/
2110
static std::string msPostGISBuildSQL(layerObj *layer, const rectObj *rect,
243✔
2111
                                     const long *uid,
2112
                                     const rectObj *rectInOtherSRID,
2113
                                     int otherSRID) {
2114
  if (layer->debug) {
243✔
2115
    msDebug("msPostGISBuildSQL called.\n");
×
2116
  }
2117

2118
  assert(layer->layerinfo != nullptr);
2119

2120
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
243✔
2121

2122
  const std::string strItems = msPostGISBuildSQLItems(layer);
243✔
2123
  if (strItems.empty()) {
243✔
2124
    msSetError(MS_MISCERR, "Failed to build SQL items.", "msPostGISBuildSQL()");
×
2125
    return std::string();
2126
  }
2127

2128
  const std::string strFrom = msPostGISBuildSQLFrom(layer, rect);
243✔
2129
  if (strFrom.empty()) {
243✔
2130
    msSetError(MS_MISCERR, "Failed to build SQL 'from'.",
×
2131
               "msPostGISBuildSQL()");
2132
    return std::string();
2133
  }
2134

2135
  /* If there's BOX hackery going on, we don't want to append a box index test
2136
     at the end of the query, the user is going to be responsible for making
2137
     things work with their hackery. */
2138
  std::string strWhere;
2139
  if (strstr(layerinfo->fromsource.c_str(), BOXTOKEN))
243✔
2140
    strWhere =
2141
        msPostGISBuildSQLWhere(layer, nullptr, uid, rectInOtherSRID, otherSRID);
2✔
2142
  else
2143
    strWhere =
2144
        msPostGISBuildSQLWhere(layer, rect, uid, rectInOtherSRID, otherSRID);
484✔
2145

2146
  if (strWhere.empty()) {
243✔
2147
    msSetError(MS_MISCERR, "Failed to build SQL 'where'.",
×
2148
               "msPostGISBuildSQL()");
2149
    return std::string();
2150
  }
2151

2152
  std::string sql("SELECT ");
243✔
2153
  sql += strItems;
2154
  sql += " FROM ";
2155
  sql += strFrom;
2156
  sql += " WHERE ";
2157
  sql += strWhere;
2158

2159
  return sql;
243✔
2160
}
2161

2162
#define wkbstaticsize 4096
2163
static int msPostGISReadShape(layerObj *layer, shapeObj *shape) {
3,745✔
2164
  if (layer->debug) {
3,745✔
2165
    msDebug("msPostGISReadShape called.\n");
×
2166
  }
2167

2168
  assert(layer->layerinfo != nullptr);
2169
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
3,745✔
2170

2171
  /* Retrieve the geometry. */
2172
  const char *wkbstr =
2173
      PQgetvalue(layerinfo->pgresult, layerinfo->rownum, layer->numitems);
3,745✔
2174
  const int wkbstrlen =
2175
      PQgetlength(layerinfo->pgresult, layerinfo->rownum, layer->numitems);
3,745✔
2176

2177
  if (!wkbstr || wkbstrlen == 0) {
3,745✔
2178
    msSetError(MS_QUERYERR, "WKB returned is null!", "msPostGISReadShape()");
×
2179
    return MS_FAILURE;
×
2180
  }
2181

2182
  unsigned char wkbstatic[wkbstaticsize];
2183
  unsigned char *wkb = nullptr;
2184
  if (wkbstrlen > wkbstaticsize) {
3,745✔
2185
    wkb =
2186
        static_cast<unsigned char *>(calloc(wkbstrlen, sizeof(unsigned char)));
111✔
2187
  } else {
2188
    wkb = wkbstatic;
2189
  }
2190

2191
  wkbObj w;
2192
  int result = 0;
2193
#if TRANSFER_ENCODING == 64
2194
  result = msPostGISBase64Decode(wkb, wkbstr, wkbstrlen - 1);
2195
  w.size = (wkbstrlen - 1) / 2;
2196
  if (!result) {
2197
    if (wkb != wkbstatic)
2198
      free(wkb);
2199
    return MS_FAILURE;
2200
  }
2201
#elif TRANSFER_ENCODING == 256
2202
  memcpy(wkb, wkbstr, wkbstrlen);
3,745✔
2203
  w.size = wkbstrlen;
3,745✔
2204
#else
2205
  result = msPostGISHexDecode(wkb, wkbstr, wkbstrlen);
2206
  w.size = (wkbstrlen - 1) / 2;
2207
  if (!result) {
2208
    if (wkb != wkbstatic)
2209
      free(wkb);
2210
    return MS_FAILURE;
2211
  }
2212
#endif
2213

2214
  /* Initialize our wkbObj */
2215
  w.wkb = (char *)wkb;
3,745✔
2216
  w.ptr = w.wkb;
3,745✔
2217

2218
  /* Set the type map according to what version of PostGIS we are dealing with
2219
   */
2220
  if (layerinfo->version >= 20000) /* PostGIS 2.0+ */
3,745✔
2221
    w.typemap = wkb_postgis20;
3,745✔
2222
  else {
2223
    w.typemap = wkb_postgis15;
×
2224
    if (layerinfo->force2d == MS_FALSE) {
×
2225
      /* Is there SRID ? Skip it */
2226
      if (w.size >= 9 && (w.ptr[4] & 0x20) != 0) {
×
2227
        w.ptr[5] = w.ptr[1];
×
2228
        w.ptr[6] = w.ptr[2];
×
2229
        w.ptr[7] = w.ptr[3];
×
2230
        w.ptr[8] = w.ptr[4] & ~(0x20);
×
2231
        w.ptr[4] = 1;
×
2232
        w.ptr += 4;
×
2233
        w.size -= 4;
×
2234
      }
2235
    }
2236
  }
2237

2238
  switch (layer->type) {
3,745✔
2239

2240
  case MS_LAYER_POINT:
1,574✔
2241
    shape->type = MS_SHAPE_POINT;
1,574✔
2242
    result = wkbConvGeometryToShape(&w, shape);
1,574✔
2243
    break;
2244

2245
  case MS_LAYER_LINE:
1,857✔
2246
    shape->type = MS_SHAPE_LINE;
1,857✔
2247
    result = wkbConvGeometryToShape(&w, shape);
1,857✔
2248
    break;
2249

2250
  case MS_LAYER_POLYGON:
314✔
2251
    shape->type = MS_SHAPE_POLYGON;
314✔
2252
    result = wkbConvGeometryToShape(&w, shape);
314✔
2253
    break;
2254

2255
  case MS_LAYER_QUERY:
×
2256
  case MS_LAYER_CHART:
2257
    result = msPostGISFindBestType(&w, shape);
×
2258
    break;
2259

2260
  case MS_LAYER_RASTER:
×
2261
    msDebug("Ignoring MS_LAYER_RASTER in msPostGISReadShape.\n");
×
2262
    break;
2263

2264
  case MS_LAYER_CIRCLE:
×
2265
    msDebug("Ignoring MS_LAYER_RASTER in msPostGISReadShape.\n");
×
2266
    break;
2267

2268
  default:
×
2269
    msDebug("Unsupported layer type in msPostGISReadShape()!\n");
×
2270
    break;
2271
  }
2272

2273
  /* All done with WKB geometry, free it! */
2274
  if (wkb != wkbstatic)
3,745✔
2275
    free(wkb);
111✔
2276

2277
  if (result != MS_FAILURE) {
3,745✔
2278
    /* Found a drawable shape, so now retrieve the attributes. */
2279

2280
    shape->values = (char **)msSmallMalloc(sizeof(char *) * layer->numitems);
3,745✔
2281
    for (int t = 0; t < layer->numitems; t++) {
16,225✔
2282
      const int size = PQgetlength(layerinfo->pgresult, layerinfo->rownum, t);
12,480✔
2283
      const char *val = PQgetvalue(layerinfo->pgresult, layerinfo->rownum, t);
12,480✔
2284
      const int isnull = PQgetisnull(layerinfo->pgresult, layerinfo->rownum, t);
12,480✔
2285
      if (isnull) {
12,480✔
2286
        shape->values[t] = msStrdup("");
2,935✔
2287
      } else {
2288
        shape->values[t] = (char *)msSmallMalloc(size + 1);
9,545✔
2289
        memcpy(shape->values[t], val, size);
9,545✔
2290
        shape->values[t][size] = '\0'; /* null terminate it */
9,545✔
2291

2292
        // From https://www.postgresql.org/docs/9.0/datatype-character.html
2293
        // fields of type Char are blank padded, but this blank is semantically
2294
        // insignificant, so let's trim it
2295
        if (PQftype(layerinfo->pgresult, t) == CHAROID)
9,545✔
2296
          msStringTrimBlanks(shape->values[t]);
×
2297
      }
2298
      if (layer->debug > 4) {
12,480✔
2299
        msDebug("msPostGISReadShape: PQgetlength = %d\n", size);
×
2300
      }
2301
      if (layer->debug > 1) {
12,480✔
2302
        msDebug("msPostGISReadShape: [%s] \"%s\"\n", layer->items[t],
×
2303
                shape->values[t]);
×
2304
      }
2305
    }
2306

2307
    /* layer->numitems is the geometry, layer->numitems+1 is the uid */
2308
    const char *tmp =
2309
        PQgetvalue(layerinfo->pgresult, layerinfo->rownum, layer->numitems + 1);
3,745✔
2310
    long uid = 0;
2311
    if (tmp) {
3,745✔
2312
      uid = strtol(tmp, nullptr, 10);
3,745✔
2313
    }
2314
    if (layer->debug > 4) {
3,745✔
2315
      msDebug("msPostGISReadShape: Setting shape->index = %ld\n", uid);
×
2316
      msDebug("msPostGISReadShape: Setting shape->resultindex = %ld\n",
×
2317
              layerinfo->rownum);
2318
    }
2319
    shape->index = uid;
3,745✔
2320
    shape->resultindex = layerinfo->rownum;
3,745✔
2321

2322
    if (layer->debug > 2) {
3,745✔
2323
      msDebug("msPostGISReadShape: [index] %ld\n", shape->index);
×
2324
    }
2325

2326
    shape->numvalues = layer->numitems;
3,745✔
2327

2328
    msComputeBounds(shape);
3,745✔
2329
  } else {
2330
    shape->type = MS_SHAPE_NULL;
×
2331
  }
2332

2333
  if (layer->debug > 2) {
3,745✔
2334
    char *tmp = msShapeToWKT(shape);
×
2335
    msDebug("msPostGISReadShape: [shape] %s\n", tmp);
×
2336
    free(tmp);
×
2337
  }
2338

2339
  return MS_SUCCESS;
2340
}
2341

2342
#endif /* USE_POSTGIS */
2343

2344
/*
2345
** msPostGISLayerOpen()
2346
**
2347
** Registered vtable->LayerOpen function.
2348
*/
2349
static int msPostGISLayerOpen(layerObj *layer) {
625✔
2350
#ifdef USE_POSTGIS
2351
  assert(layer != nullptr);
2352

2353
  if (layer->debug) {
625✔
2354
    msDebug("msPostGISLayerOpen called: %s\n", layer->data);
×
2355
  }
2356

2357
  if (layer->layerinfo) {
625✔
2358
    if (layer->debug) {
69✔
2359
      msDebug("msPostGISLayerOpen: Layer is already open!\n");
×
2360
    }
2361
    return MS_SUCCESS; /* already open */
69✔
2362
  }
2363

2364
  if (!layer->data) {
556✔
2365
    msSetError(MS_QUERYERR, "Nothing specified in DATA statement.",
1✔
2366
               "msPostGISLayerOpen()");
2367
    return MS_FAILURE;
1✔
2368
  }
2369

2370
  /*
2371
  ** Initialize the layerinfo
2372
  **/
2373
  msPostGISLayerInfo *layerinfo = msPostGISCreateLayerInfo();
555✔
2374

2375
  int order_test = 1;
2376
  if (((char *)&order_test)[0] == 1) {
2377
    layerinfo->endian = LITTLE_ENDIAN;
555✔
2378
  } else {
2379
    layerinfo->endian = BIG_ENDIAN;
2380
  }
2381

2382
  /*
2383
  ** Get a database connection from the pool.
2384
  */
2385
  layerinfo->pgconn = (PGconn *)msConnPoolRequest(layer);
555✔
2386

2387
  /* No connection in the pool, so set one up. */
2388
  if (!layerinfo->pgconn) {
555✔
2389
    if (layer->debug) {
530✔
2390
      msDebug(
×
2391
          "msPostGISLayerOpen: No connection in pool, creating a fresh one.\n");
2392
    }
2393

2394
    if (!layer->connection) {
530✔
2395
      msSetError(MS_MISCERR, "Missing CONNECTION keyword.",
×
2396
                 "msPostGISLayerOpen()");
2397
      delete layerinfo;
×
2398
      return MS_FAILURE;
×
2399
    }
2400

2401
    /*
2402
    ** Decrypt any encrypted token in connection string and attempt to connect.
2403
    */
2404
    char *conn_decrypted = msDecryptStringTokens(layer->map, layer->connection);
530✔
2405
    if (conn_decrypted == nullptr) {
530✔
2406
      delete layerinfo;
×
2407
      return MS_FAILURE; /* An error should already have been produced */
×
2408
    }
2409
    layerinfo->pgconn = PQconnectdb(conn_decrypted);
530✔
2410
    msFree(conn_decrypted);
530✔
2411
    conn_decrypted = nullptr;
2412

2413
    /*
2414
    ** Connection failed, return error message with passwords ***ed out.
2415
    */
2416
    if (!layerinfo->pgconn || PQstatus(layerinfo->pgconn) == CONNECTION_BAD) {
530✔
2417
      if (layer->debug)
1✔
2418
        msDebug("msPostGISLayerOpen: Connection failure.\n");
×
2419

2420
      msDebug("Database connection failed (%s) with connect string '%s'\nIs "
1✔
2421
              "the database running? Is it allowing connections? Does the "
2422
              "specified user exist? Is the password valid? Is the database on "
2423
              "the standard port? in msPostGISLayerOpen()",
2424
              PQerrorMessage(layerinfo->pgconn), layer->connection);
1✔
2425
      msSetError(MS_QUERYERR,
1✔
2426
                 "Database connection failed. Check server logs for more "
2427
                 "details.Is the database running? Is it allowing connections? "
2428
                 "Does the specified user exist? Is the password valid? Is the "
2429
                 "database on the standard port?",
2430
                 "msPostGISLayerOpen()");
2431

2432
      if (layerinfo->pgconn)
1✔
2433
        PQfinish(layerinfo->pgconn);
1✔
2434
      delete layerinfo;
1✔
2435
      return MS_FAILURE;
1✔
2436
    }
2437

2438
    /* Register to receive notifications from the database. */
2439
    PQsetNoticeProcessor(layerinfo->pgconn, postresqlNoticeHandler,
529✔
2440
                         (void *)layer);
2441

2442
    /* Save this connection in the pool for later. */
2443
    msConnPoolRegister(layer, layerinfo->pgconn, msPostGISCloseConnection);
529✔
2444
  } else {
2445
    /* Connection in the pool should be tested to see if backend is alive. */
2446
    if (PQstatus(layerinfo->pgconn) != CONNECTION_OK) {
25✔
2447
      /* Uh oh, bad connection. Can we reset it? */
2448
      PQreset(layerinfo->pgconn);
×
2449
      if (PQstatus(layerinfo->pgconn) != CONNECTION_OK) {
×
2450
        /* Nope, time to bail out. */
2451
        msSetError(MS_QUERYERR,
×
2452
                   "PostgreSQL database connection. Check server logs for more "
2453
                   "details",
2454
                   "msPostGISLayerOpen()");
2455
        msDebug("PostgreSQL database connection gone bad (%s) in "
×
2456
                "msPostGISLayerOpen()",
2457
                PQerrorMessage(layerinfo->pgconn));
×
2458
        delete layerinfo;
×
2459
        /* FIXME: we should also release the connection from the pool in this
2460
         * case, but it is stale... for the time being we do not release it so
2461
         * it can never be used again. If this happens multiple times there will
2462
         * be a leak... */
2463
        return MS_FAILURE;
×
2464
      }
2465
    }
2466
  }
2467

2468
  /* Get the PostGIS version number from the database */
2469
  layerinfo->version = msPostGISRetrieveVersion(layerinfo->pgconn);
554✔
2470
  if (layerinfo->version == MS_FAILURE) {
554✔
2471
    msConnPoolRelease(layer, layerinfo->pgconn);
×
2472
    delete layerinfo;
×
2473
    return MS_FAILURE;
×
2474
  }
2475
  if (layer->debug)
554✔
2476
    msDebug("msPostGISLayerOpen: Got PostGIS version %d.\n",
×
2477
            layerinfo->version);
2478

2479
  const char *force2d_processing = msLayerGetProcessingKey(layer, "FORCE2D");
554✔
2480
  if (force2d_processing && !strcasecmp(force2d_processing, "no")) {
554✔
2481
    layerinfo->force2d = MS_FALSE;
×
2482
  } else if (force2d_processing && !strcasecmp(force2d_processing, "yes")) {
×
2483
    layerinfo->force2d = MS_TRUE;
×
2484
  }
2485
  if (layer->debug)
554✔
2486
    msDebug("msPostGISLayerOpen: Forcing 2D geometries: %s.\n",
×
2487
            (layerinfo->force2d) ? "yes" : "no");
×
2488

2489
  /* Save the layerinfo in the layerObj. */
2490
  layer->layerinfo = (void *)layerinfo;
554✔
2491

2492
  return MS_SUCCESS;
554✔
2493
#else
2494
  msSetError(MS_MISCERR, "PostGIS support is not available.",
2495
             "msPostGISLayerOpen()");
2496
  return MS_FAILURE;
2497
#endif
2498
}
2499

2500
/*
2501
** msPostGISLayerClose()
2502
**
2503
** Registered vtable->LayerClose function.
2504
*/
2505
static int msPostGISLayerClose(layerObj *layer) {
609✔
2506
#ifdef USE_POSTGIS
2507

2508
  if (layer->debug) {
609✔
2509
    msDebug("msPostGISLayerClose called: %s\n", layer->data);
×
2510
  }
2511

2512
  if (layer->layerinfo) {
609✔
2513
    msPostGISFreeLayerInfo(layer);
554✔
2514
  }
2515

2516
  return MS_SUCCESS;
609✔
2517
#else
2518
  msSetError(MS_MISCERR, "PostGIS support is not available.",
2519
             "msPostGISLayerClose()");
2520
  return MS_FAILURE;
2521
#endif
2522
}
2523

2524
/*
2525
** msPostGISLayerIsOpen()
2526
**
2527
** Registered vtable->LayerIsOpen function.
2528
*/
2529
static int msPostGISLayerIsOpen(layerObj *layer) {
1,757✔
2530
#ifdef USE_POSTGIS
2531

2532
  if (layer->debug) {
2,094✔
2533
    msDebug("msPostGISLayerIsOpen called.\n");
×
2534
  }
2535

2536
  if (layer->layerinfo)
2,094✔
2537
    return MS_TRUE;
2538
  else
2539
    return MS_FALSE;
1,556✔
2540
#else
2541
  msSetError(MS_MISCERR, "PostGIS support is not available.",
2542
             "msPostGISLayerIsOpen()");
2543
  return MS_FAILURE;
2544
#endif
2545
}
2546

2547
/*
2548
** msPostGISLayerFreeItemInfo()
2549
**
2550
** Registered vtable->LayerFreeItemInfo function.
2551
*/
2552
static void msPostGISLayerFreeItemInfo(layerObj *layer) {
1,230✔
2553
#ifdef USE_POSTGIS
2554
  if (layer->debug) {
1,230✔
2555
    msDebug("msPostGISLayerFreeItemInfo called.\n");
×
2556
  }
2557

2558
  if (layer->iteminfo) {
1,230✔
2559
    free(layer->iteminfo);
487✔
2560
  }
2561
  layer->iteminfo = nullptr;
1,230✔
2562
#endif
2563
}
1,230✔
2564

2565
/*
2566
** msPostGISLayerInitItemInfo()
2567
**
2568
** Registered vtable->LayerInitItemInfo function.
2569
** Our iteminfo is list of indexes from 1..numitems.
2570
*/
2571
static int msPostGISLayerInitItemInfo(layerObj *layer) {
614✔
2572
#ifdef USE_POSTGIS
2573
  if (layer->debug) {
614✔
2574
    msDebug("msPostGISLayerInitItemInfo called.\n");
×
2575
  }
2576

2577
  if (layer->numitems == 0) {
614✔
2578
    return MS_SUCCESS;
2579
  }
2580

2581
  if (layer->iteminfo) {
614✔
2582
    free(layer->iteminfo);
127✔
2583
  }
2584

2585
  layer->iteminfo = msSmallMalloc(sizeof(int) * layer->numitems);
614✔
2586
  if (!layer->iteminfo) {
614✔
2587
    msSetError(MS_MEMERR, "Out of memory.", "msPostGISLayerInitItemInfo()");
×
2588
    return MS_FAILURE;
×
2589
  }
2590

2591
  int *itemindexes = (int *)layer->iteminfo;
2592
  for (int i = 0; i < layer->numitems; i++) {
7,277✔
2593
    itemindexes[i] =
6,663✔
2594
        i; /* Last item is always the geometry. The rest are non-geometry. */
2595
  }
2596

2597
  return MS_SUCCESS;
2598
#else
2599
  msSetError(MS_MISCERR, "PostGIS support is not available.",
2600
             "msPostGISLayerInitItemInfo()");
2601
  return MS_FAILURE;
2602
#endif
2603
}
2604

2605
#ifdef USE_POSTGIS
2606
static std::vector<const char *> buildBindValues(layerObj *layer) {
690✔
2607
  /* try to get the first bind value */
2608
  const char *bind_value = msLookupHashTable(&layer->bindvals, "1");
690✔
2609
  std::vector<const char *> layer_bind_values;
2610
  while (bind_value != nullptr) {
695✔
2611
    /* put the bind value on the stack */
2612
    layer_bind_values.push_back(bind_value);
5✔
2613
    /* get the bind_value */
2614
    bind_value = msLookupHashTable(
5✔
2615
        &layer->bindvals,
2616
        std::to_string(static_cast<size_t>(layer_bind_values.size()) + 1)
10✔
2617
            .c_str());
2618
  }
2619
  return layer_bind_values;
690✔
2620
}
×
2621

2622
static PGresult *runPQexecParamsWithBindSubstitution(layerObj *layer,
690✔
2623
                                                     const char *strSQL,
2624
                                                     int binary) {
2625
  PGresult *pgresult = nullptr;
2626
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
690✔
2627

2628
  const auto layer_bind_values = buildBindValues(layer);
690✔
2629

2630
  if (!layer_bind_values.empty()) {
690✔
2631
    pgresult = PQexecParams(layerinfo->pgconn, strSQL,
5✔
2632
                            static_cast<int>(layer_bind_values.size()), nullptr,
2633
                            layer_bind_values.data(), nullptr, nullptr, binary);
2634
  } else {
2635
    pgresult = PQexecParams(layerinfo->pgconn, strSQL, 0, nullptr, nullptr,
685✔
2636
                            nullptr, nullptr, binary);
2637
  }
2638

2639
  return pgresult;
690✔
2640
}
690✔
2641
#endif
2642

2643
/*
2644
** msPostGISLayerWhichShapes()
2645
**
2646
** Registered vtable->LayerWhichShapes function.
2647
*/
2648
// cppcheck-suppress passedByValue
2649
static int msPostGISLayerWhichShapes(layerObj *layer, rectObj rect,
222✔
2650
                                     int isQuery) {
2651
  (void)isQuery;
2652
#ifdef USE_POSTGIS
2653
  assert(layer != nullptr);
2654
  assert(layer->layerinfo != nullptr);
2655

2656
  if (layer->debug) {
222✔
2657
    msDebug("msPostGISLayerWhichShapes called.\n");
×
2658
  }
2659

2660
  /* Fill out layerinfo with our current DATA state. */
2661
  if (msPostGISParseData(layer) != MS_SUCCESS) {
222✔
2662
    return MS_FAILURE;
2663
  }
2664

2665
  /*
2666
  ** This comes *after* parsedata, because parsedata fills in
2667
  ** layer->layerinfo.
2668
  */
2669
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
222✔
2670

2671
  /* Build a SQL query based on our current state. */
2672
  const std::string strSQL =
2673
      msPostGISBuildSQL(layer, &rect, nullptr, nullptr, -1);
222✔
2674
  if (strSQL.empty()) {
222✔
2675
    msSetError(MS_QUERYERR, "Failed to build query SQL.",
×
2676
               "msPostGISLayerWhichShapes()");
2677
    return MS_FAILURE;
2678
  }
2679

2680
  if (layer->debug) {
222✔
2681
    msDebug("msPostGISLayerWhichShapes query: %s\n", strSQL.c_str());
×
2682
  }
2683

2684
  PGresult *pgresult = runPQexecParamsWithBindSubstitution(
222✔
2685
      layer, strSQL.c_str(), RESULTSET_TYPE);
2686

2687
  if (layer->debug > 1) {
222✔
2688
    msDebug("msPostGISLayerWhichShapes query status: %s (%d)\n",
×
2689
            PQresStatus(PQresultStatus(pgresult)), PQresultStatus(pgresult));
×
2690
  }
2691

2692
  /* Something went wrong. */
2693
  if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) {
222✔
2694
    msDebug("msPostGISLayerWhichShapes(): Error (%s) executing query: %s\n",
1✔
2695
            PQerrorMessage(layerinfo->pgconn), strSQL.c_str());
1✔
2696
    msSetError(MS_QUERYERR, "Error executing query. Check server logs",
1✔
2697
               "msPostGISLayerWhichShapes()");
2698
    if (pgresult) {
1✔
2699
      PQclear(pgresult);
1✔
2700
    }
2701
    return MS_FAILURE;
1✔
2702
  }
2703

2704
  if (layer->debug) {
221✔
2705
    msDebug("msPostGISLayerWhichShapes got %d records in result.\n",
×
2706
            PQntuples(pgresult));
2707
  }
2708

2709
  /* Clean any existing pgresult before storing current one. */
2710
  if (layerinfo->pgresult)
221✔
2711
    PQclear(layerinfo->pgresult);
×
2712
  layerinfo->pgresult = pgresult;
221✔
2713

2714
  layerinfo->sql = strSQL;
221✔
2715

2716
  layerinfo->rownum = 0;
221✔
2717

2718
  return MS_SUCCESS;
221✔
2719
#else
2720
  msSetError(MS_MISCERR, "PostGIS support is not available.",
2721
             "msPostGISLayerWhichShapes()");
2722
  return MS_FAILURE;
2723
#endif
2724
}
2725

2726
/*
2727
** msPostGISLayerNextShape()
2728
**
2729
** Registered vtable->LayerNextShape function.
2730
*/
2731
static int msPostGISLayerNextShape(layerObj *layer, shapeObj *shape) {
3,626✔
2732
#ifdef USE_POSTGIS
2733
  if (layer->debug) {
3,626✔
2734
    msDebug("msPostGISLayerNextShape called.\n");
×
2735
  }
2736

2737
  assert(layer != nullptr);
2738
  assert(layer->layerinfo != nullptr);
2739

2740
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
3,626✔
2741

2742
  shape->type = MS_SHAPE_NULL;
3,626✔
2743

2744
  /*
2745
  ** Roll through pgresult until we hit non-null shape (usually right away).
2746
  */
2747
  while (shape->type == MS_SHAPE_NULL) {
3,626✔
2748
    if (layerinfo->rownum < PQntuples(layerinfo->pgresult)) {
3,626✔
2749
      /* Retrieve this shape, cursor access mode. */
2750
      msPostGISReadShape(layer, shape);
3,418✔
2751
      if (shape->type != MS_SHAPE_NULL) {
3,418✔
2752
        (layerinfo->rownum)++; /* move to next shape */
3,418✔
2753
        return MS_SUCCESS;
3,418✔
2754
      } else {
2755
        (layerinfo->rownum)++; /* move to next shape */
×
2756
      }
2757
    } else {
2758
      return MS_DONE;
2759
    }
2760
  }
2761

2762
  /* Found nothing, clean up and exit. */
2763
  msFreeShape(shape);
×
2764

2765
  return MS_FAILURE;
×
2766
#else
2767
  msSetError(MS_MISCERR, "PostGIS support is not available.",
2768
             "msPostGISLayerNextShape()");
2769
  return MS_FAILURE;
2770
#endif
2771
}
2772

2773
/*
2774
** msPostGISLayerGetShape()
2775
**
2776
*/
2777
// cppcheck-suppress passedByValue
2778
static int msPostGISLayerGetShapeCount(layerObj *layer, rectObj rect,
19✔
2779
                                       projectionObj *rectProjection) {
2780
#ifdef USE_POSTGIS
2781
  int rectSRID = -1;
2782
  rectObj searchrectInLayerProj = rect;
19✔
2783
  const rectObj rectInvalid = MS_INIT_INVALID_RECT;
19✔
2784
  bool bIsValidRect = memcmp(&rect, &rectInvalid, sizeof(rect)) != 0;
19✔
2785

2786
  assert(layer != nullptr);
2787
  assert(layer->layerinfo != nullptr);
2788

2789
  if (layer->debug) {
19✔
2790
    msDebug("msPostGISLayerGetShapeCount called.\n");
×
2791
  }
2792

2793
  // Special processing if the specified projection for the rect is different
2794
  // from the layer projection We want to issue a WHERE that includes
2795
  // ((the_geom && rect_reprojected_in_layer_SRID) AND NOT
2796
  // ST_Disjoint(ST_Transform(the_geom, rect_SRID), rect))
2797
  if (bIsValidRect &&
19✔
2798
      (rectProjection != NULL && layer->project &&
34✔
2799
       msProjectionsDiffer(&(layer->projection), rectProjection))) {
17✔
2800
    // If we cannot guess the EPSG code of the rectProjection, we cannot
2801
    // use ST_Transform, so fallback on slow implementation
2802
    if (rectProjection->numargs < 1 ||
12✔
2803
        strncasecmp(rectProjection->args[0],
12✔
2804
                    "init=epsg:", strlen("init=epsg:")) != 0) {
2805
      if (layer->debug) {
×
2806
        msDebug("msPostGISLayerGetShapeCount(): cannot find EPSG code of "
×
2807
                "rectProjection. Falling back on client-side feature count.\n");
2808
      }
2809
      return LayerDefaultGetShapeCount(layer, rect, rectProjection);
×
2810
    }
2811

2812
    // Reproject the passed rect into the layer projection and get
2813
    // the SRID from the rectProjection
2814
    msProjectRect(
12✔
2815
        rectProjection, &(layer->projection),
2816
        &searchrectInLayerProj); /* project the searchrect to source coords */
2817
    rectSRID = atoi(rectProjection->args[0] + strlen("init=epsg:"));
12✔
2818
  }
2819

2820
  msLayerTranslateFilter(layer, &layer->filter, layer->filteritem);
19✔
2821

2822
  /* Fill out layerinfo with our current DATA state. */
2823
  if (msPostGISParseData(layer) != MS_SUCCESS) {
19✔
2824
    return -1;
2825
  }
2826

2827
  /*
2828
  ** This comes *after* parsedata, because parsedata fills in
2829
  ** layer->layerinfo.
2830
  */
2831
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
19✔
2832

2833
  /* Build a SQL query based on our current state. */
2834
  const std::string strSQL = msPostGISBuildSQL(layer, &searchrectInLayerProj,
2835
                                               nullptr, &rect, rectSRID);
19✔
2836
  if (strSQL.empty()) {
19✔
2837
    msSetError(MS_QUERYERR, "Failed to build query SQL.",
×
2838
               "msPostGISLayerGetShapeCount()");
2839
    return -1;
2840
  }
2841

2842
  std::string strSQLCount = "SELECT COUNT(*) FROM (";
19✔
2843
  strSQLCount += strSQL;
2844
  strSQLCount += ") msQuery";
2845

2846
  if (layer->debug) {
19✔
2847
    msDebug("msPostGISLayerGetShapeCount query: %s\n", strSQLCount.c_str());
×
2848
  }
2849

2850
  PGresult *pgresult =
2851
      runPQexecParamsWithBindSubstitution(layer, strSQLCount.c_str(), 0);
19✔
2852

2853
  if (layer->debug > 1) {
19✔
2854
    msDebug("msPostGISLayerWhichShapes query status: %s (%d)\n",
×
2855
            PQresStatus(PQresultStatus(pgresult)), PQresultStatus(pgresult));
×
2856
  }
2857

2858
  /* Something went wrong. */
2859
  if (!pgresult || PQresultStatus(pgresult) != PGRES_TUPLES_OK) {
19✔
2860
    msDebug("msPostGISLayerGetShapeCount(): Error (%s) executing query: %s. "
×
2861
            "Falling back to client-side evaluation\n",
2862
            PQerrorMessage(layerinfo->pgconn), strSQLCount.c_str());
×
2863
    if (pgresult) {
×
2864
      PQclear(pgresult);
×
2865
    }
2866
    return LayerDefaultGetShapeCount(layer, rect, rectProjection);
×
2867
  }
2868

2869
  const int nCount = atoi(PQgetvalue(pgresult, 0, 0));
19✔
2870

2871
  if (layer->debug) {
19✔
2872
    msDebug("msPostGISLayerWhichShapes return: %d.\n", nCount);
×
2873
  }
2874
  PQclear(pgresult);
19✔
2875

2876
  return nCount;
2877
#else
2878
  msSetError(MS_MISCERR, "PostGIS support is not available.",
2879
             "msPostGISLayerGetShapeCount()");
2880
  return -1;
2881
#endif
2882
}
2883

2884
/*
2885
** msPostGISLayerGetShape()
2886
**
2887
** Registered vtable->LayerGetShape function. For pulling from a prepared and
2888
** undisposed result set.
2889
*/
2890
static int msPostGISLayerGetShape(layerObj *layer, shapeObj *shape,
327✔
2891
                                  resultObj *record) {
2892
#ifdef USE_POSTGIS
2893

2894
  long shapeindex = record->shapeindex;
327✔
2895
  int resultindex = record->resultindex;
327✔
2896

2897
  assert(layer != nullptr);
2898
  assert(layer->layerinfo != nullptr);
2899

2900
  if (layer->debug) {
327✔
2901
    msDebug("msPostGISLayerGetShape called for record = %i\n", resultindex);
×
2902
  }
2903

2904
  /* If resultindex is set, fetch the shape from the resultcache, otherwise
2905
   * fetch it from the DB  */
2906
  if (resultindex >= 0) {
327✔
2907
    msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
325✔
2908

2909
    /* Check the validity of the open result. */
2910
    PGresult *pgresult = layerinfo->pgresult;
325✔
2911
    if (!pgresult) {
325✔
2912
      msSetError(MS_MISCERR, "PostgreSQL result set is null.",
×
2913
                 "msPostGISLayerGetShape()");
2914
      return MS_FAILURE;
×
2915
    }
2916
    ExecStatusType status = PQresultStatus(pgresult);
325✔
2917
    if (layer->debug > 1) {
325✔
2918
      msDebug("msPostGISLayerGetShape query status: %s (%d)\n",
×
2919
              PQresStatus(status), (int)status);
2920
    }
2921
    if (!(status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK)) {
325✔
2922
      msSetError(MS_MISCERR, "PostgreSQL result set is not ready.",
×
2923
                 "msPostGISLayerGetShape()");
2924
      return MS_FAILURE;
×
2925
    }
2926

2927
    /* Check the validity of the requested record number. */
2928
    if (resultindex >= PQntuples(pgresult)) {
325✔
2929
      msDebug("msPostGISLayerGetShape got request for (%d) but only has %d "
×
2930
              "tuples.\n",
2931
              resultindex, PQntuples(pgresult));
2932
      msSetError(MS_MISCERR, "Got request larger than result set.",
×
2933
                 "msPostGISLayerGetShape()");
2934
      return MS_FAILURE;
×
2935
    }
2936

2937
    layerinfo->rownum = resultindex; /* Only return one result. */
325✔
2938

2939
    /* We don't know the shape type until we read the geometry. */
2940
    shape->type = MS_SHAPE_NULL;
325✔
2941

2942
    /* Return the shape, cursor access mode. */
2943
    msPostGISReadShape(layer, shape);
325✔
2944

2945
    return (shape->type == MS_SHAPE_NULL) ? MS_FAILURE : MS_SUCCESS;
650✔
2946
  } else { /* no resultindex, fetch the shape from the DB */
2947
    int num_tuples;
2948

2949
    /* Fill out layerinfo with our current DATA state. */
2950
    if (msPostGISParseData(layer) != MS_SUCCESS) {
2✔
2951
      return MS_FAILURE;
2952
    }
2953

2954
    /*
2955
    ** This comes *after* parsedata, because parsedata fills in
2956
    ** layer->layerinfo.
2957
    */
2958
    msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
2✔
2959

2960
    /* Build a SQL query based on our current state. */
2961
    const std::string strSQL =
2962
        msPostGISBuildSQL(layer, nullptr, &shapeindex, nullptr, -1);
2✔
2963
    if (strSQL.empty()) {
2✔
2964
      msSetError(MS_QUERYERR, "Failed to build query SQL.",
×
2965
                 "msPostGISLayerGetShape()");
2966
      return MS_FAILURE;
2967
    }
2968

2969
    if (layer->debug) {
2✔
2970
      msDebug("msPostGISLayerGetShape query: %s\n", strSQL.c_str());
×
2971
    }
2972

2973
    PGresult *pgresult = runPQexecParamsWithBindSubstitution(
2✔
2974
        layer, strSQL.c_str(), RESULTSET_TYPE);
2975

2976
    /* Something went wrong. */
2977
    if ((!pgresult) || (PQresultStatus(pgresult) != PGRES_TUPLES_OK)) {
2✔
2978
      msDebug("msPostGISLayerGetShape(): Error (%s) executing SQL: %s\n",
×
2979
              PQerrorMessage(layerinfo->pgconn), strSQL.c_str());
×
2980
      msSetError(MS_QUERYERR, "Error executing SQL. Check server logs.",
×
2981
                 "msPostGISLayerGetShape()");
2982

2983
      if (pgresult) {
×
2984
        PQclear(pgresult);
×
2985
      }
2986
      return MS_FAILURE;
×
2987
    }
2988

2989
    /* Clean any existing pgresult before storing current one. */
2990
    if (layerinfo->pgresult)
2✔
2991
      PQclear(layerinfo->pgresult);
1✔
2992
    layerinfo->pgresult = pgresult;
2✔
2993

2994
    /* Clean any existing SQL before storing current. */
2995
    layerinfo->sql = strSQL;
2✔
2996

2997
    layerinfo->rownum = 0; /* Only return one result. */
2✔
2998

2999
    /* We don't know the shape type until we read the geometry. */
3000
    shape->type = MS_SHAPE_NULL;
2✔
3001

3002
    num_tuples = PQntuples(pgresult);
2✔
3003
    if (layer->debug) {
2✔
3004
      msDebug("msPostGISLayerGetShape number of records: %d\n", num_tuples);
×
3005
    }
3006

3007
    if (num_tuples > 0) {
2✔
3008
      /* Get shape in random access mode. */
3009
      msPostGISReadShape(layer, shape);
2✔
3010
    }
3011

3012
    return (shape->type == MS_SHAPE_NULL)
2✔
3013
               ? MS_FAILURE
2✔
3014
               : ((num_tuples > 0) ? MS_SUCCESS : MS_DONE);
2✔
3015
  }
3016
#else
3017
  msSetError(MS_MISCERR, "PostGIS support is not available.",
3018
             "msPostGISLayerGetShape()");
3019
  return MS_FAILURE;
3020
#endif
3021
}
3022

3023
/**********************************************************************
3024
 *                     msPostGISPassThroughFieldDefinitions()
3025
 *
3026
 * Pass the field definitions through to the layer metadata in the
3027
 * "gml_[item]_{type,width,precision}" set of metadata items for
3028
 * defining fields.
3029
 **********************************************************************/
3030

3031
#ifdef USE_POSTGIS
3032
static void msPostGISPassThroughFieldDefinitions(layerObj *layer,
66✔
3033
                                                 PGresult *pgresult)
3034

3035
{
3036
  const int numitems = PQnfields(pgresult);
66✔
3037
  msPostGISLayerInfo *layerinfo =
66✔
3038
      static_cast<msPostGISLayerInfo *>(layer->layerinfo);
3039

3040
  for (int i = 0; i < numitems; i++) {
368✔
3041
    const char *gml_type = "Character";
3042
    const char *item = PQfname(pgresult, i);
302✔
3043
    std::string gml_width;
3044
    std::string gml_precision;
3045

3046
    /* skip geometry column */
3047
    if (item == layerinfo->geomcolumn)
302✔
3048
      continue;
3049

3050
    const int oid = PQftype(pgresult, i);
236✔
3051
    const int fmod = PQfmod(pgresult, i);
236✔
3052

3053
    if ((oid == BPCHAROID || oid == VARCHAROID) && fmod >= 4) {
236✔
3054
      gml_width = std::to_string(fmod - 4);
36✔
3055

3056
    } else if (oid == BOOLOID) {
3057
      gml_type = "Boolean";
3058

3059
    } else if (oid == INT2OID) {
3060
      gml_type = "Integer";
3061
      gml_width = '5';
3062

3063
    } else if (oid == INT4OID) {
3064
      gml_type = "Integer";
3065

3066
    } else if (oid == INT8OID) {
3067
      gml_type = "Long";
3068

3069
    } else if (oid == FLOAT4OID || oid == FLOAT8OID) {
3070
      gml_type = "Real";
3071

3072
    } else if (oid == NUMERICOID) {
3073
      gml_type = "Real";
3074

3075
      if (fmod >= 4 && ((fmod - 4) & 0xFFFF) == 0) {
9✔
3076
        gml_type = "Integer";
3077
        gml_width = std::to_string((fmod - 4) >> 16);
×
3078
      } else if (fmod >= 4) {
3079
        gml_width = std::to_string((fmod - 4) >> 16);
9✔
3080
        gml_precision = std::to_string((fmod - 4) & 0xFFFF);
18✔
3081
      }
3082
    } else if (oid == DATEOID) {
3083
      gml_type = "Date";
3084
    } else if (oid == TIMEOID || oid == TIMETZOID) {
3085
      gml_type = "Time";
3086
    } else if (oid == TIMESTAMPOID || oid == TIMESTAMPTZOID) {
3087
      gml_type = "DateTime";
3088
    }
3089

3090
    msUpdateGMLFieldMetadata(layer, item, gml_type, gml_width.c_str(),
236✔
3091
                             gml_precision.c_str(), 0);
3092
  }
3093
}
66✔
3094
#endif /* defined(USE_POSTGIS) */
3095

3096
/*
3097
** msPostGISLayerGetItems()
3098
**
3099
** Registered vtable->LayerGetItems function. Query the database for
3100
** column information about the requested layer. Rather than look in
3101
** system tables, we just run a zero-cost query and read out of the
3102
** result header.
3103
*/
3104
static int msPostGISLayerGetItems(layerObj *layer) {
379✔
3105
#ifdef USE_POSTGIS
3106
  rectObj rect;
3107

3108
  /* A useless rectangle for our useless query */
3109
  rect.minx = rect.miny = rect.maxx = rect.maxy = 0.0;
379✔
3110

3111
  assert(layer != nullptr);
3112
  assert(layer->layerinfo != nullptr);
3113

3114
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
3115

3116
  assert(layerinfo->pgconn);
3117

3118
  if (layer->debug) {
379✔
3119
    msDebug("msPostGISLayerGetItems called.\n");
×
3120
  }
3121

3122
  /* Fill out layerinfo with our current DATA state. */
3123
  if (msPostGISParseData(layer) != MS_SUCCESS) {
379✔
3124
    return MS_FAILURE;
3125
  }
3126

3127
  layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
379✔
3128

3129
  /*
3130
  ** Both the "table" and "(select ...) as sub" cases can be handled with the
3131
  ** same SQL.
3132
  */
3133
  std::string sql("select * from ");
379✔
3134
  sql += msPostGISReplaceBoxToken(layer, &rect, layerinfo->fromsource.c_str());
758✔
3135
  sql += " where false limit 0";
3136

3137
  if (layer->debug) {
379✔
3138
    msDebug("msPostGISLayerGetItems executing SQL: %s\n", sql.c_str());
×
3139
  }
3140

3141
  PGresult *pgresult =
3142
      runPQexecParamsWithBindSubstitution(layer, sql.c_str(), 0);
379✔
3143

3144
  if ((!pgresult) || (PQresultStatus(pgresult) != PGRES_TUPLES_OK)) {
379✔
3145
    msDebug("msPostGISLayerGetItems(): Error (%s) executing SQL: %s\n",
×
3146
            PQerrorMessage(layerinfo->pgconn), sql.c_str());
×
3147
    msSetError(MS_QUERYERR, "Error executing SQL. Check server logs",
×
3148
               "msPostGISLayerGetItems()");
3149
    if (pgresult) {
×
3150
      PQclear(pgresult);
×
3151
    }
3152
    return MS_FAILURE;
×
3153
  }
3154

3155
  layer->numitems = PQnfields(pgresult) -
379✔
3156
                    1; /* don't include the geometry column (last entry)*/
3157
  layer->items = static_cast<char **>(msSmallMalloc(
758✔
3158
      sizeof(char *) *
3159
      (layer->numitems +
379✔
3160
       1))); /* +1 in case there is a problem finding geometry column */
3161

3162
  bool found_geom = false; /* haven't found the geom field */
3163
  int item_num = 0;
3164

3165
  for (int t = 0; t < PQnfields(pgresult); t++) {
5,742✔
3166
    const char *col = PQfname(pgresult, t);
5,363✔
3167
    if (col != layerinfo->geomcolumn) {
5,363✔
3168
      /* this isn't the geometry column */
3169
      layer->items[item_num] = msStrdup(col);
4,984✔
3170
      item_num++;
4,984✔
3171
    } else {
3172
      found_geom = true;
3173
    }
3174
  }
3175

3176
  /*
3177
  ** consider populating the field definitions in metadata.
3178
  */
3179
  const char *value = msOWSLookupMetadata(&(layer->metadata), "G", "types");
379✔
3180
  if (value != nullptr && strcasecmp(value, "auto") == 0)
379✔
3181
    msPostGISPassThroughFieldDefinitions(layer, pgresult);
66✔
3182

3183
  /*
3184
  ** Cleanup
3185
  */
3186
  PQclear(pgresult);
379✔
3187

3188
  if (!found_geom) {
379✔
3189
    msSetError(MS_QUERYERR,
×
3190
               "Tried to find the geometry column in the database, but "
3191
               "couldn't find it.  Is it mis-capitalized? '%s'",
3192
               "msPostGISLayerGetItems()", layerinfo->geomcolumn.c_str());
3193
    return MS_FAILURE;
3194
  }
3195

3196
  return msPostGISLayerInitItemInfo(layer);
379✔
3197
#else
3198
  msSetError(MS_MISCERR, "PostGIS support is not available.",
3199
             "msPostGISLayerGetItems()");
3200
  return MS_FAILURE;
3201
#endif
3202
}
3203

3204
#ifdef USE_POSTGIS
3205
static std::string
3206
addTableNameAndFilterToSelectFrom(layerObj *layer,
68✔
3207
                                  const std::string &selectFrom) {
3208
  auto layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
68✔
3209
  /* if we have !BOX! substitution then we use just the table name */
3210
  std::string f_table_name;
3211
  if (strstr(layerinfo->fromsource.c_str(), BOXTOKEN))
68✔
3212
    f_table_name = msPostGISFindTableName(layerinfo->fromsource.c_str());
4✔
3213
  else
3214
    f_table_name = layerinfo->fromsource;
66✔
3215

3216
  std::string strSQL = selectFrom;
3217
  strSQL += f_table_name;
3218

3219
  /* Handle a translated filter (RFC91). */
3220
  if (layer->filter.native_string) {
68✔
3221
    strSQL += " WHERE (";
3222
    strSQL += layer->filter.native_string;
×
3223
    strSQL += ')';
3224
  }
3225

3226
  /* Handle a native filter set as a PROCESSING option (#5001). */
3227
  const char *native_filter = msLayerGetProcessingKey(layer, "NATIVE_FILTER");
68✔
3228
  if (native_filter) {
68✔
3229
    if (layer->filter.native_string)
×
3230
      strSQL += " AND (";
3231
    else
3232
      strSQL += " WHERE (";
3233
    strSQL += native_filter;
3234
    strSQL += ')';
3235
  }
3236
  return strSQL;
68✔
3237
}
3238
#endif
3239

3240
/*
3241
** msPostGISLayerGetExtent()
3242
**
3243
** Registered vtable->LayerGetExtent function. Query the database for
3244
** the extent of the requested layer.
3245
*/
3246
static int msPostGISLayerGetExtent(layerObj *layer, rectObj *extent) {
55✔
3247
#ifdef USE_POSTGIS
3248
  if (layer->debug) {
55✔
3249
    msDebug("msPostGISLayerGetExtent called.\n");
×
3250
  }
3251

3252
  assert(layer->layerinfo != nullptr);
3253

3254
  if (msPostGISParseData(layer) != MS_SUCCESS) {
55✔
3255
    return MS_FAILURE;
3256
  }
3257

3258
  auto layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
55✔
3259
  const std::string strSQL(addTableNameAndFilterToSelectFrom(
3260
      layer, "SELECT ST_Extent(\"" + layerinfo->geomcolumn + "\") FROM "));
110✔
3261

3262
  if (layer->debug) {
55✔
3263
    msDebug("msPostGISLayerGetExtent executing SQL: %s\n", strSQL.c_str());
×
3264
  }
3265

3266
  /* executing the query */
3267
  PGresult *pgresult =
3268
      runPQexecParamsWithBindSubstitution(layer, strSQL.c_str(), 0);
55✔
3269

3270
  if ((!pgresult) || (PQresultStatus(pgresult) != PGRES_TUPLES_OK)) {
55✔
3271
    msDebug("Error executing SQL: (%s) in msPostGISLayerGetExtent()",
×
3272
            PQerrorMessage(layerinfo->pgconn));
×
3273
    msSetError(MS_MISCERR, "Error executing SQL. Check server logs.",
×
3274
               "msPostGISLayerGetExtent()");
3275
    if (pgresult)
×
3276
      PQclear(pgresult);
×
3277

3278
    return MS_FAILURE;
×
3279
  }
3280

3281
  /* process results */
3282
  if (PQntuples(pgresult) < 1) {
55✔
3283
    msSetError(MS_MISCERR, "msPostGISLayerGetExtent: No results found.",
×
3284
               "msPostGISLayerGetExtent()");
3285
    PQclear(pgresult);
×
3286
    return MS_FAILURE;
3287
  }
3288

3289
  if (PQgetisnull(pgresult, 0, 0)) {
55✔
3290
    msSetError(MS_MISCERR, "msPostGISLayerGetExtent: Null result returned.",
×
3291
               "msPostGISLayerGetExtent()");
3292
    PQclear(pgresult);
×
3293
    return MS_FAILURE;
3294
  }
3295

3296
  if (sscanf(PQgetvalue(pgresult, 0, 0), "BOX(%lf %lf,%lf %lf)", &extent->minx,
55✔
3297
             &extent->miny, &extent->maxx, &extent->maxy) != 4) {
3298
    msSetError(MS_MISCERR, "Failed to process result data.",
×
3299
               "msPostGISLayerGetExtent()");
3300
    PQclear(pgresult);
×
3301
    return MS_FAILURE;
3302
  }
3303

3304
  /* cleanup */
3305
  PQclear(pgresult);
55✔
3306

3307
  return MS_SUCCESS;
3308
#else
3309
  msSetError(MS_MISCERR, "PostGIS support is not available.",
3310
             "msPostGISLayerGetExtent()");
3311
  return MS_FAILURE;
3312
#endif
3313
}
3314

3315
/*
3316
** msPostGISLayerGetNumFeatures()
3317
**
3318
** Registered vtable->LayerGetNumFeatures function. Query the database for
3319
** the feature count of the requested layer.
3320
*/
3321
static int msPostGISLayerGetNumFeatures(layerObj *layer) {
19✔
3322
#ifdef USE_POSTGIS
3323
  if (layer->debug) {
19✔
3324
    msDebug("msPostGISLayerGetNumFeatures called.\n");
×
3325
  }
3326

3327
  assert(layer->layerinfo != nullptr);
3328

3329
  if (msPostGISParseData(layer) != MS_SUCCESS) {
19✔
3330
    return -1;
3331
  }
3332

3333
  auto layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
13✔
3334
  const std::string strSQL(
3335
      addTableNameAndFilterToSelectFrom(layer, "SELECT count(*) FROM "));
13✔
3336
  if (layer->debug) {
13✔
3337
    msDebug("msPostGISLayerGetNumFeatures executing SQL: %s\n", strSQL.c_str());
×
3338
  }
3339

3340
  /* executing the query */
3341
  PGresult *pgresult =
3342
      runPQexecParamsWithBindSubstitution(layer, strSQL.c_str(), 0);
13✔
3343

3344
  if ((!pgresult) || (PQresultStatus(pgresult) != PGRES_TUPLES_OK)) {
13✔
3345
    msDebug("Error executing SQL: (%s) in msPostGISLayerGetNumFeatures()",
1✔
3346
            PQerrorMessage(layerinfo->pgconn));
1✔
3347
    msSetError(MS_MISCERR, "Error executing SQL. Check server logs.",
1✔
3348
               "msPostGISLayerGetNumFeatures()");
3349
    if (pgresult)
1✔
3350
      PQclear(pgresult);
1✔
3351

3352
    return -1;
1✔
3353
  }
3354

3355
  /* process results */
3356
  if (PQntuples(pgresult) < 1) {
12✔
3357
    msSetError(MS_MISCERR, "msPostGISLayerGetNumFeatures: No results found.",
×
3358
               "msPostGISLayerGetNumFeatures()");
3359
    PQclear(pgresult);
×
3360
    return -1;
3361
  }
3362

3363
  if (PQgetisnull(pgresult, 0, 0)) {
12✔
3364
    msSetError(MS_MISCERR,
×
3365
               "msPostGISLayerGetNumFeatures: Null result returned.",
3366
               "msPostGISLayerGetNumFeatures()");
3367
    PQclear(pgresult);
×
3368
    return -1;
3369
  }
3370

3371
  const char *tmp = PQgetvalue(pgresult, 0, 0);
12✔
3372
  int result = 0;
3373
  if (tmp) {
12✔
3374
    result = strtol(tmp, nullptr, 10);
12✔
3375
  }
3376

3377
  /* cleanup */
3378
  PQclear(pgresult);
12✔
3379

3380
  return result;
3381
#else
3382
  msSetError(MS_MISCERR, "PostGIS support is not available.",
3383
             "msPostGISLayerGetNumFeatures()");
3384
  return -1;
3385
#endif
3386
}
3387

3388
#ifdef USE_POSTGIS
3389
/*
3390
 * make sure that the timestring is complete and acceptable
3391
 * to the date_trunc function :
3392
 * - if the resolution is year (2004) or month (2004-01),
3393
 * a complete string for time would be 2004-01-01
3394
 * - if the resolluion is hour or minute (2004-01-01 15), a
3395
 * complete time is 2004-01-01 15:00:00
3396
 */
3397
static int postgresTimeStampForTimeString(const char *timestring, char *dest,
141✔
3398
                                          size_t destsize) {
3399
  int nlength = strlen(timestring);
141✔
3400
  int timeresolution = msTimeGetResolution(timestring);
141✔
3401
  int bNoDate = (*timestring == 'T');
141✔
3402
  if (timeresolution < 0)
141✔
3403
    return MS_FALSE;
3404

3405
  switch (timeresolution) {
141✔
3406
  case TIME_RESOLUTION_YEAR:
9✔
3407
    if (timestring[nlength - 1] != '-') {
9✔
3408
      snprintf(dest, destsize, "date '%s-01-01'", timestring);
3409
    } else {
3410
      snprintf(dest, destsize, "date '%s01-01'", timestring);
3411
    }
3412
    break;
3413
  case TIME_RESOLUTION_MONTH:
9✔
3414
    if (timestring[nlength - 1] != '-') {
9✔
3415
      snprintf(dest, destsize, "date '%s-01'", timestring);
3416
    } else {
3417
      snprintf(dest, destsize, "date '%s01'", timestring);
3418
    }
3419
    break;
3420
  case TIME_RESOLUTION_DAY:
3421
    snprintf(dest, destsize, "date '%s'", timestring);
3422
    break;
3423
  case TIME_RESOLUTION_HOUR:
22✔
3424
    if (timestring[nlength - 1] != ':') {
22✔
3425
      if (bNoDate)
22✔
3426
        snprintf(dest, destsize, "time '%s:00:00'", timestring);
3427
      else
3428
        snprintf(dest, destsize, "timestamp '%s:00:00'", timestring);
3429
    } else {
3430
      if (bNoDate)
×
3431
        snprintf(dest, destsize, "time '%s00:00'", timestring);
3432
      else
3433
        snprintf(dest, destsize, "timestamp '%s00:00'", timestring);
3434
    }
3435
    break;
3436
  case TIME_RESOLUTION_MINUTE:
18✔
3437
    if (timestring[nlength - 1] != ':') {
18✔
3438
      if (bNoDate)
18✔
3439
        snprintf(dest, destsize, "time '%s:00'", timestring);
3440
      else
3441
        snprintf(dest, destsize, "timestamp '%s:00'", timestring);
3442
    } else {
3443
      if (bNoDate)
×
3444
        snprintf(dest, destsize, "time '%s00'", timestring);
3445
      else
3446
        snprintf(dest, destsize, "timestamp '%s00'", timestring);
3447
    }
3448
    break;
3449
  case TIME_RESOLUTION_SECOND:
53✔
3450
    if (bNoDate)
53✔
3451
      snprintf(dest, destsize, "time '%s'", timestring);
3452
    else
3453
      snprintf(dest, destsize, "timestamp '%s'", timestring);
3454
    break;
3455
  default:
3456
    return MS_FAILURE;
3457
  }
3458
  return MS_SUCCESS;
3459
}
3460

3461
/*
3462
 * create a postgresql where clause for the given timestring, taking into
3463
 * account the resolution (e.g. second, day, month...) of the given timestring
3464
 * we apply the date_trunc function on the given timestring and not on the time
3465
 * column in order for postgres to take advantage of an eventual index on the
3466
 * time column
3467
 *
3468
 * the generated sql is
3469
 *
3470
 * (
3471
 *    timecol >= date_trunc(timestring,resolution)
3472
 *      and
3473
 *    timecol < date_trunc(timestring,resolution) + interval '1 resolution'
3474
 * )
3475
 */
3476
static int createPostgresTimeCompareEquals(const char *timestring, char *dest,
45✔
3477
                                           size_t destsize) {
3478
  int timeresolution = msTimeGetResolution(timestring);
45✔
3479
  char timeStamp[100];
3480
  const char *interval;
3481
  if (timeresolution < 0)
45✔
3482
    return MS_FALSE;
3483

3484
  postgresTimeStampForTimeString(timestring, timeStamp, 100);
45✔
3485

3486
  switch (timeresolution) {
45✔
3487
  case TIME_RESOLUTION_YEAR:
3488
    interval = "year";
3489
    break;
3490
  case TIME_RESOLUTION_MONTH:
3✔
3491
    interval = "month";
3492
    break;
3✔
3493
  case TIME_RESOLUTION_DAY:
6✔
3494
    interval = "day";
3495
    break;
6✔
3496
  case TIME_RESOLUTION_HOUR:
6✔
3497
    interval = "hour";
3498
    break;
6✔
3499
  case TIME_RESOLUTION_MINUTE:
6✔
3500
    interval = "minute";
3501
    break;
6✔
3502
  case TIME_RESOLUTION_SECOND:
21✔
3503
    interval = "second";
3504
    break;
21✔
3505
  default:
3506
    return MS_FAILURE;
3507
  }
3508
  snprintf(dest, destsize,
3509
           " between date_trunc('%s',%s) and date_trunc('%s',%s) + interval '1 "
3510
           "%s' - interval '1 second'",
3511
           interval, timeStamp, interval, timeStamp, interval);
3512
  return MS_SUCCESS;
45✔
3513
}
3514

3515
static int createPostgresTimeCompareGreaterThan(const char *timestring,
48✔
3516
                                                char *dest, size_t destsize) {
3517
  int timeresolution = msTimeGetResolution(timestring);
48✔
3518
  char timestamp[100];
3519
  const char *interval;
3520
  if (timeresolution < 0)
48✔
3521
    return MS_FALSE;
3522

3523
  postgresTimeStampForTimeString(timestring, timestamp, 100);
48✔
3524

3525
  switch (timeresolution) {
48✔
3526
  case TIME_RESOLUTION_YEAR:
3527
    interval = "year";
3528
    break;
3529
  case TIME_RESOLUTION_MONTH:
3✔
3530
    interval = "month";
3531
    break;
3✔
3532
  case TIME_RESOLUTION_DAY:
12✔
3533
    interval = "day";
3534
    break;
12✔
3535
  case TIME_RESOLUTION_HOUR:
8✔
3536
    interval = "hour";
3537
    break;
8✔
3538
  case TIME_RESOLUTION_MINUTE:
6✔
3539
    interval = "minute";
3540
    break;
6✔
3541
  case TIME_RESOLUTION_SECOND:
16✔
3542
    interval = "second";
3543
    break;
16✔
3544
  default:
3545
    return MS_FAILURE;
3546
  }
3547

3548
  snprintf(dest, destsize, "date_trunc('%s',%s)", interval, timestamp);
3549
  return MS_SUCCESS;
48✔
3550
}
3551

3552
/*
3553
 * create a postgresql where clause for the range given by the two input
3554
 * timestring, taking into account the resolution (e.g. second, day, month...)
3555
 * of each of the given timestrings (both timestrings can have different
3556
 * resolutions, although I don't know if that's a valid TIME range we apply the
3557
 * date_trunc function on the given timestrings and not on the time column in
3558
 * order for postgres to take advantage of an eventual index on the time column
3559
 *
3560
 * the generated sql is
3561
 *
3562
 * (
3563
 *    timecol >= date_trunc(mintimestring,minresolution)
3564
 *      and
3565
 *    timecol < date_trunc(maxtimestring,maxresolution) + interval '1
3566
 * maxresolution'
3567
 * )
3568
 */
3569
static int createPostgresTimeCompareLessThan(const char *timestring, char *dest,
48✔
3570
                                             size_t destsize) {
3571
  int timeresolution = msTimeGetResolution(timestring);
48✔
3572
  char timestamp[100];
3573
  const char *interval;
3574
  if (timeresolution < 0)
48✔
3575
    return MS_FALSE;
3576

3577
  postgresTimeStampForTimeString(timestring, timestamp, 100);
48✔
3578

3579
  switch (timeresolution) {
48✔
3580
  case TIME_RESOLUTION_YEAR:
3581
    interval = "year";
3582
    break;
3583
  case TIME_RESOLUTION_MONTH:
3✔
3584
    interval = "month";
3585
    break;
3✔
3586
  case TIME_RESOLUTION_DAY:
12✔
3587
    interval = "day";
3588
    break;
12✔
3589
  case TIME_RESOLUTION_HOUR:
8✔
3590
    interval = "hour";
3591
    break;
8✔
3592
  case TIME_RESOLUTION_MINUTE:
6✔
3593
    interval = "minute";
3594
    break;
6✔
3595
  case TIME_RESOLUTION_SECOND:
16✔
3596
    interval = "second";
3597
    break;
16✔
3598
  default:
3599
    return MS_FAILURE;
3600
  }
3601
  snprintf(dest, destsize,
3602
           "(date_trunc('%s',%s) + interval '1 %s' - interval '1 second')",
3603
           interval, timestamp, interval);
3604
  return MS_SUCCESS;
48✔
3605
}
3606
#endif
3607

3608
static char *msPostGISEscapeSQLParam(layerObj *layer, const char *pszString) {
61✔
3609
#ifdef USE_POSTGIS
3610
  char *pszEscapedStr = nullptr;
3611

3612
  if (layer && pszString) {
61✔
3613
    if (!msPostGISLayerIsOpen(layer))
3614
      msPostGISLayerOpen(layer);
×
3615

3616
    assert(layer->layerinfo != nullptr);
3617

3618
    msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
61✔
3619
    size_t nSrcLen = strlen(pszString);
61✔
3620
    pszEscapedStr = (char *)msSmallMalloc(2 * nSrcLen + 1);
61✔
3621
    int nError = 0;
61✔
3622
    PQescapeStringConn(layerinfo->pgconn, pszEscapedStr, pszString, nSrcLen,
61✔
3623
                       &nError);
3624
    if (nError != 0) {
61✔
3625
      free(pszEscapedStr);
×
3626
      pszEscapedStr = nullptr;
3627
    }
3628
  }
3629
  return pszEscapedStr;
61✔
3630
#else
3631
  msSetError(MS_MISCERR, "PostGIS support is not available.",
3632
             "msPostGISEscapeSQLParam()");
3633
  return NULL;
3634
#endif
3635
}
3636

3637
static void msPostGISEnablePaging(layerObj *layer, int value) {
127✔
3638
#ifdef USE_POSTGIS
3639
  if (layer->debug) {
127✔
3640
    msDebug("msPostGISEnablePaging called.\n");
×
3641
  }
3642

3643
  if (!msPostGISLayerIsOpen(layer)) {
3644
    if (msPostGISLayerOpen(layer) != MS_SUCCESS) {
×
3645
      return;
3646
    }
3647
  }
3648

3649
  assert(layer->layerinfo != nullptr);
3650

3651
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
127✔
3652
  layerinfo->paging = value;
127✔
3653

3654
#else
3655
  msSetError(MS_MISCERR, "PostGIS support is not available.",
3656
             "msPostGISEnablePaging()");
3657
#endif
3658
  return;
127✔
3659
}
3660

3661
static int msPostGISGetPaging(layerObj *layer) {
149✔
3662
#ifdef USE_POSTGIS
3663
  if (layer->debug) {
149✔
3664
    msDebug("msPostGISGetPaging called.\n");
×
3665
  }
3666

3667
  if (!msPostGISLayerIsOpen(layer))
3668
    return MS_TRUE;
3669

3670
  assert(layer->layerinfo != nullptr);
3671

3672
  msPostGISLayerInfo *layerinfo = (msPostGISLayerInfo *)layer->layerinfo;
3673
  return layerinfo->paging;
99✔
3674
#else
3675
  msSetError(MS_MISCERR, "PostGIS support is not available.",
3676
             "msPostGISEnablePaging()");
3677
  return MS_FAILURE;
3678
#endif
3679
}
3680

3681
/*
3682
** msPostGISLayerTranslateFilter()
3683
**
3684
** Registered vtable->LayerTranslateFilter function.
3685
*/
3686
static int msPostGISLayerTranslateFilter(layerObj *layer, expressionObj *filter,
243✔
3687
                                         char *filteritem) {
3688
#ifdef USE_POSTGIS
3689
  tokenListNodeObjPtr node = nullptr;
3690

3691
  std::string native_string;
3692

3693
  int comparisonToken = -1;
3694
  int bindingToken = -1;
3695

3696
  msPostGISLayerInfo *layerinfo =
243✔
3697
      static_cast<msPostGISLayerInfo *>(layer->layerinfo);
3698

3699
  if (!filter->string)
243✔
3700
    return MS_SUCCESS; /* not an error, just nothing to do */
3701

3702
  // fprintf(stderr, "input: %s, %s, %d\n", filter->string, filteritem,
3703
  // filter->type);
3704

3705
  /*
3706
  ** FILTERs use MapServer syntax *only* (#5001).
3707
  */
3708
  if (filter->type == MS_STRING && filter->string &&
151✔
3709
      filteritem) { /* item/value pair - add escaping */
3710

3711
    // check if field is numeric and avoid converting to string
3712
    // as this prevents indexes being used
3713
    bool is_numeric = msLayerPropertyIsNumeric(layer, filteritem);
5✔
3714

3715
    char *stresc = msLayerEscapePropertyName(layer, filteritem);
5✔
3716
    if (filter->flags & MS_EXP_INSENSITIVE) {
5✔
3717
      native_string += "upper(";
3718
      native_string += stresc;
3719
      native_string += "::text) = upper(";
3720
    } else {
3721
      native_string += stresc;
3722
      if (!is_numeric) {
4✔
3723
        native_string += "::text";
3724
      }
3725
      native_string += " = ";
3726
    }
3727
    msFree(stresc);
5✔
3728

3729
    /* don't have a type for the righthand literal so assume it's a string and
3730
     * we quote */
3731
    stresc = msPostGISEscapeSQLParam(layer, filter->string);
5✔
3732
    if (!is_numeric) {
5✔
3733
      native_string += '\'';
3734
      native_string += stresc;
3735
      native_string += '\'';
3736
    } else {
NEW
3737
      char *endptr = nullptr;
×
NEW
3738
      std::strtod(stresc, &endptr);
×
NEW
3739
      if (endptr != stresc + strlen(stresc)) {
×
NEW
3740
        if (layer->debug >= 2) {
×
NEW
3741
          msDebug(
×
3742
              "msPostGISLayerTranslateFilter. '%s' is not a numeric value.\n",
3743
              filter->string);
3744
        }
NEW
3745
        msFree(stresc);
×
NEW
3746
        return MS_FAILURE;
×
3747
      }
3748
      native_string += stresc;
3749
    }
3750
    msFree(stresc);
5✔
3751

3752
    if (filter->flags & MS_EXP_INSENSITIVE)
5✔
3753
      native_string += ")";
3754
  } else if (filter->type == MS_REGEX && filter->string &&
146✔
3755
             filteritem) { /* item/regex pair  - add escaping */
3756

3757
    char *stresc = msLayerEscapePropertyName(layer, filteritem);
4✔
3758
    native_string += stresc;
3759
    if (filter->flags & MS_EXP_INSENSITIVE) {
4✔
3760
      native_string += "::text ~* ";
3761
    } else {
3762
      native_string += "::text ~ ";
3763
    }
3764
    msFree(stresc);
4✔
3765

3766
    stresc = msPostGISEscapeSQLParam(layer, filter->string);
4✔
3767
    native_string += '\'';
3768
    native_string += stresc;
3769
    native_string += '\'';
3770
    msFree(stresc);
4✔
3771
  } else if (filter->type == MS_EXPRESSION) {
146✔
3772
    int ieq_expected = MS_FALSE;
3773

3774
    if (msPostGISParseData(layer) != MS_SUCCESS)
142✔
3775
      return MS_FAILURE;
3776

3777
    if (layer->debug >= 2)
142✔
3778
      msDebug("msPostGISLayerTranslateFilter. String: %s.\n", filter->string);
×
3779
    if (!filter->tokens)
142✔
3780
      return MS_SUCCESS;
3781
    if (layer->debug >= 2)
142✔
3782
      msDebug(
×
3783
          "msPostGISLayerTranslateFilter. There are tokens to process... \n");
3784

3785
    node = filter->tokens;
142✔
3786
    while (node != nullptr) {
1,531✔
3787

3788
      /*
3789
      ** Do any token caching/tracking here, easier to have it in one place.
3790
      */
3791
      if (node->token == MS_TOKEN_BINDING_TIME) {
1,391✔
3792
        bindingToken = node->token;
3793
      } else if (node->token == MS_TOKEN_COMPARISON_EQ ||
1,250✔
3794
                 node->token == MS_TOKEN_COMPARISON_IEQ ||
1,136✔
3795
                 node->token == MS_TOKEN_COMPARISON_NE ||
1,133✔
3796
                 node->token == MS_TOKEN_COMPARISON_GT ||
1,131✔
3797
                 node->token == MS_TOKEN_COMPARISON_GE ||
1,072✔
3798
                 node->token == MS_TOKEN_COMPARISON_LT ||
1,058✔
3799
                 node->token == MS_TOKEN_COMPARISON_LE ||
1,004✔
3800
                 node->token == MS_TOKEN_COMPARISON_IN) {
3801
        comparisonToken = node->token;
3802
      }
3803

3804
      switch (node->token) {
1,391✔
3805

3806
      /* literal tokens */
3807
      case MS_TOKEN_LITERAL_BOOLEAN:
15✔
3808
        if (node->tokenval.dblval == MS_TRUE)
15✔
3809
          native_string += "TRUE";
3810
        else
3811
          native_string += "FALSE";
3812
        break;
3813
      case MS_TOKEN_LITERAL_NUMBER: {
50✔
3814
        if (node->tokenval.dblval >= INT_MIN &&
50✔
3815
            node->tokenval.dblval <= INT_MAX &&
50✔
3816
            node->tokenval.dblval == (int)node->tokenval.dblval)
50✔
3817
          native_string += std::to_string((int)node->tokenval.dblval);
98✔
3818
        else {
3819
          char buffer[32];
3820
          snprintf(buffer, sizeof(buffer), "%.18g", node->tokenval.dblval);
3821
          native_string += buffer;
3822
        }
3823
        break;
3824
      }
3825
      case MS_TOKEN_LITERAL_STRING:
50✔
3826

3827
        if (comparisonToken == MS_TOKEN_COMPARISON_IN) { /* issue 5490 */
50✔
3828
          int nstrings = 0;
1✔
3829
          char **strings = msStringSplit(node->tokenval.strval, ',', &nstrings);
1✔
3830
          if (nstrings > 0) {
1✔
3831
            native_string += "(";
3832
            for (int i = 0; i < nstrings; i++) {
4✔
3833
              if (i != 0)
3✔
3834
                native_string += ",";
3835
              char *stresc = msPostGISEscapeSQLParam(layer, strings[i]);
3✔
3836
              native_string += '\'';
3837
              native_string += stresc;
3838
              native_string += '\'';
3839
              msFree(stresc);
3✔
3840
            }
3841
            native_string += ")";
3842
          }
3843

3844
          msFreeCharArray(strings, nstrings);
1✔
3845
        } else {
3846
          const char *strbegin;
3847
          const char *strend;
3848
          if (comparisonToken == MS_TOKEN_COMPARISON_IEQ) {
49✔
3849
            strbegin = "lower('";
3850
            strend = "')";
3851
          } else {
3852
            strbegin = "'";
3853
            strend = "'";
3854
          }
3855
          char *stresc = msPostGISEscapeSQLParam(layer, node->tokenval.strval);
49✔
3856
          native_string += strbegin;
3857
          native_string += stresc;
3858
          native_string += strend;
3859
          msFree(stresc);
49✔
3860
        }
3861

3862
        break;
3863
      case MS_TOKEN_LITERAL_TIME: {
141✔
3864
        char *snippet = (char *)msSmallMalloc(512);
141✔
3865
        if (comparisonToken == MS_TOKEN_COMPARISON_EQ) { // TODO: support !=
141✔
3866
          createPostgresTimeCompareEquals(node->tokensrc, snippet, 512);
45✔
3867
        } else if (comparisonToken == MS_TOKEN_COMPARISON_GT ||
96✔
3868
                   comparisonToken == MS_TOKEN_COMPARISON_GE) {
96✔
3869
          createPostgresTimeCompareGreaterThan(node->tokensrc, snippet, 512);
48✔
3870
        } else if (comparisonToken == MS_TOKEN_COMPARISON_LT ||
48✔
3871
                   comparisonToken == MS_TOKEN_COMPARISON_LE) {
3872
          createPostgresTimeCompareLessThan(node->tokensrc, snippet, 512);
48✔
3873
        } else {
3874
          msFree(snippet);
×
3875
          goto cleanup;
×
3876
        }
3877

3878
        comparisonToken = -1;
3879
        bindingToken = -1; /* reset */
3880
        native_string += snippet;
3881
        msFree(snippet);
141✔
3882
        break;
141✔
3883
      }
3884
      case MS_TOKEN_LITERAL_SHAPE: {
15✔
3885
        char *wkt = msShapeToWKT(node->tokenval.shpval);
15✔
3886
        native_string += "ST_GeomFromText('";
3887
        native_string += wkt;
3888
        msFree(wkt);
15✔
3889
        native_string += "'";
3890
        if (!layerinfo->srid.empty()) {
15✔
3891
          native_string += ",";
3892
          native_string += layerinfo->srid;
3893
        }
3894
        native_string += ")";
3895
        break;
3896
      }
3897

3898
        /* data binding tokens */
3899
      case MS_TOKEN_BINDING_TIME:
240✔
3900
      case MS_TOKEN_BINDING_DOUBLE:
3901
      case MS_TOKEN_BINDING_INTEGER:
3902
      case MS_TOKEN_BINDING_STRING: {
3903
        const char *strbegin = "";
3904
        const char *strend = "";
3905
        if (node->token == MS_TOKEN_BINDING_STRING &&
240✔
3906
            node->next->token == MS_TOKEN_COMPARISON_IEQ) {
53✔
3907
          strbegin = "lower(";
3908
          strend = "::text)";
3909
          ieq_expected = MS_TRUE;
3910
        } else if (node->token == MS_TOKEN_BINDING_STRING ||
238✔
3911
                   node->next->token == MS_TOKEN_COMPARISON_RE ||
187✔
3912
                   node->next->token == MS_TOKEN_COMPARISON_IRE)
3913
          strend = "::text"; /* explicit cast necessary for certain operators */
3914

3915
        char *stresc =
3916
            msLayerEscapePropertyName(layer, node->tokenval.bindval.item);
240✔
3917
        native_string += strbegin;
3918
        native_string += stresc;
3919
        native_string += strend;
3920
        msFree(stresc);
240✔
3921
        break;
240✔
3922
      }
3923
      case MS_TOKEN_BINDING_SHAPE:
15✔
3924
        native_string += layerinfo->geomcolumn;
3925
        break;
3926
      case MS_TOKEN_BINDING_MAP_CELLSIZE: {
×
3927
        char buffer[32];
3928
        snprintf(buffer, sizeof(buffer), "%.18g", layer->map->cellsize);
×
3929
        native_string += buffer;
3930
        break;
3931
      }
3932

3933
        /* spatial comparison tokens */
3934
      case MS_TOKEN_COMPARISON_INTERSECTS:
15✔
3935
      case MS_TOKEN_COMPARISON_DISJOINT:
3936
      case MS_TOKEN_COMPARISON_TOUCHES:
3937
      case MS_TOKEN_COMPARISON_OVERLAPS:
3938
      case MS_TOKEN_COMPARISON_CROSSES:
3939
      case MS_TOKEN_COMPARISON_WITHIN:
3940
      case MS_TOKEN_COMPARISON_CONTAINS:
3941
      case MS_TOKEN_COMPARISON_EQUALS:
3942
      case MS_TOKEN_COMPARISON_DWITHIN: {
3943
        if (node->next->token != '(')
15✔
3944
          goto cleanup;
×
3945
        native_string += "st_";
3946
        const char *str = msExpressionTokenToString(node->token);
15✔
3947
        if (str == nullptr)
15✔
3948
          goto cleanup;
×
3949
        native_string += str;
3950
        break;
3951
      }
3952

3953
        /* functions */
3954
      case MS_TOKEN_FUNCTION_LENGTH:
3955
      case MS_TOKEN_FUNCTION_AREA:
3956
      case MS_TOKEN_FUNCTION_BUFFER:
3957
      case MS_TOKEN_FUNCTION_DIFFERENCE: {
3958
        native_string += "st_";
3959
        const char *str = msExpressionTokenToString(node->token);
×
3960
        if (str == nullptr)
×
3961
          goto cleanup;
×
3962
        native_string += str;
3963
        break;
3964
      }
3965

3966
      case MS_TOKEN_COMPARISON_IEQ:
2✔
3967
        if (ieq_expected) {
2✔
3968
          native_string += "=";
3969
          ieq_expected = MS_FALSE;
3970
        } else {
3971
          goto cleanup;
×
3972
        }
3973
        break;
3974

3975
        /* unsupported tokens */
3976
      case MS_TOKEN_COMPARISON_BEYOND:
1✔
3977
      case MS_TOKEN_FUNCTION_TOSTRING:
3978
      case MS_TOKEN_FUNCTION_ROUND:
3979
      case MS_TOKEN_FUNCTION_SIMPLIFY:
3980
      case MS_TOKEN_FUNCTION_SIMPLIFYPT:
3981
      case MS_TOKEN_FUNCTION_GENERALIZE:
3982
        goto cleanup;
1✔
3983
        break;
3984

3985
      default: {
847✔
3986
        /* by default accept the general token to string conversion */
3987

3988
        if (node->token == MS_TOKEN_COMPARISON_EQ && node->next != nullptr &&
847✔
3989
            node->next->token == MS_TOKEN_LITERAL_TIME)
112✔
3990
          break; /* skip, handled with the next token */
3991
        if (bindingToken == MS_TOKEN_BINDING_TIME &&
802✔
3992
            (node->token == MS_TOKEN_COMPARISON_EQ ||
96✔
3993
             node->token == MS_TOKEN_COMPARISON_NE))
3994
          break; /* skip, handled elsewhere */
3995
        if (node->token == MS_TOKEN_COMPARISON_EQ && node->next != nullptr &&
802✔
3996
            node->next->token == MS_TOKEN_LITERAL_STRING &&
67✔
3997
            strcmp(node->next->tokenval.strval, "_MAPSERVER_NULL_") == 0) {
35✔
3998
          native_string += " IS NULL";
3999
          node = node->next;
3✔
4000
          break;
3✔
4001
        }
4002

4003
        const char *str = msExpressionTokenToString(node->token);
799✔
4004
        if (str == nullptr)
799✔
4005
          goto cleanup;
1✔
4006
        native_string += str;
4007
        break;
4008
      }
4009
      }
4010

4011
      node = node->next;
1,389✔
4012
    }
4013
  }
4014

4015
  filter->native_string = msStrdup(native_string.c_str());
149✔
4016

4017
  // fprintf(stderr, "output: %s\n", filter->native_string);
4018

4019
  return MS_SUCCESS;
149✔
4020

4021
cleanup:
2✔
4022
  msSetError(MS_MISCERR, "Translation to native SQL failed.",
2✔
4023
             "msPostGISLayerTranslateFilter()");
4024
  return MS_FAILURE;
4025
#else
4026
  msSetError(MS_MISCERR, "PostGIS support is not available.",
4027
             "msPostGISLayerTranslateFilter()");
4028
  return MS_FAILURE;
4029
#endif
4030
}
4031

4032
int msPostGISLayerInitializeVirtualTable(layerObj *layer) {
1,475✔
4033
  assert(layer != nullptr);
4034
  assert(layer->vtable != nullptr);
4035

4036
  layer->vtable->LayerTranslateFilter = msPostGISLayerTranslateFilter;
1,475✔
4037

4038
  layer->vtable->LayerInitItemInfo = msPostGISLayerInitItemInfo;
1,475✔
4039
  layer->vtable->LayerFreeItemInfo = msPostGISLayerFreeItemInfo;
1,475✔
4040
  layer->vtable->LayerOpen = msPostGISLayerOpen;
1,475✔
4041
  layer->vtable->LayerIsOpen = msPostGISLayerIsOpen;
1,475✔
4042
  layer->vtable->LayerWhichShapes = msPostGISLayerWhichShapes;
1,475✔
4043
  layer->vtable->LayerNextShape = msPostGISLayerNextShape;
1,475✔
4044
  layer->vtable->LayerGetShape = msPostGISLayerGetShape;
1,475✔
4045
  layer->vtable->LayerGetShapeCount = msPostGISLayerGetShapeCount;
1,475✔
4046
  layer->vtable->LayerClose = msPostGISLayerClose;
1,475✔
4047
  layer->vtable->LayerGetItems = msPostGISLayerGetItems;
1,475✔
4048
  layer->vtable->LayerGetExtent = msPostGISLayerGetExtent;
1,475✔
4049
  /* layer->vtable->LayerApplyFilterToLayer, use default */
4050
  /* layer->vtable->LayerGetAutoStyle, not supported for this layer */
4051
  /* layer->vtable->LayerCloseConnection = msPostGISLayerClose; */
4052
  // layer->vtable->LayerSetTimeFilter = msPostGISLayerSetTimeFilter;
4053
  layer->vtable->LayerSetTimeFilter = msLayerMakeBackticsTimeFilter;
1,475✔
4054
  /* layer->vtable->LayerCreateItems, use default */
4055
  layer->vtable->LayerGetNumFeatures = msPostGISLayerGetNumFeatures;
1,475✔
4056

4057
  /* layer->vtable->LayerGetAutoProjection, use default*/
4058

4059
  layer->vtable->LayerEscapeSQLParam = msPostGISEscapeSQLParam;
1,475✔
4060
  layer->vtable->LayerEnablePaging = msPostGISEnablePaging;
1,475✔
4061
  layer->vtable->LayerGetPaging = msPostGISGetPaging;
1,475✔
4062

4063
  return MS_SUCCESS;
1,475✔
4064
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc