Coverage Report

Created: 2025-08-12 06:43

/src/postgres/src/backend/access/spgist/spgquadtreeproc.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * spgquadtreeproc.c
4
 *    implementation of quad tree over points for SP-GiST
5
 *
6
 *
7
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8
 * Portions Copyright (c) 1994, Regents of the University of California
9
 *
10
 * IDENTIFICATION
11
 *      src/backend/access/spgist/spgquadtreeproc.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
16
#include "postgres.h"
17
18
#include "access/spgist.h"
19
#include "access/spgist_private.h"
20
#include "access/stratnum.h"
21
#include "catalog/pg_type.h"
22
#include "utils/float.h"
23
#include "utils/fmgrprotos.h"
24
#include "utils/geo_decls.h"
25
26
Datum
27
spg_quad_config(PG_FUNCTION_ARGS)
28
0
{
29
  /* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */
30
0
  spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
31
32
0
  cfg->prefixType = POINTOID;
33
0
  cfg->labelType = VOIDOID; /* we don't need node labels */
34
0
  cfg->canReturnData = true;
35
0
  cfg->longValuesOK = false;
36
0
  PG_RETURN_VOID();
37
0
}
38
39
#define SPTEST(f, x, y) \
40
0
  DatumGetBool(DirectFunctionCall2(f, PointPGetDatum(x), PointPGetDatum(y)))
41
42
/*
43
 * Determine which quadrant a point falls into, relative to the centroid.
44
 *
45
 * Quadrants are identified like this:
46
 *
47
 *   4  |  1
48
 *  ----+-----
49
 *   3  |  2
50
 *
51
 * Points on one of the axes are taken to lie in the lowest-numbered
52
 * adjacent quadrant.
53
 */
54
static int16
55
getQuadrant(Point *centroid, Point *tst)
56
0
{
57
0
  if ((SPTEST(point_above, tst, centroid) ||
58
0
     SPTEST(point_horiz, tst, centroid)) &&
59
0
    (SPTEST(point_right, tst, centroid) ||
60
0
     SPTEST(point_vert, tst, centroid)))
61
0
    return 1;
62
63
0
  if (SPTEST(point_below, tst, centroid) &&
64
0
    (SPTEST(point_right, tst, centroid) ||
65
0
     SPTEST(point_vert, tst, centroid)))
66
0
    return 2;
67
68
0
  if ((SPTEST(point_below, tst, centroid) ||
69
0
     SPTEST(point_horiz, tst, centroid)) &&
70
0
    SPTEST(point_left, tst, centroid))
71
0
    return 3;
72
73
0
  if (SPTEST(point_above, tst, centroid) &&
74
0
    SPTEST(point_left, tst, centroid))
75
0
    return 4;
76
77
0
  elog(ERROR, "getQuadrant: impossible case");
78
0
  return 0;
79
0
}
80
81
/* Returns bounding box of a given quadrant inside given bounding box */
82
static BOX *
83
getQuadrantArea(BOX *bbox, Point *centroid, int quadrant)
84
0
{
85
0
  BOX      *result = (BOX *) palloc(sizeof(BOX));
86
87
0
  switch (quadrant)
88
0
  {
89
0
    case 1:
90
0
      result->high = bbox->high;
91
0
      result->low = *centroid;
92
0
      break;
93
0
    case 2:
94
0
      result->high.x = bbox->high.x;
95
0
      result->high.y = centroid->y;
96
0
      result->low.x = centroid->x;
97
0
      result->low.y = bbox->low.y;
98
0
      break;
99
0
    case 3:
100
0
      result->high = *centroid;
101
0
      result->low = bbox->low;
102
0
      break;
103
0
    case 4:
104
0
      result->high.x = centroid->x;
105
0
      result->high.y = bbox->high.y;
106
0
      result->low.x = bbox->low.x;
107
0
      result->low.y = centroid->y;
108
0
      break;
109
0
  }
110
111
0
  return result;
112
0
}
113
114
Datum
115
spg_quad_choose(PG_FUNCTION_ARGS)
116
0
{
117
0
  spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0);
118
0
  spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1);
119
0
  Point    *inPoint = DatumGetPointP(in->datum),
120
0
         *centroid;
121
122
0
  if (in->allTheSame)
123
0
  {
124
0
    out->resultType = spgMatchNode;
125
    /* nodeN will be set by core */
126
0
    out->result.matchNode.levelAdd = 0;
127
0
    out->result.matchNode.restDatum = PointPGetDatum(inPoint);
128
0
    PG_RETURN_VOID();
129
0
  }
130
131
0
  Assert(in->hasPrefix);
132
0
  centroid = DatumGetPointP(in->prefixDatum);
133
134
0
  Assert(in->nNodes == 4);
135
136
0
  out->resultType = spgMatchNode;
137
0
  out->result.matchNode.nodeN = getQuadrant(centroid, inPoint) - 1;
138
0
  out->result.matchNode.levelAdd = 0;
139
0
  out->result.matchNode.restDatum = PointPGetDatum(inPoint);
140
141
0
  PG_RETURN_VOID();
142
0
}
143
144
#ifdef USE_MEDIAN
145
static int
146
x_cmp(const void *a, const void *b, void *arg)
147
{
148
  Point    *pa = *(Point **) a;
149
  Point    *pb = *(Point **) b;
150
151
  if (pa->x == pb->x)
152
    return 0;
153
  return (pa->x > pb->x) ? 1 : -1;
154
}
155
156
static int
157
y_cmp(const void *a, const void *b, void *arg)
158
{
159
  Point    *pa = *(Point **) a;
160
  Point    *pb = *(Point **) b;
161
162
  if (pa->y == pb->y)
163
    return 0;
164
  return (pa->y > pb->y) ? 1 : -1;
165
}
166
#endif
167
168
Datum
169
spg_quad_picksplit(PG_FUNCTION_ARGS)
170
0
{
171
0
  spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
172
0
  spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
173
0
  int     i;
174
0
  Point    *centroid;
175
176
#ifdef USE_MEDIAN
177
  /* Use the median values of x and y as the centroid point */
178
  Point   **sorted;
179
180
  sorted = palloc(sizeof(*sorted) * in->nTuples);
181
  for (i = 0; i < in->nTuples; i++)
182
    sorted[i] = DatumGetPointP(in->datums[i]);
183
184
  centroid = palloc(sizeof(*centroid));
185
186
  qsort(sorted, in->nTuples, sizeof(*sorted), x_cmp);
187
  centroid->x = sorted[in->nTuples >> 1]->x;
188
  qsort(sorted, in->nTuples, sizeof(*sorted), y_cmp);
189
  centroid->y = sorted[in->nTuples >> 1]->y;
190
#else
191
  /* Use the average values of x and y as the centroid point */
192
0
  centroid = palloc0(sizeof(*centroid));
193
194
0
  for (i = 0; i < in->nTuples; i++)
195
0
  {
196
0
    centroid->x += DatumGetPointP(in->datums[i])->x;
197
0
    centroid->y += DatumGetPointP(in->datums[i])->y;
198
0
  }
199
200
0
  centroid->x /= in->nTuples;
201
0
  centroid->y /= in->nTuples;
202
0
#endif
203
204
0
  out->hasPrefix = true;
205
0
  out->prefixDatum = PointPGetDatum(centroid);
206
207
0
  out->nNodes = 4;
208
0
  out->nodeLabels = NULL;   /* we don't need node labels */
209
210
0
  out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
211
0
  out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
212
213
0
  for (i = 0; i < in->nTuples; i++)
214
0
  {
215
0
    Point    *p = DatumGetPointP(in->datums[i]);
216
0
    int     quadrant = getQuadrant(centroid, p) - 1;
217
218
0
    out->leafTupleDatums[i] = PointPGetDatum(p);
219
0
    out->mapTuplesToNodes[i] = quadrant;
220
0
  }
221
222
0
  PG_RETURN_VOID();
223
0
}
224
225
226
Datum
227
spg_quad_inner_consistent(PG_FUNCTION_ARGS)
228
0
{
229
0
  spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
230
0
  spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
231
0
  Point    *centroid;
232
0
  BOX     infbbox;
233
0
  BOX      *bbox = NULL;
234
0
  int     which;
235
0
  int     i;
236
237
0
  Assert(in->hasPrefix);
238
0
  centroid = DatumGetPointP(in->prefixDatum);
239
240
  /*
241
   * When ordering scan keys are specified, we've to calculate distance for
242
   * them.  In order to do that, we need calculate bounding boxes for all
243
   * children nodes.  Calculation of those bounding boxes on non-zero level
244
   * require knowledge of bounding box of upper node.  So, we save bounding
245
   * boxes to traversalValues.
246
   */
247
0
  if (in->norderbys > 0)
248
0
  {
249
0
    out->distances = (double **) palloc(sizeof(double *) * in->nNodes);
250
0
    out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes);
251
252
0
    if (in->level == 0)
253
0
    {
254
0
      double    inf = get_float8_infinity();
255
256
0
      infbbox.high.x = inf;
257
0
      infbbox.high.y = inf;
258
0
      infbbox.low.x = -inf;
259
0
      infbbox.low.y = -inf;
260
0
      bbox = &infbbox;
261
0
    }
262
0
    else
263
0
    {
264
0
      bbox = in->traversalValue;
265
0
      Assert(bbox);
266
0
    }
267
0
  }
268
269
0
  if (in->allTheSame)
270
0
  {
271
    /* Report that all nodes should be visited */
272
0
    out->nNodes = in->nNodes;
273
0
    out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
274
0
    for (i = 0; i < in->nNodes; i++)
275
0
    {
276
0
      out->nodeNumbers[i] = i;
277
278
0
      if (in->norderbys > 0)
279
0
      {
280
0
        MemoryContext oldCtx = MemoryContextSwitchTo(in->traversalMemoryContext);
281
282
        /* Use parent quadrant box as traversalValue */
283
0
        BOX      *quadrant = box_copy(bbox);
284
285
0
        MemoryContextSwitchTo(oldCtx);
286
287
0
        out->traversalValues[i] = quadrant;
288
0
        out->distances[i] = spg_key_orderbys_distances(BoxPGetDatum(quadrant), false,
289
0
                                 in->orderbys, in->norderbys);
290
0
      }
291
0
    }
292
0
    PG_RETURN_VOID();
293
0
  }
294
295
0
  Assert(in->nNodes == 4);
296
297
  /* "which" is a bitmask of quadrants that satisfy all constraints */
298
0
  which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
299
300
0
  for (i = 0; i < in->nkeys; i++)
301
0
  {
302
0
    Point    *query = DatumGetPointP(in->scankeys[i].sk_argument);
303
0
    BOX      *boxQuery;
304
305
0
    switch (in->scankeys[i].sk_strategy)
306
0
    {
307
0
      case RTLeftStrategyNumber:
308
0
        if (SPTEST(point_right, centroid, query))
309
0
          which &= (1 << 3) | (1 << 4);
310
0
        break;
311
0
      case RTRightStrategyNumber:
312
0
        if (SPTEST(point_left, centroid, query))
313
0
          which &= (1 << 1) | (1 << 2);
314
0
        break;
315
0
      case RTSameStrategyNumber:
316
0
        which &= (1 << getQuadrant(centroid, query));
317
0
        break;
318
0
      case RTBelowStrategyNumber:
319
0
      case RTOldBelowStrategyNumber:
320
0
        if (SPTEST(point_above, centroid, query))
321
0
          which &= (1 << 2) | (1 << 3);
322
0
        break;
323
0
      case RTAboveStrategyNumber:
324
0
      case RTOldAboveStrategyNumber:
325
0
        if (SPTEST(point_below, centroid, query))
326
0
          which &= (1 << 1) | (1 << 4);
327
0
        break;
328
0
      case RTContainedByStrategyNumber:
329
330
        /*
331
         * For this operator, the query is a box not a point.  We
332
         * cheat to the extent of assuming that DatumGetPointP won't
333
         * do anything that would be bad for a pointer-to-box.
334
         */
335
0
        boxQuery = DatumGetBoxP(in->scankeys[i].sk_argument);
336
337
0
        if (DatumGetBool(DirectFunctionCall2(box_contain_pt,
338
0
                           PointerGetDatum(boxQuery),
339
0
                           PointerGetDatum(centroid))))
340
0
        {
341
          /* centroid is in box, so all quadrants are OK */
342
0
        }
343
0
        else
344
0
        {
345
          /* identify quadrant(s) containing all corners of box */
346
0
          Point   p;
347
0
          int     r = 0;
348
349
0
          p = boxQuery->low;
350
0
          r |= 1 << getQuadrant(centroid, &p);
351
0
          p.y = boxQuery->high.y;
352
0
          r |= 1 << getQuadrant(centroid, &p);
353
0
          p = boxQuery->high;
354
0
          r |= 1 << getQuadrant(centroid, &p);
355
0
          p.x = boxQuery->low.x;
356
0
          r |= 1 << getQuadrant(centroid, &p);
357
358
0
          which &= r;
359
0
        }
360
0
        break;
361
0
      default:
362
0
        elog(ERROR, "unrecognized strategy number: %d",
363
0
           in->scankeys[i].sk_strategy);
364
0
        break;
365
0
    }
366
367
0
    if (which == 0)
368
0
      break;       /* no need to consider remaining conditions */
369
0
  }
370
371
0
  out->levelAdds = palloc(sizeof(int) * 4);
372
0
  for (i = 0; i < 4; ++i)
373
0
    out->levelAdds[i] = 1;
374
375
  /* We must descend into the quadrant(s) identified by which */
376
0
  out->nodeNumbers = (int *) palloc(sizeof(int) * 4);
377
0
  out->nNodes = 0;
378
379
0
  for (i = 1; i <= 4; i++)
380
0
  {
381
0
    if (which & (1 << i))
382
0
    {
383
0
      out->nodeNumbers[out->nNodes] = i - 1;
384
385
0
      if (in->norderbys > 0)
386
0
      {
387
0
        MemoryContext oldCtx = MemoryContextSwitchTo(in->traversalMemoryContext);
388
0
        BOX      *quadrant = getQuadrantArea(bbox, centroid, i);
389
390
0
        MemoryContextSwitchTo(oldCtx);
391
392
0
        out->traversalValues[out->nNodes] = quadrant;
393
394
0
        out->distances[out->nNodes] = spg_key_orderbys_distances(BoxPGetDatum(quadrant), false,
395
0
                                     in->orderbys, in->norderbys);
396
0
      }
397
398
0
      out->nNodes++;
399
0
    }
400
0
  }
401
402
0
  PG_RETURN_VOID();
403
0
}
404
405
406
Datum
407
spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
408
0
{
409
0
  spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
410
0
  spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
411
0
  Point    *datum = DatumGetPointP(in->leafDatum);
412
0
  bool    res;
413
0
  int     i;
414
415
  /* all tests are exact */
416
0
  out->recheck = false;
417
418
  /* leafDatum is what it is... */
419
0
  out->leafValue = in->leafDatum;
420
421
  /* Perform the required comparison(s) */
422
0
  res = true;
423
0
  for (i = 0; i < in->nkeys; i++)
424
0
  {
425
0
    Point    *query = DatumGetPointP(in->scankeys[i].sk_argument);
426
427
0
    switch (in->scankeys[i].sk_strategy)
428
0
    {
429
0
      case RTLeftStrategyNumber:
430
0
        res = SPTEST(point_left, datum, query);
431
0
        break;
432
0
      case RTRightStrategyNumber:
433
0
        res = SPTEST(point_right, datum, query);
434
0
        break;
435
0
      case RTSameStrategyNumber:
436
0
        res = SPTEST(point_eq, datum, query);
437
0
        break;
438
0
      case RTBelowStrategyNumber:
439
0
      case RTOldBelowStrategyNumber:
440
0
        res = SPTEST(point_below, datum, query);
441
0
        break;
442
0
      case RTAboveStrategyNumber:
443
0
      case RTOldAboveStrategyNumber:
444
0
        res = SPTEST(point_above, datum, query);
445
0
        break;
446
0
      case RTContainedByStrategyNumber:
447
448
        /*
449
         * For this operator, the query is a box not a point.  We
450
         * cheat to the extent of assuming that DatumGetPointP won't
451
         * do anything that would be bad for a pointer-to-box.
452
         */
453
0
        res = SPTEST(box_contain_pt, query, datum);
454
0
        break;
455
0
      default:
456
0
        elog(ERROR, "unrecognized strategy number: %d",
457
0
           in->scankeys[i].sk_strategy);
458
0
        break;
459
0
    }
460
461
0
    if (!res)
462
0
      break;
463
0
  }
464
465
0
  if (res && in->norderbys > 0)
466
    /* ok, it passes -> let's compute the distances */
467
0
    out->distances = spg_key_orderbys_distances(in->leafDatum, true,
468
0
                          in->orderbys, in->norderbys);
469
470
0
  PG_RETURN_BOOL(res);
471
0
}