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

estebanzimanyi / MobilityDB / 10095830605

25 Jul 2024 01:54PM UTC coverage: 95.089% (-0.009%) from 95.098%
10095830605

push

github

estebanzimanyi
Fix stbox_level_cmp

2 of 3 new or added lines in 1 file covered. (66.67%)

2 existing lines in 1 file now uncovered.

32009 of 33662 relevant lines covered (95.09%)

758706.85 hits per line

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

89.59
/mobilitydb/src/general/tnumber_gist.c
1
/*****************************************************************************
2
 *
3
 * This MobilityDB code is provided under The PostgreSQL License.
4
 * Copyright (c) 2016-2024, Université libre de Bruxelles and MobilityDB
5
 * contributors
6
 *
7
 * MobilityDB includes portions of PostGIS version 3 source code released
8
 * under the GNU General Public License (GPLv2 or later).
9
 * Copyright (c) 2001-2024, PostGIS contributors
10
 *
11
 * Permission to use, copy, modify, and distribute this software and its
12
 * documentation for any purpose, without fee, and without a written
13
 * agreement is hereby granted, provided that the above copyright notice and
14
 * this paragraph and the following two paragraphs appear in all copies.
15
 *
16
 * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR
17
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
18
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
19
 * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY
20
 * OF SUCH DAMAGE.
21
 *
22
 * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES,
23
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
24
 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON
25
 * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO
26
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
27
 *
28
 *****************************************************************************/
29

30
/**
31
 * @file
32
 * @brief R-tree GiST index for temporal integers and temporal floats
33
 *
34
 * These functions are based on those in the file `gistproc.c`.
35
 */
36

37
#include "pg_general/tnumber_gist.h"
38

39
/* C */
40
#include <assert.h>
41
#include <float.h>
42
/* PostgreSQL */
43
#include <postgres.h>
44
#include <utils/float.h>
45
#include <utils/timestamp.h>
46
/* MEOS */
47
#include <meos.h>
48
#include <meos_internal.h>
49
#include "general/span.h"
50
#include "general/tbox.h"
51
#include "general/temporal_boxops.h"
52
#include "general/type_util.h"
53
/* MobilityDB */
54
#include "pg_general/meos_catalog.h"
55
#include "pg_general/temporal.h"
56
#include "pg_general/span_gist.h"
57

58
/*****************************************************************************
59
 * GiST consistent methods
60
 *****************************************************************************/
61

62
/**
63
 * @brief Leaf-level consistency for temporal numbers
64
 *
65
 * Since temporal boxes do not distinguish between inclusive and
66
 * exclusive bounds, it is necessary to generalize the tests, e.g.,
67
 * - left : (box1->xmax < box2->xmin) => (box1->xmax <= box2->xmin)
68
 *   e.g., to take into account left([a,b],(b,c])
69
 * - right : (box1->xmin > box2->xmax) => (box1->xmin >= box2->xmax)
70
 *   e.g., to take into account right((b,c],[a,b])
71
 * and similarly for before and after
72
 *
73
 * @param[in] key Element in the index
74
 * @param[in] query Value being looked up in the index
75
 * @param[in] strategy Operator of the operator class being applied
76
 * @note This function is used for both GiST and SP-GiST indexes
77
 */
78
bool
79
tbox_index_consistent_leaf(const TBox *key, const TBox *query,
3,750,339✔
80
  StrategyNumber strategy)
81
{
82
  bool retval;
83

84
  switch (strategy)
3,750,339✔
85
  {
86
    case RTOverlapStrategyNumber:
385,598✔
87
      retval = overlaps_tbox_tbox(key, query);
385,598✔
88
      break;
385,598✔
89
    case RTContainsStrategyNumber:
310,280✔
90
      retval = contains_tbox_tbox(key, query);
310,280✔
91
      break;
310,280✔
92
    case RTContainedByStrategyNumber:
340,944✔
93
      retval = contained_tbox_tbox(key, query);
340,944✔
94
      break;
340,944✔
95
    case RTSameStrategyNumber:
310,280✔
96
      retval = same_tbox_tbox(key, query);
310,280✔
97
      break;
310,280✔
98
    case RTAdjacentStrategyNumber:
324,596✔
99
      retval = adjacent_tbox_tbox(key, query);
324,596✔
100
      break;
324,596✔
101
    case RTLeftStrategyNumber:
325,689✔
102
      retval = left_tbox_tbox(key, query);
325,689✔
103
      break;
325,689✔
104
    case RTOverLeftStrategyNumber:
243,505✔
105
      retval = overleft_tbox_tbox(key, query);
243,505✔
106
      break;
243,505✔
107
    case RTRightStrategyNumber:
308,659✔
108
      retval = right_tbox_tbox(key, query);
308,659✔
109
      break;
308,659✔
110
    case RTOverRightStrategyNumber:
205,268✔
111
      retval = overright_tbox_tbox(key, query);
205,268✔
112
      break;
205,268✔
113
    case RTBeforeStrategyNumber:
309,066✔
114
      retval = before_tbox_tbox(key, query);
309,066✔
115
      break;
309,066✔
116
    case RTOverBeforeStrategyNumber:
214,302✔
117
      retval = overbefore_tbox_tbox(key, query);
214,302✔
118
      break;
214,302✔
119
    case RTAfterStrategyNumber:
298,616✔
120
      retval = after_tbox_tbox(key, query);
298,616✔
121
      break;
298,616✔
122
    case RTOverAfterStrategyNumber:
173,536✔
123
      retval = overafter_tbox_tbox(key, query);
173,536✔
124
      break;
173,536✔
125
    default:
×
126
      elog(ERROR, "unrecognized strategy number: %d", strategy);
×
127
      retval = false;    /* keep compiler quiet */
128
      break;
129
  }
130
  return retval;
3,750,339✔
131
}
132

133
/**
134
 * @brief GiST internal-page consistent method for temporal numbers
135
 *
136
 * Return false if for all data items x below entry, the predicate
137
 * x op query must be false, where op is the oper corresponding to
138
 * strategy in the pg_amop table.
139
 *
140
 * @param[in] key Element in the index
141
 * @param[in] query Value being looked up in the index
142
 * @param[in] strategy Operator of the operator class being applied
143
 */
144
static bool
145
tnumber_gist_consistent(const TBox *key, const TBox *query,
6,800✔
146
  StrategyNumber strategy)
147
{
148
  bool retval;
149

150
  switch (strategy)
6,800✔
151
  {
152
    case RTOverlapStrategyNumber:
2,250✔
153
    case RTContainedByStrategyNumber:
154
      retval = overlaps_tbox_tbox(key, query);
2,250✔
155
      break;
2,250✔
156
    case RTContainsStrategyNumber:
1,080✔
157
    case RTSameStrategyNumber:
158
      retval = contains_tbox_tbox(key, query);
1,080✔
159
      break;
1,080✔
160
    case RTAdjacentStrategyNumber:
457✔
161
      retval = adjacent_tbox_tbox(key, query) ||
913✔
162
        overlaps_tbox_tbox(key, query);
456✔
163
      break;
457✔
164
    case RTLeftStrategyNumber:
15✔
165
      retval = ! overright_tbox_tbox(key, query);
15✔
166
      break;
15✔
167
    case RTOverLeftStrategyNumber:
626✔
168
      retval = ! right_tbox_tbox(key, query);
626✔
169
      break;
626✔
170
    case RTRightStrategyNumber:
316✔
171
      retval = ! overleft_tbox_tbox(key, query);
316✔
172
      break;
316✔
173
    case RTOverRightStrategyNumber:
313✔
174
      retval = ! left_tbox_tbox(key, query);
313✔
175
      break;
313✔
176
    case RTBeforeStrategyNumber:
750✔
177
      retval = ! overafter_tbox_tbox(key, query);
750✔
178
      break;
750✔
179
    case RTOverBeforeStrategyNumber:
750✔
180
      retval = ! after_tbox_tbox(key, query);
750✔
181
      break;
750✔
182
    case RTAfterStrategyNumber:
243✔
183
      retval = ! overbefore_tbox_tbox(key, query);
243✔
184
      break;
243✔
185
    case RTOverAfterStrategyNumber:
×
186
      retval = ! before_tbox_tbox(key, query);
×
187
      break;
×
188
    default:
×
189
      elog(ERROR, "unrecognized strategy number: %d", strategy);
×
190
      retval = false;    /* keep compiler quiet */
191
      break;
192
  }
193
  return retval;
6,800✔
194
}
195

196
/**
197
 * @brief Transform the query argument into a box initializing the dimensions
198
 * that must not be taken into account by the operators to infinity
199
 */
200
static bool
201
tnumber_gist_get_tbox(FunctionCallInfo fcinfo, TBox *result, Oid typid)
1,315,095✔
202
{
203
  meosType type = oid_type(typid);
1,315,095✔
204
  Span *s;
205
  if (tnumber_spantype(type))
1,315,095✔
206
  {
207
    s = PG_GETARG_SPAN_P(1);
326,620✔
208
    if (s == NULL)
326,620✔
209
      return false;
210
    numspan_set_tbox(s, result);
326,620✔
211
  }
212
  else if (type == T_TSTZSPAN)
988,475✔
213
  {
214
    s = PG_GETARG_SPAN_P(1);
317,150✔
215
    tstzspan_set_tbox(s, result);
317,150✔
216
  }
217
  else if (type == T_TBOX)
671,325✔
218
  {
219
    TBox *box = PG_GETARG_TBOX_P(1);
380,280✔
220
    if (box == NULL)
380,280✔
221
      return false;
222
    memcpy(result, box, sizeof(TBox));
223
  }
224
  else if (tnumber_type(type))
291,045✔
225
  {
226
    if (PG_ARGISNULL(1))
291,045✔
227
      return false;
228
    Datum tempdatum = PG_GETARG_DATUM(1);
291,045✔
229
    Temporal *temp = temporal_slice(tempdatum);
291,045✔
230
    tnumber_set_tbox(temp, result);
291,045✔
231
  }
232
  else
233
    elog(ERROR, "Unsupported type for indexing: %d", type);
×
234
  return true;
235
}
236

237
PGDLLEXPORT Datum Tnumber_gist_consistent(PG_FUNCTION_ARGS);
238
PG_FUNCTION_INFO_V1(Tnumber_gist_consistent);
11✔
239
/**
240
 * @brief GiST consistent method for temporal numbers
241
 */
242
Datum
243
Tnumber_gist_consistent(PG_FUNCTION_ARGS)
1,314,587✔
244
{
245
  GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
1,314,587✔
246
  StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
1,314,587✔
247
  Oid typid = PG_GETARG_OID(3);
1,314,587✔
248
  bool *recheck = (bool *) PG_GETARG_POINTER(4), result;
1,314,587✔
249
  const TBox *key = DatumGetTboxP(entry->key);
1,314,587✔
250
  TBox query;
251

252
  /*
253
   * All tests are lossy since boxes do not distinghish between inclusive
254
   * and exclusive bounds.
255
   */
256
  *recheck = true;
1,314,587✔
257

258
  if (key == NULL)
1,314,587✔
259
    PG_RETURN_BOOL(false);
260

261
  /* Transform the query into a box */
262
  if (! tnumber_gist_get_tbox(fcinfo, &query, typid))
1,314,587✔
263
    PG_RETURN_BOOL(false);
264

265
  if (GIST_LEAF(entry))
1,314,587✔
266
    result = tbox_index_consistent_leaf(key, &query, strategy);
1,307,787✔
267
  else
268
    result = tnumber_gist_consistent(key, &query, strategy);
6,800✔
269

270
  PG_RETURN_BOOL(result);
1,314,587✔
271
}
272

273
/*****************************************************************************
274
 * GiST union method
275
 *****************************************************************************/
276

277
/**
278
 * @brief Expand the first box to include the second one
279
 * @param[in,out] bbox1 Resulting box
280
 * @param[in] bbox2 Add-on box
281
 * @note This function is similar to tbox_expand in file tbox.c but uses
282
 *   NaN-aware comparisons for the value dimension
283
 */
284
static void
285
tbox_adjust(void *bbox1, void *bbox2)
271,699✔
286
{
287
  TBox *box1 = (TBox *) bbox1;
288
  TBox *box2 = (TBox *) bbox2;
289
  double xmin = FLOAT8_MIN(DatumGetFloat8(box1->span.lower),
271,699✔
290
    DatumGetFloat8(box2->span.lower));
291
  double xmax = FLOAT8_MAX(DatumGetFloat8(box1->span.upper),
271,699✔
292
    DatumGetFloat8(box2->span.upper));
293
  box1->span.lower = Float8GetDatum(xmin);
271,699✔
294
  box1->span.upper = Float8GetDatum(xmax);
271,699✔
295
  TimestampTz tmin = Min(DatumGetTimestampTz(box1->period.lower),
271,699✔
296
    DatumGetTimestampTz(box2->period.lower));
297
  TimestampTz tmax = Max(DatumGetTimestampTz(box1->period.upper),
271,699✔
298
    DatumGetTimestampTz(box2->period.upper));
299
  box1->period.lower = TimestampTzGetDatum(tmin);
271,699✔
300
  box1->period.upper = TimestampTzGetDatum(tmax);
271,699✔
301
  return;
271,699✔
302
}
303

304
PGDLLEXPORT Datum Tbox_gist_union(PG_FUNCTION_ARGS);
305
PG_FUNCTION_INFO_V1(Tbox_gist_union);
5✔
306
/**
307
 * @brief GiST union method for temporal numbers
308
 *
309
 * Return the minimal bounding box that encloses all the entries in entryvec
310
 */
311
Datum
312
Tbox_gist_union(PG_FUNCTION_ARGS)
73,363✔
313
{
314
  GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
73,363✔
315
  GISTENTRY *ent = entryvec->vector;
73,363✔
316
  TBox *result = tbox_cp(DatumGetTboxP(ent[0].key));
73,363✔
317
  for (int i = 1; i < entryvec->n; i++)
147,824✔
318
    tbox_adjust((void *)result, DatumGetPointer(ent[i].key));
74,461✔
319
  PG_RETURN_TBOX_P(result);
73,363✔
320
}
321

322
/*****************************************************************************
323
 * GiST compress method
324
 *****************************************************************************/
325

326
PGDLLEXPORT Datum Tnumber_gist_compress(PG_FUNCTION_ARGS);
327
PG_FUNCTION_INFO_V1(Tnumber_gist_compress);
10✔
328
/**
329
 * @brief GiST compress method for temporal numbers
330
 */
331
Datum
332
Tnumber_gist_compress(PG_FUNCTION_ARGS)
65,825✔
333
{
334
  GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
65,825✔
335
  if (entry->leafkey)
65,825✔
336
  {
337
    GISTENTRY *retval = palloc(sizeof(GISTENTRY));
57,984✔
338
    TBox *box = palloc(sizeof(TBox));
57,984✔
339
    Temporal *temp = temporal_slice(entry->key);
57,984✔
340
    tnumber_set_tbox(temp, box);
57,984✔
341
    gistentryinit(*retval, PointerGetDatum(box), entry->rel, entry->page,
57,984✔
342
      entry->offset, false);
343
    PG_RETURN_POINTER(retval);
57,984✔
344
  }
345
  PG_RETURN_POINTER(entry);
346
}
347

348
/*****************************************************************************
349
 * GiST penalty method
350
 *****************************************************************************/
351

352
/**
353
 * @brief Calculates the union of two temporal boxes
354
 * @param[in] a,b Input boxes
355
 * @param[out] new Resulting box
356
 * @note This function uses NaN-aware comparisons
357
 */
358
static void
359
tbox_union_rt(const TBox *a, const TBox *b, TBox *new)
1,318,770✔
360
{
361
  memset(new, 0, sizeof(TBox));
362
  double xmin = FLOAT8_MIN(DatumGetFloat8(a->span.lower),
1,318,770✔
363
    DatumGetFloat8(b->span.lower));
364
  double xmax = FLOAT8_MAX(DatumGetFloat8(a->span.upper),
1,318,770✔
365
    DatumGetFloat8(b->span.upper));
366
  new->span.lower = Float8GetDatum(xmin);
1,318,770✔
367
  new->span.upper = Float8GetDatum(xmax);
1,318,770✔
368
  TimestampTz tmin = Min(DatumGetTimestampTz(a->period.lower),
1,318,770✔
369
    DatumGetTimestampTz(b->period.lower));
370
  TimestampTz tmax = Max(DatumGetTimestampTz(a->period.upper),
1,318,770✔
371
    DatumGetTimestampTz(b->period.upper));
372
  new->period.lower = TimestampTzGetDatum(tmin);
1,318,770✔
373
  new->period.upper = TimestampTzGetDatum(tmax);
1,318,770✔
374
  return;
1,318,770✔
375
}
376

377
/**
378
 * @brief Return the size of a temporal box for penalty-calculation purposes
379
 * @note The result can be +Infinity, but not NaN
380
 */
381
static double
382
tbox_size(const TBox *box)
2,637,540✔
383
{
384
  /*
385
   * Check for zero-width cases.  Note that we define the size of a zero-
386
   * by-infinity box as zero.  It's important to special-case this somehow,
387
   * as naively multiplying infinity by zero will produce NaN.
388
   *
389
   * The less-than cases should not happen, but if they do, say "zero".
390
   */
391
  if (FLOAT8_LE(DatumGetFloat8(box->span.upper),
2,637,540✔
392
        DatumGetFloat8(box->span.lower)) ||
2,637,507✔
393
      datum_le(box->period.upper, box->period.lower, T_TIMESTAMPTZ))
2,637,507✔
394
    return 0.0;
33✔
395

396
  /*
397
   * We treat NaN as larger than +Infinity, so any distance involving a NaN
398
   * and a non-NaN is infinite.  Note the previous check eliminated the
399
   * possibility that the low fields are NaNs.
400
   */
401
  if (isnan(DatumGetFloat8(box->span.upper)))
2,637,507✔
402
    return get_float8_infinity();
403
  return (DatumGetFloat8(box->span.upper) - DatumGetFloat8(box->span.lower)) *
2,637,507✔
404
    (DatumGetTimestampTz(box->period.upper) -
2,637,507✔
405
      DatumGetTimestampTz(box->period.lower));
2,637,507✔
406
}
407

408
/**
409
 * @brief Return the amount by which the union of two temporal boxes is larger
410
 * than the area of the first one
411
 * @note The result can be +Infinity, but not NaN
412
 */
413
double
414
tbox_penalty(void *bbox1, void *bbox2)
1,318,770✔
415
{
416
  const TBox *original = (TBox *) bbox1;
417
  const TBox *new = (TBox *) bbox2;
418
  TBox unionbox;
419
  tbox_union_rt(original, new, &unionbox);
1,318,770✔
420
  return tbox_size(&unionbox) - tbox_size(original);
1,318,770✔
421
}
422

423
PGDLLEXPORT Datum Tbox_gist_penalty(PG_FUNCTION_ARGS);
424
PG_FUNCTION_INFO_V1(Tbox_gist_penalty);
5✔
425
/**
426
 * @brief GiST penalty method for temporal boxes
427
 * @note As in the R-tree paper, we use change in area as our penalty metric
428
 */
429
Datum
430
Tbox_gist_penalty(PG_FUNCTION_ARGS)
1,318,454✔
431
{
432
  GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
1,318,454✔
433
  GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
1,318,454✔
434
  float *result = (float *) PG_GETARG_POINTER(2);
1,318,454✔
435
  void *origbox = DatumGetPointer(origentry->key);
1,318,454✔
436
  void *newbox = DatumGetPointer(newentry->key);
1,318,454✔
437
  *result = (float) tbox_penalty(origbox, newbox);
1,318,454✔
438
  PG_RETURN_POINTER(result);
1,318,454✔
439
}
440

441
/*****************************************************************************
442
 * GiST picksplit method
443
 *****************************************************************************/
444

445
/**
446
 * @brief Interval comparison function by lower bound of the intervals
447
 */
448
int
449
interval_cmp_lower(const void *i1, const void *i2)
2,311,144✔
450
{
451
  double lower1 = ((const SplitInterval *) i1)->lower,
2,311,144✔
452
         lower2 = ((const SplitInterval *) i2)->lower;
2,311,144✔
453

454
  return float8_cmp_internal(lower1, lower2);
2,311,144✔
455
}
456

457
/**
458
 * @brief Interval comparison function by upper bound of the intervals
459
 */
460
int
461
interval_cmp_upper(const void *i1, const void *i2)
2,312,887✔
462
{
463
  double upper1 = ((const SplitInterval *) i1)->upper,
2,312,887✔
464
         upper2 = ((const SplitInterval *) i2)->upper;
2,312,887✔
465

466
  return float8_cmp_internal(upper1, upper2);
2,312,887✔
467
}
468

469
/**
470
 * @brief Replace negative (or NaN) value with zero
471
 */
472
inline float
473
non_negative(float val)
×
474
{
475
  if (val >= 0.0f)
83,983✔
476
    return val;
477
  else
478
    return 0.0f;
17,352✔
479
}
480

481
/**
482
 * @brief Consider replacement of currently selected split with the better one
483
 */
484
inline void
485
bbox_gist_consider_split(ConsiderSplitContext *context, int dimNum,
638,299✔
486
  meosType bboxtype, double rightLower, int minLeftCount, double leftUpper,
487
  int maxLeftCount)
488
{
489
  int leftCount, rightCount;
490
  float4 ratio, overlap;
491
  assert(bboxtype == T_TBOX || bboxtype == T_STBOX);
492

493
  /*
494
   * Calculate entries distribution ratio assuming most uniform distribution
495
   * of common entries.
496
   */
497
  if (minLeftCount >= (context->entriesCount + 1) / 2)
638,299✔
498
  {
499
    leftCount = minLeftCount;
500
  }
501
  else
502
  {
503
    if (maxLeftCount <= context->entriesCount / 2)
382,889✔
504
      leftCount = maxLeftCount;
505
    else
506
      leftCount = context->entriesCount / 2;
507
  }
508
  rightCount = context->entriesCount - leftCount;
638,299✔
509

510
  /*
511
   * Ratio of split - quotient between size of lesser group and total
512
   * entries count.
513
   */
514
  ratio = ((float4) Min(leftCount, rightCount)) /
638,299✔
515
    ((float4) context->entriesCount);
638,299✔
516

517
  if (ratio > LIMIT_RATIO)
638,299✔
518
  {
519
    double range;
520
    bool selectthis = false;
521

522
    /*
523
     * The ratio is acceptable, so compare current split with previously
524
     * selected one. Between splits of one dimension we search for minimal
525
     * overlap (allowing negative values) and minimal ration (between same
526
     * overlaps. We switch dimension if find less overlap (non-negative)
527
     * or less range with same overlap.
528
     */
529
    if (bboxtype == T_TBOX)
337,719✔
530
    {
531
      TBox *bbox = (TBox *) &context->boundingBox;
532
      if (dimNum == 0)
152,709✔
533
        range = DatumGetFloat8(bbox->span.upper) -
77,896✔
534
          DatumGetFloat8(bbox->span.lower);
77,896✔
535
      else
536
        range = (double) (DatumGetTimestampTz(bbox->period.upper) -
74,813✔
537
          DatumGetTimestampTz(bbox->period.lower));
74,813✔
538
    }
539
    else /* bboxtype == T_STBOX */
540
    {
541
      STBox *bbox = (STBox *) &context->boundingBox;
542
      if (dimNum == 0)
185,010✔
543
        range = bbox->xmax - bbox->xmin;
50,925✔
544
      else if (dimNum == 1)
134,085✔
545
        range = bbox->ymax - bbox->ymin;
50,849✔
546
      else if (dimNum == 2)
83,236✔
547
        range = bbox->zmax - bbox->zmin;
49,520✔
548
      else
549
        range = (double) (DatumGetTimestampTz(bbox->period.upper) -
33,716✔
550
          DatumGetTimestampTz(bbox->period.lower));
33,716✔
551
    }
552

553
    overlap = (float4) ((leftUpper - rightLower) / range);
337,719✔
554

555
    /* If there is no previous selection, select this */
556
    if (context->first)
337,719✔
557
      selectthis = true;
558
    else if (context->dim == dimNum)
336,278✔
559
    {
560
      /*
561
       * Within the same dimension, choose the new split if it has a
562
       * smaller overlap, or same overlap but better ratio.
563
       */
564
      if (overlap < context->overlap ||
252,295✔
565
        (overlap == context->overlap && ratio > context->ratio))
2,947✔
566
        selectthis = true;
567
    }
568
    else
569
    {
570
      /*
571
       * Across dimensions, choose the new split if it has a smaller
572
       * *non-negative* overlap, or same *non-negative* overlap but
573
       * bigger range. This condition differs from the one described in
574
       * the article. On the datasets where leaf MBRs don't overlap
575
       * themselves, non-overlapping splits (i.e. splits which have zero
576
       * *non-negative* overlap) are frequently possible. In this case
577
       * splits tends to be along one dimension, because most distant
578
       * non-overlapping splits (i.e. having lowest negative overlap)
579
       * appears to be in the same dimension as in the previous split.
580
       * Therefore MBRs appear to be very prolonged along another
581
       * dimension, which leads to bad search performance. Using range
582
       * as the second split criteria makes MBRs more quadratic. Using
583
       * *non-negative* overlap instead of overlap as the first split
584
       * criteria gives to range criteria a chance to matter, because
585
       * non-overlapping splits are equivalent in this criteria.
586
       */
587
      if (non_negative(overlap) < non_negative(context->overlap) ||
92,165✔
588
        (range > context->range &&
82,570✔
589
         non_negative(overlap) <= non_negative(context->overlap)))
590
        selectthis = true;
591
    }
592

593
    if (selectthis)
594
    {
595
      /* save information about selected split */
596
      context->first = false;
49,233✔
597
      context->ratio = ratio;
49,233✔
598
      context->range = range;
49,233✔
599
      context->overlap = overlap;
49,233✔
600
      context->rightLower = rightLower;
49,233✔
601
      context->leftUpper = leftUpper;
49,233✔
602
      context->dim = dimNum;
49,233✔
603
    }
604
  }
605
}
638,299✔
606

607
/* Helper macros to place an entry in the left or right group */
608
#define PLACE_LEFT(box, off) \
609
  do {  \
610
    if (v->spl_nleft > 0)  \
611
      bbox_adjust(leftBox, box);  \
612
    else  \
613
      memcpy(leftBox, box, bbox_size);  \
614
    v->spl_left[v->spl_nleft++] = off;  \
615
  } while(0)
616

617
#define PLACE_RIGHT(box, off)  \
618
  do {  \
619
    if (v->spl_nright > 0)  \
620
      bbox_adjust(rightBox, box);  \
621
    else  \
622
      memcpy(rightBox, box, bbox_size);  \
623
    v->spl_right[v->spl_nright++] = off;  \
624
  } while(0)
625

626
/**
627
 * @brief Trivial split: half of entries will be placed on one page and the
628
 * other half on another page
629
 */
630
void
631
bbox_gist_fallback_split(GistEntryVector *entryvec, GIST_SPLITVEC *v,
×
632
  meosType bboxtype, void (*bbox_adjust)(void *, void *))
633
{
634
  OffsetNumber i;
635
  OffsetNumber maxoff = (OffsetNumber) (entryvec->n - 1);
×
636
  /* Split entries before this to left page, after to right: */
637
  OffsetNumber split_idx = (OffsetNumber) ((maxoff - FirstOffsetNumber) / 2 +
×
638
    FirstOffsetNumber);
639

640
  size_t nbytes = (maxoff + 2) * sizeof(OffsetNumber);
×
641
  v->spl_left = palloc(nbytes);
×
642
  v->spl_right = palloc(nbytes);
×
643
  v->spl_nleft = v->spl_nright = 0;
×
644

645
  /* Allocate bounding boxes of left and right groups */
646
  size_t bbox_size = bbox_get_size(bboxtype);
×
647
  void *leftBox = palloc0(bbox_size);
×
648
  void *rightBox = palloc0(bbox_size);
×
649

650
  for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
×
651
  {
652
    void *box = DatumGetPointer(entryvec->vector[i].key);
×
653
    if (i < split_idx)
×
654
      PLACE_LEFT(box, i);
×
655
    else
656
      PLACE_RIGHT(box, i);
×
657
  }
658

659
  v->spl_ldatum = PointerGetDatum(leftBox);
×
660
  v->spl_rdatum = PointerGetDatum(rightBox);
×
661
  return;
×
662
}
663

664
/**
665
 * @brief Double sorting split algorithm
666
 *
667
 * The algorithm finds split of boxes by considering splits along each axis.
668
 * Each entry is first projected as an interval on the X-axis, and different
669
 * ways to split the intervals into two groups are considered, trying to
670
 * minimize the overlap of the groups. Then the same is repeated for the
671
 * Y-axis, and the overall best split is chosen. The quality of a split is
672
 * determined by overlap along that axis and some other criteria (see
673
 * bbox_gist_consider_split).
674
 *
675
 * After that, all the entries are divided into three groups:
676
 *
677
 * 1. Entries which should be placed to the left group
678
 * 2. Entries which should be placed to the right group
679
 * 3. "Common entries" which can be placed to any of groups without affecting
680
 *    of overlap along selected axis.
681
 *
682
 * The common entries are distributed by minimizing penalty.
683
 *
684
 * For details see:
685
 * "A new double sorting-based node splitting algorithm for R-tree", A. Korotkov
686
 * http://syrcose.ispras.ru/2011/files/SYRCoSE2011_Proceedings.pdf#page=36
687
 */
688
Datum
689
bbox_gist_picksplit(FunctionCallInfo fcinfo, meosType bboxtype,
1,441✔
690
  void (*bbox_adjust)(void *, void *), double (*bbox_penalty)(void *, void *))
691
{
692
  GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
1,441✔
693
  GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
1,441✔
694
  OffsetNumber i, maxoff;
695
  ConsiderSplitContext context;
696
  void *box, *leftBox, *rightBox;
697
  SplitInterval *intervalsLower, *intervalsUpper;
698
  CommonEntry *common_entries;
699
  int nentries, common_entries_count, dim;
700
  assert(bboxtype == T_TBOX || bboxtype == T_STBOX);
701

702
  int maxdims = bbox_max_dims(bboxtype);
1,441✔
703
  size_t bbox_size = bbox_get_size(bboxtype);
1,441✔
704
  bool hasz = false;
705

706
  memset(&context, 0, sizeof(ConsiderSplitContext));
707
  maxoff = (OffsetNumber) (entryvec->n - 1);
1,441✔
708
  nentries = context.entriesCount = maxoff - FirstOffsetNumber + 1;
1,441✔
709

710
  /* Allocate arrays for intervals along axes */
711
  intervalsLower = palloc(sizeof(SplitInterval) * nentries);
1,441✔
712
  intervalsUpper = palloc(sizeof(SplitInterval) * nentries);
1,441✔
713

714
  /*
715
   * Calculate the overall minimum bounding box over all the entries.
716
   */
717
  for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
142,649✔
718
  {
719
    box = DatumGetPointer(entryvec->vector[i].key);
141,208✔
720
    if (i == FirstOffsetNumber)
141,208✔
721
      memcpy(&context.boundingBox, box, bbox_size);
722
    else
723
      bbox_adjust(&context.boundingBox, box);
139,767✔
724
  }
725

726
  /* Determine whether there is a Z dimension for spatiotemporal boxes */
727
  if (bboxtype == T_STBOX)
1,441✔
728
  {
729
    box = DatumGetPointer(entryvec->vector[FirstOffsetNumber].key);
515✔
730
    hasz = MEOS_FLAGS_GET_Z(((STBox *) box)->flags);
515✔
731
  }
732

733
  /*
734
   * Iterate over axes for optimal split searching
735
   */
736
  context.first = true;    /* nothing selected yet */
1,441✔
737
  for (dim = 0; dim < maxdims; dim++)
5,353✔
738
  {
739
    double leftUpper, rightLower;
740
    int i1, i2;
741

742
    /* Skip the process for Z dimension if it is missing */
743
    if (dim == 2 && ! hasz)
3,912✔
744
      continue;
15✔
745

746
    /* Project each entry as an interval on the selected axis */
747
    for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
367,513✔
748
    {
749
      box = DatumGetPointer(entryvec->vector[i].key);
363,616✔
750
      if (bboxtype == T_TBOX)
363,616✔
751
      {
752
        if (dim == 0)
200,016✔
753
        {
754
          intervalsLower[i - FirstOffsetNumber].lower =
100,008✔
755
            DatumGetFloat8(((TBox *) box)->span.lower);
100,008✔
756
          intervalsLower[i - FirstOffsetNumber].upper =
100,008✔
757
            DatumGetFloat8(((TBox *) box)->span.upper);
100,008✔
758
        }
759
        else
760
        {
761
          intervalsLower[i - FirstOffsetNumber].lower =
100,008✔
762
            (double) DatumGetTimestampTz(((TBox *) box)->period.lower);
100,008✔
763
          intervalsLower[i - FirstOffsetNumber].upper =
100,008✔
764
            (double) DatumGetTimestampTz(((TBox *) box)->period.upper);
100,008✔
765
        }
766
      }
767
      else /* bboxtype == T_STBOX */
768
      {
769
        if (dim == 0)
163,600✔
770
        {
771
          intervalsLower[i - FirstOffsetNumber].lower = ((STBox *) box)->xmin;
41,200✔
772
          intervalsLower[i - FirstOffsetNumber].upper = ((STBox *) box)->xmax;
41,200✔
773
        }
774
        else if (dim == 1)
122,400✔
775
        {
776
          intervalsLower[i - FirstOffsetNumber].lower = ((STBox *) box)->ymin;
41,200✔
777
          intervalsLower[i - FirstOffsetNumber].upper = ((STBox *) box)->ymax;
41,200✔
778
        }
779
        else if (dim == 2)
81,200✔
780
        {
781
          intervalsLower[i - FirstOffsetNumber].lower = ((STBox *) box)->zmin;
40,000✔
782
          intervalsLower[i - FirstOffsetNumber].upper = ((STBox *) box)->zmax;
40,000✔
783
        }
784
        else
785
        {
786
          intervalsLower[i - FirstOffsetNumber].lower =
41,200✔
787
            (double) DatumGetTimestampTz(((STBox *) box)->period.lower);
41,200✔
788
          intervalsLower[i - FirstOffsetNumber].upper =
41,200✔
789
            (double) DatumGetTimestampTz(((STBox *) box)->period.upper);
41,200✔
790
        }
791
      }
792
    }
793

794
    /*
795
     * Make two arrays of intervals: one sorted by lower bound and another
796
     * sorted by upper bound.
797
     */
798
    memcpy(intervalsUpper, intervalsLower, sizeof(SplitInterval) * nentries);
799
    qsort(intervalsLower, (size_t) nentries, sizeof(SplitInterval),
3,897✔
800
      (qsort_comparator) interval_cmp_lower);
801
    qsort(intervalsUpper, (size_t) nentries, sizeof(SplitInterval),
3,897✔
802
      (qsort_comparator) interval_cmp_upper);
803

804
    /*----
805
     * The goal is to form a left and right interval, so that every entry
806
     * interval is contained in either left or right interval (or both).
807
     *
808
     * For example, with the intervals (0,1), (1,3), (2,3), (2,4):
809
     *
810
     * 0 1 2 3 4
811
     * +-+
812
     *   +---+
813
     *     +-+
814
     *     +---+
815
     *
816
     * The left and right intervals are of the form (0,a) and (b,4).
817
     * We first consider splits where b is the lower bound of an entry.
818
     * We iterate through all entries, and for each b, calculate the
819
     * smallest possible a. Then we consider splits where a is the
820
     * upper bound of an entry, and for each a, calculate the greatest
821
     * possible b.
822
     *
823
     * In the above example, the first loop would consider splits:
824
     * b=0: (0,1)-(0,4)
825
     * b=1: (0,1)-(1,4)
826
     * b=2: (0,3)-(2,4)
827
     *
828
     * And the second loop:
829
     * a=1: (0,1)-(1,4)
830
     * a=3: (0,3)-(2,4)
831
     * a=4: (0,4)-(2,4)
832
     */
833

834
    /*
835
     * Iterate over lower bound of right group, finding smallest possible
836
     * upper bound of left group.
837
     */
838
    i1 = 0;
839
    i2 = 0;
840
    rightLower = intervalsLower[i1].lower;
3,897✔
841
    leftUpper = intervalsUpper[i2].lower;
3,897✔
842
    while (true)
843
    {
844
      /*
845
       * Find next lower bound of right group.
846
       */
847
      while (i1 < nentries && FLOAT8_EQ(rightLower, intervalsLower[i1].lower))
1,005,381✔
848
      {
849
        if (FLOAT8_LT(leftUpper, intervalsLower[i1].upper))
363,616✔
850
          leftUpper = intervalsLower[i1].upper;
181,072✔
851
        i1++;
363,616✔
852
      }
853
      if (i1 >= nentries)
322,831✔
854
        break;
855
      rightLower = intervalsLower[i1].lower;
318,934✔
856

857
      /*
858
       * Find count of intervals which anyway should be placed to the
859
       * left group.
860
       */
861
      while (i2 < nentries &&
1,271,853✔
862
           FLOAT8_LE(intervalsUpper[i2].upper, leftUpper))
591,637✔
863
        i2++;
361,282✔
864

865
      /*
866
       * Consider found split.
867
       */
868
      bbox_gist_consider_split(&context, dim, bboxtype, rightLower, i1,
318,934✔
869
        leftUpper, i2);
870
    }
871

872
    /*
873
     * Iterate over upper bound of left group finding greatest possible
874
     * lower bound of right group.
875
     */
876
    i1 = nentries - 1;
3,897✔
877
    i2 = nentries - 1;
878
    rightLower = intervalsLower[i1].upper;
3,897✔
879
    leftUpper = intervalsUpper[i2].upper;
3,897✔
880
    while (true)
881
    {
882
      /*
883
       * Find next upper bound of left group.
884
       */
885
      while (i2 >= 0 && FLOAT8_EQ(leftUpper, intervalsUpper[i2].upper))
1,006,243✔
886
      {
887
        if (FLOAT8_GT(rightLower, intervalsUpper[i2].lower))
363,616✔
888
          rightLower = intervalsUpper[i2].lower;
181,053✔
889
        i2--;
363,616✔
890
      }
891
      if (i2 < 0)
323,262✔
892
        break;
893
      leftUpper = intervalsUpper[i2].upper;
319,365✔
894

895
      /*
896
       * Find count of intervals which anyway should be placed to the
897
       * right group.
898
       */
899
      while (i1 >= 0 && FLOAT8_GE(intervalsLower[i1].lower, rightLower))
680,661✔
900
        i1--;
361,296✔
901

902
      /*
903
       * Consider found split.
904
       */
905
      bbox_gist_consider_split(&context, dim, bboxtype, rightLower, i1 + 1,
319,365✔
906
        leftUpper, i2 + 1);
907
    }
908
  }
909

910
  pfree(intervalsLower); pfree(intervalsUpper);
1,441✔
911

912
  /*
913
   * If we failed to find any acceptable splits, use trivial split.
914
   */
915
  if (context.first)
1,441✔
916
  {
917
    bbox_gist_fallback_split(entryvec, v, bboxtype, &tbox_adjust);
×
918
    PG_RETURN_POINTER(v);
×
919
  }
920

921
  /*
922
   * Ok, we have now selected the split across one axis.
923
   *
924
   * While considering the splits, we already determined that there will be
925
   * enough entries in both groups to reach the desired ratio, but we did
926
   * not memorize which entries go to which group. So determine that now.
927
   */
928

929
  /* Allocate vectors for results */
930
  v->spl_left = palloc(sizeof(OffsetNumber) * nentries);
1,441✔
931
  v->spl_right = palloc(sizeof(OffsetNumber) * nentries);
1,441✔
932
  v->spl_nleft = v->spl_nright = 0;
1,441✔
933

934
  /* Allocate bounding boxes of left and right groups */
935
  leftBox = palloc0(bbox_size);
1,441✔
936
  rightBox = palloc0(bbox_size);
1,441✔
937

938
  /*
939
   * Allocate an array for "common entries" - entries which can be placed to
940
   * either group without affecting overlap along selected axis.
941
   */
942
  common_entries_count = 0;
943
  common_entries = palloc(sizeof(CommonEntry) * nentries);
1,441✔
944

945
  /*
946
   * Distribute entries which can be distributed unambiguously, and collect
947
   * common entries.
948
   */
949
  for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
142,649✔
950
  {
951
    double lower, upper;
952

953
    /*
954
     * Get upper and lower bounds along selected axis.
955
     */
956
    box = DatumGetPointer(entryvec->vector[i].key);
141,208✔
957
    assert(bboxtype == T_TBOX || bboxtype == T_STBOX);
958
    if (bboxtype == T_TBOX)
141,208✔
959
    {
960
      if (context.dim == 0)
100,008✔
961
      {
962
        lower = DatumGetFloat8(((TBox *) box)->span.lower);
×
963
        upper = DatumGetFloat8(((TBox *) box)->span.upper);
×
964
      }
965
      else
966
      {
967
        lower = (double) DatumGetTimestampTz(((TBox *) box)->period.lower);
100,008✔
968
        upper = (double) DatumGetTimestampTz(((TBox *) box)->period.upper);
100,008✔
969
      }
970
    }
971
    else /* bboxtype == T_STBOX */
972
    {
973
      if (context.dim == 0)
41,200✔
974
      {
975
        lower = ((STBox *) box)->xmin;
×
976
        upper = ((STBox *) box)->xmax;
×
977
      }
978
      else if (context.dim == 1)
41,200✔
979
      {
980
        lower = ((STBox *) box)->ymin;
×
981
        upper = ((STBox *) box)->ymax;
×
982
      }
983
      else if (context.dim == 2 && hasz)
41,200✔
984
      {
985
        lower = ((STBox *) box)->zmin;
×
986
        upper = ((STBox *) box)->zmax;
×
987
      }
988
      else
989
      {
990
        lower = (double) DatumGetTimestampTz(((TBox *) box)->period.lower);
41,200✔
991
        upper = (double) DatumGetTimestampTz(((TBox *) box)->period.upper);
41,200✔
992
      }
993
    }
994

995
    if (FLOAT8_LE(upper, context.leftUpper))
141,208✔
996
    {
997
      /* Fits to the left group */
998
      if (FLOAT8_GE(lower, context.rightLower))
70,045✔
999
      {
1000
        /* Fits also to the right group, so "common entry" */
1001
        common_entries[common_entries_count++].index = i;
326✔
1002
      }
1003
      else
1004
      {
1005
        /* Doesn't fit to the right group, so join to the left group */
1006
        PLACE_LEFT(box, i);
71,158✔
1007
      }
1008
    }
1009
    else
1010
    {
1011
      /*
1012
       * Each entry should fit on either left or right group. Since this
1013
       * entry didn't fit on the left group, it better fit in the right
1014
       * group.
1015
       */
1016
      assert(FLOAT8_GE(lower, context.rightLower));
1017

1018
      /* Doesn't fit to the left group, so join to the right group */
1019
      PLACE_RIGHT(box, i);
72,603✔
1020
    }
1021
  }
1022

1023
  /*
1024
   * Distribute "common entries", if any.
1025
   */
1026
  if (common_entries_count > 0)
1,441✔
1027
  {
1028
    /*
1029
     * Calculate minimum number of entries that must be placed in both
1030
     * groups, to reach LIMIT_RATIO.
1031
     */
1032
    int m = (int) ceil(LIMIT_RATIO * (double) nentries);
13✔
1033
    int j;
1034

1035
    /*
1036
     * Calculate delta between penalties of join "common entries" to
1037
     * different groups.
1038
     */
1039
    for (j = 0; j < common_entries_count; j++)
339✔
1040
    {
1041
      box = DatumGetPointer(entryvec->vector[common_entries[j].index].key);
326✔
1042
      common_entries[j].delta = fabs(bbox_penalty(leftBox, box) -
652✔
1043
        bbox_penalty(rightBox, box));
326✔
1044
    }
1045

1046
    /*
1047
     * Sort "common entries" by calculated deltas in order to distribute
1048
     * the most ambiguous entries first.
1049
     */
1050
    qsort(common_entries, (size_t) common_entries_count, sizeof(CommonEntry),
13✔
1051
      common_entry_cmp);
1052

1053
    /*
1054
     * Distribute "common entries" between groups.
1055
     */
1056
    for (j = 0; j < common_entries_count; j++)
339✔
1057
    {
1058
      OffsetNumber idx = (OffsetNumber) (common_entries[j].index);
326✔
1059
      box = DatumGetPointer(entryvec->vector[idx].key);
326✔
1060

1061
      /*
1062
       * Check if we have to place this entry in either group to achieve
1063
       * LIMIT_RATIO.
1064
       */
1065
      if (v->spl_nleft + (common_entries_count - j) <= m)
326✔
1066
        PLACE_LEFT(box, idx);
158✔
1067
      else if (v->spl_nright + (common_entries_count - j) <= m)
170✔
1068
        PLACE_RIGHT(box, idx);
171✔
1069
      else
1070
      {
1071
        /* Otherwise select the group by minimal penalty */
UNCOV
1072
        if (tbox_penalty(leftBox, box) < tbox_penalty(rightBox, box))
×
1073
          PLACE_LEFT(box, idx);
×
1074
        else
UNCOV
1075
          PLACE_RIGHT(box, idx);
×
1076
      }
1077
    }
1078
  }
1079

1080
  v->spl_ldatum = PointerGetDatum(leftBox);
1,441✔
1081
  v->spl_rdatum = PointerGetDatum(rightBox);
1,441✔
1082

1083
  pfree(common_entries);
1,441✔
1084

1085
  PG_RETURN_POINTER(v);
1,441✔
1086
}
1087

1088
PGDLLEXPORT Datum Tbox_gist_picksplit(PG_FUNCTION_ARGS);
1089
PG_FUNCTION_INFO_V1(Tbox_gist_picksplit);
5✔
1090
/**
1091
 * @brief GiST picksplit method for temporal numbers
1092
 */
1093
Datum
1094
Tbox_gist_picksplit(PG_FUNCTION_ARGS)
926✔
1095
{
1096
  return bbox_gist_picksplit(fcinfo, T_TBOX, &tbox_adjust, &tbox_penalty);
926✔
1097
}
1098

1099
/*****************************************************************************
1100
 * GiST same method
1101
 *****************************************************************************/
1102

1103
PGDLLEXPORT Datum Tbox_gist_same(PG_FUNCTION_ARGS);
1104
PG_FUNCTION_INFO_V1(Tbox_gist_same);
5✔
1105
/**
1106
 * @brief GiST same method for temporal numbers
1107
 * Return true only when boxes are exactly the same.  We can't use fuzzy
1108
 * comparisons here without breaking index consistency; therefore, this isn't
1109
 * equivalent to box_same().
1110
 */
1111
Datum
1112
Tbox_gist_same(PG_FUNCTION_ARGS)
73,351✔
1113
{
1114
  TBox *b1 = PG_GETARG_TBOX_P(0);
73,351✔
1115
  TBox *b2 = PG_GETARG_TBOX_P(1);
73,351✔
1116
  bool *result = (bool *) PG_GETARG_POINTER(2);
73,351✔
1117
  if (b1 && b2)
73,351✔
1118
    *result = FLOAT8_EQ(DatumGetFloat8(b1->span.lower),
73,351✔
1119
        DatumGetFloat8(b2->span.lower)) &&
72,342✔
1120
      FLOAT8_EQ(DatumGetFloat8(b1->span.upper),
72,342✔
1121
        DatumGetFloat8(b2->span.upper)) &&
71,273✔
1122
      /* Equality test does not require to use DatumGetTimestampTz */
1123
      (b1->period.lower == b2->period.lower) &&
214,992✔
1124
      (b1->period.upper == b2->period.upper);
69,299✔
1125
  else
1126
    *result = (b1 == NULL && b2 == NULL);
×
1127
  PG_RETURN_POINTER(result);
73,351✔
1128
}
1129

1130
/*****************************************************************************
1131
 * GiST distance method
1132
 *****************************************************************************/
1133

1134
PGDLLEXPORT Datum Tbox_gist_distance(PG_FUNCTION_ARGS);
1135
PG_FUNCTION_INFO_V1(Tbox_gist_distance);
5✔
1136
/**
1137
 * @brief GiST support distance function that takes in a query and an entry and
1138
 * returns the "distance" between them
1139
*/
1140
Datum
1141
Tbox_gist_distance(PG_FUNCTION_ARGS)
508✔
1142
{
1143
  GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
508✔
1144
  Oid typid = PG_GETARG_OID(3);
508✔
1145
  bool *recheck = (bool *) PG_GETARG_POINTER(4);
508✔
1146
  TBox *key = (TBox *) DatumGetPointer(entry->key);
508✔
1147
  TBox query;
1148
  double distance;
1149

1150
  /* The index is lossy for leaf levels */
1151
  if (GIST_LEAF(entry))
508✔
1152
    *recheck = true;
301✔
1153

1154
  if (key == NULL)
508✔
1155
    PG_RETURN_FLOAT8(DBL_MAX);
1156

1157
  /* Transform the query into a box */
1158
  if (! tnumber_gist_get_tbox(fcinfo, &query, typid))
508✔
1159
    PG_RETURN_FLOAT8(DBL_MAX);
1160

1161
  /* Since we only have boxes we'll return the minimum possible distance,
1162
   * and let the recheck sort things out in the case of leaves */
1163
  distance = nad_tbox_tbox(key, &query);
508✔
1164

1165
  PG_RETURN_FLOAT8(distance);
508✔
1166
}
1167

1168
/*****************************************************************************/
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

© 2025 Coveralls, Inc