Coverage Report

Created: 2026-08-14 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/spgist/spgtextproc.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * spgtextproc.c
4
 *    implementation of radix tree (compressed trie) over text
5
 *
6
 * In a text_ops SPGiST index, inner tuples can have a prefix which is the
7
 * common prefix of all strings indexed under that tuple.  The node labels
8
 * represent the next byte of the string(s) after the prefix.  Assuming we
9
 * always use the longest possible prefix, we will get more than one node
10
 * label unless the prefix length is restricted by SPGIST_MAX_PREFIX_LENGTH.
11
 *
12
 * To reconstruct the indexed string for any index entry, concatenate the
13
 * inner-tuple prefixes and node labels starting at the root and working
14
 * down to the leaf entry, then append the datum in the leaf entry.
15
 * (While descending the tree, "level" is the number of bytes reconstructed
16
 * so far.)
17
 *
18
 * However, there are two special cases for node labels: -1 indicates that
19
 * there are no more bytes after the prefix-so-far, and -2 indicates that we
20
 * had to split an existing allTheSame tuple (in such a case we have to create
21
 * a node label that doesn't correspond to any string byte).  In either case,
22
 * the node label does not contribute anything to the reconstructed string.
23
 *
24
 * Previously, we used a node label of zero for both special cases, but
25
 * this was problematic because one can't tell whether a string ending at
26
 * the current level can be pushed down into such a child node.  For
27
 * backwards compatibility, we still support such node labels for reading;
28
 * but no new entries will ever be pushed down into a zero-labeled child.
29
 * No new entries ever get pushed into a -2-labeled child, either.
30
 *
31
 *
32
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
33
 * Portions Copyright (c) 1994, Regents of the University of California
34
 *
35
 * IDENTIFICATION
36
 *      src/backend/access/spgist/spgtextproc.c
37
 *
38
 *-------------------------------------------------------------------------
39
 */
40
#include "postgres.h"
41
42
#include "access/spgist.h"
43
#include "catalog/pg_type.h"
44
#include "common/int.h"
45
#include "mb/pg_wchar.h"
46
#include "utils/datum.h"
47
#include "utils/fmgrprotos.h"
48
#include "utils/pg_locale.h"
49
#include "utils/varlena.h"
50
#include "varatt.h"
51
52
53
/*
54
 * In the worst case, an inner tuple in a text radix tree could have as many
55
 * as 258 nodes (one for each possible byte value, plus the two special
56
 * cases).  Each node can take 16 bytes on MAXALIGN=8 machines.  The inner
57
 * tuple must fit on an index page of size BLCKSZ.  Rather than assuming we
58
 * know the exact amount of overhead imposed by page headers, tuple headers,
59
 * etc, we leave 100 bytes for that (the actual overhead should be no more
60
 * than 56 bytes at this writing, so there is slop in this number).
61
 * So we can safely create prefixes up to BLCKSZ - 258 * 16 - 100 bytes long.
62
 * Unfortunately, because 258 * 16 is over 4K, there is no safe prefix length
63
 * when BLCKSZ is less than 8K; it is always possible to get "SPGiST inner
64
 * tuple size exceeds maximum" if there are too many distinct next-byte values
65
 * at a given place in the tree.  Since use of nonstandard block sizes appears
66
 * to be negligible in the field, we just live with that fact for now,
67
 * choosing a max prefix size of 32 bytes when BLCKSZ is configured smaller
68
 * than default.
69
 */
70
#define SPGIST_MAX_PREFIX_LENGTH  Max((int) (BLCKSZ - 258 * 16 - 100), 32)
71
72
/*
73
 * Strategy for collation aware operator on text is equal to btree strategy
74
 * plus value of 10.
75
 *
76
 * Current collation aware strategies and their corresponding btree strategies:
77
 * 11 BTLessStrategyNumber
78
 * 12 BTLessEqualStrategyNumber
79
 * 14 BTGreaterEqualStrategyNumber
80
 * 15 BTGreaterStrategyNumber
81
 */
82
0
#define SPG_STRATEGY_ADDITION (10)
83
0
#define SPG_IS_COLLATION_AWARE_STRATEGY(s) ((s) > SPG_STRATEGY_ADDITION \
84
0
                     && (s) != RTPrefixStrategyNumber)
85
86
/* Struct for sorting values in picksplit */
87
typedef struct spgNodePtr
88
{
89
  Datum   d;
90
  int     i;
91
  int16   c;
92
} spgNodePtr;
93
94
95
Datum
96
spg_text_config(PG_FUNCTION_ARGS)
97
0
{
98
#ifdef NOT_USED
99
  spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0);
100
#endif
101
0
  spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
102
103
0
  cfg->prefixType = TEXTOID;
104
0
  cfg->labelType = INT2OID;
105
0
  cfg->canReturnData = true;
106
0
  cfg->longValuesOK = true; /* suffixing will shorten long values */
107
0
  PG_RETURN_VOID();
108
0
}
109
110
/*
111
 * Form a text datum from the given not-necessarily-null-terminated string,
112
 * using short varlena header format if possible
113
 */
114
static Datum
115
formTextDatum(const char *data, int datalen)
116
0
{
117
0
  char     *p;
118
119
0
  p = (char *) palloc(datalen + VARHDRSZ);
120
121
0
  if (datalen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)
122
0
  {
123
0
    SET_VARSIZE_SHORT(p, datalen + VARHDRSZ_SHORT);
124
0
    if (datalen)
125
0
      memcpy(p + VARHDRSZ_SHORT, data, datalen);
126
0
  }
127
0
  else
128
0
  {
129
0
    SET_VARSIZE(p, datalen + VARHDRSZ);
130
0
    memcpy(p + VARHDRSZ, data, datalen);
131
0
  }
132
133
0
  return PointerGetDatum(p);
134
0
}
135
136
/*
137
 * Find the length of the common prefix of a and b
138
 */
139
static int
140
commonPrefix(const char *a, const char *b, int lena, int lenb)
141
0
{
142
0
  int     i = 0;
143
144
0
  while (i < lena && i < lenb && *a == *b)
145
0
  {
146
0
    a++;
147
0
    b++;
148
0
    i++;
149
0
  }
150
151
0
  return i;
152
0
}
153
154
/*
155
 * Binary search an array of int16 datums for a match to c
156
 *
157
 * On success, *i gets the match location; on failure, it gets where to insert
158
 */
159
static bool
160
searchChar(const Datum *nodeLabels, int nNodes, int16 c, int *i)
161
0
{
162
0
  int     StopLow = 0,
163
0
        StopHigh = nNodes;
164
165
0
  while (StopLow < StopHigh)
166
0
  {
167
0
    int     StopMiddle = (StopLow + StopHigh) >> 1;
168
0
    int16   middle = DatumGetInt16(nodeLabels[StopMiddle]);
169
170
0
    if (c < middle)
171
0
      StopHigh = StopMiddle;
172
0
    else if (c > middle)
173
0
      StopLow = StopMiddle + 1;
174
0
    else
175
0
    {
176
0
      *i = StopMiddle;
177
0
      return true;
178
0
    }
179
0
  }
180
181
0
  *i = StopHigh;
182
0
  return false;
183
0
}
184
185
Datum
186
spg_text_choose(PG_FUNCTION_ARGS)
187
0
{
188
0
  spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0);
189
0
  spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1);
190
0
  text     *inText = DatumGetTextPP(in->datum);
191
0
  char     *inStr = VARDATA_ANY(inText);
192
0
  int     inSize = VARSIZE_ANY_EXHDR(inText);
193
0
  char     *prefixStr = NULL;
194
0
  int     prefixSize = 0;
195
0
  int     commonLen = 0;
196
0
  int16   nodeChar = 0;
197
0
  int     i = 0;
198
199
  /* Check for prefix match, set nodeChar to first byte after prefix */
200
0
  if (in->hasPrefix)
201
0
  {
202
0
    text     *prefixText = DatumGetTextPP(in->prefixDatum);
203
204
0
    prefixStr = VARDATA_ANY(prefixText);
205
0
    prefixSize = VARSIZE_ANY_EXHDR(prefixText);
206
207
0
    commonLen = commonPrefix(inStr + in->level,
208
0
                 prefixStr,
209
0
                 inSize - in->level,
210
0
                 prefixSize);
211
212
0
    if (commonLen == prefixSize)
213
0
    {
214
0
      if (inSize - in->level > commonLen)
215
0
        nodeChar = *(unsigned char *) (inStr + in->level + commonLen);
216
0
      else
217
0
        nodeChar = -1;
218
0
    }
219
0
    else
220
0
    {
221
      /* Must split tuple because incoming value doesn't match prefix */
222
0
      out->resultType = spgSplitTuple;
223
224
0
      if (commonLen == 0)
225
0
      {
226
0
        out->result.splitTuple.prefixHasPrefix = false;
227
0
      }
228
0
      else
229
0
      {
230
0
        out->result.splitTuple.prefixHasPrefix = true;
231
0
        out->result.splitTuple.prefixPrefixDatum =
232
0
          formTextDatum(prefixStr, commonLen);
233
0
      }
234
0
      out->result.splitTuple.prefixNNodes = 1;
235
0
      out->result.splitTuple.prefixNodeLabels = palloc_object(Datum);
236
0
      out->result.splitTuple.prefixNodeLabels[0] =
237
0
        Int16GetDatum(*(unsigned char *) (prefixStr + commonLen));
238
239
0
      out->result.splitTuple.childNodeN = 0;
240
241
0
      if (prefixSize - commonLen == 1)
242
0
      {
243
0
        out->result.splitTuple.postfixHasPrefix = false;
244
0
      }
245
0
      else
246
0
      {
247
0
        out->result.splitTuple.postfixHasPrefix = true;
248
0
        out->result.splitTuple.postfixPrefixDatum =
249
0
          formTextDatum(prefixStr + commonLen + 1,
250
0
                  prefixSize - commonLen - 1);
251
0
      }
252
253
0
      PG_RETURN_VOID();
254
0
    }
255
0
  }
256
0
  else if (inSize > in->level)
257
0
  {
258
0
    nodeChar = *(unsigned char *) (inStr + in->level);
259
0
  }
260
0
  else
261
0
  {
262
0
    nodeChar = -1;
263
0
  }
264
265
  /* Look up nodeChar in the node label array */
266
0
  if (searchChar(in->nodeLabels, in->nNodes, nodeChar, &i))
267
0
  {
268
    /*
269
     * Descend to existing node.  (If in->allTheSame, the core code will
270
     * ignore our nodeN specification here, but that's OK.  We still have
271
     * to provide the correct levelAdd and restDatum values, and those are
272
     * the same regardless of which node gets chosen by core.)
273
     */
274
0
    int     levelAdd;
275
276
0
    out->resultType = spgMatchNode;
277
0
    out->result.matchNode.nodeN = i;
278
0
    levelAdd = commonLen;
279
0
    if (nodeChar >= 0)
280
0
      levelAdd++;
281
0
    out->result.matchNode.levelAdd = levelAdd;
282
0
    if (inSize - in->level - levelAdd > 0)
283
0
      out->result.matchNode.restDatum =
284
0
        formTextDatum(inStr + in->level + levelAdd,
285
0
                inSize - in->level - levelAdd);
286
0
    else
287
0
      out->result.matchNode.restDatum =
288
0
        formTextDatum(NULL, 0);
289
0
  }
290
0
  else if (in->allTheSame)
291
0
  {
292
    /*
293
     * Can't use AddNode action, so split the tuple.  The upper tuple has
294
     * the same prefix as before and uses a dummy node label -2 for the
295
     * lower tuple.  The lower tuple has no prefix and the same node
296
     * labels as the original tuple.
297
     *
298
     * Note: it might seem tempting to shorten the upper tuple's prefix,
299
     * if it has one, then use its last byte as label for the lower tuple.
300
     * But that doesn't win since we know the incoming value matches the
301
     * whole prefix: we'd just end up splitting the lower tuple again.
302
     */
303
0
    out->resultType = spgSplitTuple;
304
0
    out->result.splitTuple.prefixHasPrefix = in->hasPrefix;
305
0
    out->result.splitTuple.prefixPrefixDatum = in->prefixDatum;
306
0
    out->result.splitTuple.prefixNNodes = 1;
307
0
    out->result.splitTuple.prefixNodeLabels = palloc_object(Datum);
308
0
    out->result.splitTuple.prefixNodeLabels[0] = Int16GetDatum(-2);
309
0
    out->result.splitTuple.childNodeN = 0;
310
0
    out->result.splitTuple.postfixHasPrefix = false;
311
0
  }
312
0
  else
313
0
  {
314
    /* Add a node for the not-previously-seen nodeChar value */
315
0
    out->resultType = spgAddNode;
316
0
    out->result.addNode.nodeLabel = Int16GetDatum(nodeChar);
317
0
    out->result.addNode.nodeN = i;
318
0
  }
319
320
0
  PG_RETURN_VOID();
321
0
}
322
323
/* qsort comparator to sort spgNodePtr structs by "c" */
324
static int
325
cmpNodePtr(const void *a, const void *b)
326
0
{
327
0
  const spgNodePtr *aa = (const spgNodePtr *) a;
328
0
  const spgNodePtr *bb = (const spgNodePtr *) b;
329
330
0
  return pg_cmp_s16(aa->c, bb->c);
331
0
}
332
333
Datum
334
spg_text_picksplit(PG_FUNCTION_ARGS)
335
0
{
336
0
  spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
337
0
  spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
338
0
  text     *text0 = DatumGetTextPP(in->datums[0]);
339
0
  int     i,
340
0
        commonLen;
341
0
  spgNodePtr *nodes;
342
343
  /* Identify longest common prefix, if any */
344
0
  commonLen = VARSIZE_ANY_EXHDR(text0);
345
0
  for (i = 1; i < in->nTuples && commonLen > 0; i++)
346
0
  {
347
0
    text     *texti = DatumGetTextPP(in->datums[i]);
348
0
    int     tmp = commonPrefix(VARDATA_ANY(text0),
349
0
                     VARDATA_ANY(texti),
350
0
                     VARSIZE_ANY_EXHDR(text0),
351
0
                     VARSIZE_ANY_EXHDR(texti));
352
353
0
    if (tmp < commonLen)
354
0
      commonLen = tmp;
355
0
  }
356
357
  /*
358
   * Limit the prefix length, if necessary, to ensure that the resulting
359
   * inner tuple will fit on a page.
360
   */
361
0
  commonLen = Min(commonLen, SPGIST_MAX_PREFIX_LENGTH);
362
363
  /* Set node prefix to be that string, if it's not empty */
364
0
  if (commonLen == 0)
365
0
  {
366
0
    out->hasPrefix = false;
367
0
  }
368
0
  else
369
0
  {
370
0
    out->hasPrefix = true;
371
0
    out->prefixDatum = formTextDatum(VARDATA_ANY(text0), commonLen);
372
0
  }
373
374
  /* Extract the node label (first non-common byte) from each value */
375
0
  nodes = palloc_array(spgNodePtr, in->nTuples);
376
377
0
  for (i = 0; i < in->nTuples; i++)
378
0
  {
379
0
    text     *texti = DatumGetTextPP(in->datums[i]);
380
381
0
    if (commonLen < VARSIZE_ANY_EXHDR(texti))
382
0
      nodes[i].c = *(unsigned char *) (VARDATA_ANY(texti) + commonLen);
383
0
    else
384
0
      nodes[i].c = -1; /* use -1 if string is all common */
385
0
    nodes[i].i = i;
386
0
    nodes[i].d = in->datums[i];
387
0
  }
388
389
  /*
390
   * Sort by label values so that we can group the values into nodes.  This
391
   * also ensures that the nodes are ordered by label value, allowing the
392
   * use of binary search in searchChar.
393
   */
394
0
  qsort(nodes, in->nTuples, sizeof(*nodes), cmpNodePtr);
395
396
  /* And emit results */
397
0
  out->nNodes = 0;
398
0
  out->nodeLabels = palloc_array(Datum, in->nTuples);
399
0
  out->mapTuplesToNodes = palloc_array(int, in->nTuples);
400
0
  out->leafTupleDatums = palloc_array(Datum, in->nTuples);
401
402
0
  for (i = 0; i < in->nTuples; i++)
403
0
  {
404
0
    text     *texti = DatumGetTextPP(nodes[i].d);
405
0
    Datum   leafD;
406
407
0
    if (i == 0 || nodes[i].c != nodes[i - 1].c)
408
0
    {
409
0
      out->nodeLabels[out->nNodes] = Int16GetDatum(nodes[i].c);
410
0
      out->nNodes++;
411
0
    }
412
413
0
    if (commonLen < VARSIZE_ANY_EXHDR(texti))
414
0
      leafD = formTextDatum(VARDATA_ANY(texti) + commonLen + 1,
415
0
                  VARSIZE_ANY_EXHDR(texti) - commonLen - 1);
416
0
    else
417
0
      leafD = formTextDatum(NULL, 0);
418
419
0
    out->leafTupleDatums[nodes[i].i] = leafD;
420
0
    out->mapTuplesToNodes[nodes[i].i] = out->nNodes - 1;
421
0
  }
422
423
0
  PG_RETURN_VOID();
424
0
}
425
426
Datum
427
spg_text_inner_consistent(PG_FUNCTION_ARGS)
428
0
{
429
0
  spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
430
0
  spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
431
0
  bool    collate_is_c = pg_newlocale_from_collation(PG_GET_COLLATION())->collate_is_c;
432
0
  text     *reconstructedValue;
433
0
  text     *reconstrText;
434
0
  int     maxReconstrLen;
435
0
  text     *prefixText = NULL;
436
0
  int     prefixSize = 0;
437
0
  int     i;
438
439
  /*
440
   * Reconstruct values represented at this tuple, including parent data,
441
   * prefix of this tuple if any, and the node label if it's non-dummy.
442
   * in->level should be the length of the previously reconstructed value,
443
   * and the number of bytes added here is prefixSize or prefixSize + 1.
444
   *
445
   * Note: we assume that in->reconstructedValue isn't toasted and doesn't
446
   * have a short varlena header.  This is okay because it must have been
447
   * created by a previous invocation of this routine, and we always emit
448
   * long-format reconstructed values.
449
   */
450
0
  reconstructedValue = (text *) DatumGetPointer(in->reconstructedValue);
451
0
  Assert(reconstructedValue == NULL ? in->level == 0 :
452
0
       VARSIZE_ANY_EXHDR(reconstructedValue) == in->level);
453
454
0
  maxReconstrLen = in->level + 1;
455
0
  if (in->hasPrefix)
456
0
  {
457
0
    prefixText = DatumGetTextPP(in->prefixDatum);
458
0
    prefixSize = VARSIZE_ANY_EXHDR(prefixText);
459
0
    maxReconstrLen += prefixSize;
460
0
  }
461
462
0
  reconstrText = palloc(VARHDRSZ + maxReconstrLen);
463
0
  SET_VARSIZE(reconstrText, VARHDRSZ + maxReconstrLen);
464
465
0
  if (in->level)
466
0
    memcpy(VARDATA(reconstrText),
467
0
         VARDATA(reconstructedValue),
468
0
         in->level);
469
0
  if (prefixSize)
470
0
    memcpy(((char *) VARDATA(reconstrText)) + in->level,
471
0
         VARDATA_ANY(prefixText),
472
0
         prefixSize);
473
  /* last byte of reconstrText will be filled in below */
474
475
  /*
476
   * Scan the child nodes.  For each one, complete the reconstructed value
477
   * and see if it's consistent with the query.  If so, emit an entry into
478
   * the output arrays.
479
   */
480
0
  out->nodeNumbers = palloc_array(int, in->nNodes);
481
0
  out->levelAdds = palloc_array(int, in->nNodes);
482
0
  out->reconstructedValues = palloc_array(Datum, in->nNodes);
483
0
  out->nNodes = 0;
484
485
0
  for (i = 0; i < in->nNodes; i++)
486
0
  {
487
0
    int16   nodeChar = DatumGetInt16(in->nodeLabels[i]);
488
0
    int     thisLen;
489
0
    bool    res = true;
490
0
    int     j;
491
492
    /* If nodeChar is a dummy value, don't include it in data */
493
0
    if (nodeChar <= 0)
494
0
      thisLen = maxReconstrLen - 1;
495
0
    else
496
0
    {
497
0
      ((unsigned char *) VARDATA(reconstrText))[maxReconstrLen - 1] = nodeChar;
498
0
      thisLen = maxReconstrLen;
499
0
    }
500
501
0
    for (j = 0; j < in->nkeys; j++)
502
0
    {
503
0
      StrategyNumber strategy = in->scankeys[j].sk_strategy;
504
0
      text     *inText;
505
0
      int     inSize;
506
0
      int     r;
507
508
      /*
509
       * If it's a collation-aware operator, but the collation is C, we
510
       * can treat it as non-collation-aware.  With non-C collation we
511
       * need to traverse whole tree :-( so there's no point in making
512
       * any check here.  (Note also that our reconstructed value may
513
       * well end with a partial multibyte character, so that applying
514
       * any encoding-sensitive test to it would be risky anyhow.)
515
       */
516
0
      if (SPG_IS_COLLATION_AWARE_STRATEGY(strategy))
517
0
      {
518
0
        if (collate_is_c)
519
0
          strategy -= SPG_STRATEGY_ADDITION;
520
0
        else
521
0
          continue;
522
0
      }
523
524
0
      inText = DatumGetTextPP(in->scankeys[j].sk_argument);
525
0
      inSize = VARSIZE_ANY_EXHDR(inText);
526
527
0
      r = memcmp(VARDATA(reconstrText), VARDATA_ANY(inText),
528
0
             Min(inSize, thisLen));
529
530
0
      switch (strategy)
531
0
      {
532
0
        case BTLessStrategyNumber:
533
0
        case BTLessEqualStrategyNumber:
534
0
          if (r > 0)
535
0
            res = false;
536
0
          break;
537
0
        case BTEqualStrategyNumber:
538
0
          if (r != 0 || inSize < thisLen)
539
0
            res = false;
540
0
          break;
541
0
        case BTGreaterEqualStrategyNumber:
542
0
        case BTGreaterStrategyNumber:
543
0
          if (r < 0)
544
0
            res = false;
545
0
          break;
546
0
        case RTPrefixStrategyNumber:
547
0
          if (r != 0)
548
0
            res = false;
549
0
          break;
550
0
        default:
551
0
          elog(ERROR, "unrecognized strategy number: %d",
552
0
             in->scankeys[j].sk_strategy);
553
0
          break;
554
0
      }
555
556
0
      if (!res)
557
0
        break;     /* no need to consider remaining conditions */
558
0
    }
559
560
0
    if (res)
561
0
    {
562
0
      out->nodeNumbers[out->nNodes] = i;
563
0
      out->levelAdds[out->nNodes] = thisLen - in->level;
564
0
      SET_VARSIZE(reconstrText, VARHDRSZ + thisLen);
565
0
      out->reconstructedValues[out->nNodes] =
566
0
        datumCopy(PointerGetDatum(reconstrText), false, -1);
567
0
      out->nNodes++;
568
0
    }
569
0
  }
570
571
0
  PG_RETURN_VOID();
572
0
}
573
574
Datum
575
spg_text_leaf_consistent(PG_FUNCTION_ARGS)
576
0
{
577
0
  spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
578
0
  spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
579
0
  int     level = in->level;
580
0
  text     *leafValue,
581
0
         *reconstrValue = NULL;
582
0
  char     *fullValue;
583
0
  int     fullLen;
584
0
  bool    res;
585
0
  int     j;
586
587
  /* all tests are exact */
588
0
  out->recheck = false;
589
590
0
  leafValue = DatumGetTextPP(in->leafDatum);
591
592
  /* As above, in->reconstructedValue isn't toasted or short. */
593
0
  if (DatumGetPointer(in->reconstructedValue))
594
0
    reconstrValue = (text *) DatumGetPointer(in->reconstructedValue);
595
596
0
  Assert(reconstrValue == NULL ? level == 0 :
597
0
       VARSIZE_ANY_EXHDR(reconstrValue) == level);
598
599
  /* Reconstruct the full string represented by this leaf tuple */
600
0
  fullLen = level + VARSIZE_ANY_EXHDR(leafValue);
601
0
  if (VARSIZE_ANY_EXHDR(leafValue) == 0 && level > 0)
602
0
  {
603
0
    fullValue = VARDATA(reconstrValue);
604
0
    out->leafValue = PointerGetDatum(reconstrValue);
605
0
  }
606
0
  else
607
0
  {
608
0
    text     *fullText = palloc(VARHDRSZ + fullLen);
609
610
0
    SET_VARSIZE(fullText, VARHDRSZ + fullLen);
611
0
    fullValue = VARDATA(fullText);
612
0
    if (level)
613
0
      memcpy(fullValue, VARDATA(reconstrValue), level);
614
0
    if (VARSIZE_ANY_EXHDR(leafValue) > 0)
615
0
      memcpy(fullValue + level, VARDATA_ANY(leafValue),
616
0
           VARSIZE_ANY_EXHDR(leafValue));
617
0
    out->leafValue = PointerGetDatum(fullText);
618
0
  }
619
620
  /* Perform the required comparison(s) */
621
0
  res = true;
622
0
  for (j = 0; j < in->nkeys; j++)
623
0
  {
624
0
    StrategyNumber strategy = in->scankeys[j].sk_strategy;
625
0
    text     *query = DatumGetTextPP(in->scankeys[j].sk_argument);
626
0
    int     queryLen = VARSIZE_ANY_EXHDR(query);
627
0
    int     r;
628
629
0
    if (strategy == RTPrefixStrategyNumber)
630
0
    {
631
      /*
632
       * if level >= length of query then reconstrValue must begin with
633
       * query (prefix) string, so we don't need to check it again.
634
       */
635
0
      res = (level >= queryLen) ||
636
0
        DatumGetBool(DirectFunctionCall2Coll(text_starts_with,
637
0
                           PG_GET_COLLATION(),
638
0
                           out->leafValue,
639
0
                           PointerGetDatum(query)));
640
641
0
      if (!res)     /* no need to consider remaining conditions */
642
0
        break;
643
644
0
      continue;
645
0
    }
646
647
0
    if (SPG_IS_COLLATION_AWARE_STRATEGY(strategy))
648
0
    {
649
      /* Collation-aware comparison */
650
0
      strategy -= SPG_STRATEGY_ADDITION;
651
652
      /* If asserts enabled, verify encoding of reconstructed string */
653
0
      Assert(pg_verifymbstr(fullValue, fullLen, false));
654
655
0
      r = varstr_cmp(fullValue, fullLen,
656
0
               VARDATA_ANY(query), queryLen,
657
0
               PG_GET_COLLATION());
658
0
    }
659
0
    else
660
0
    {
661
      /* Non-collation-aware comparison */
662
0
      r = memcmp(fullValue, VARDATA_ANY(query), Min(queryLen, fullLen));
663
664
0
      if (r == 0)
665
0
      {
666
0
        if (queryLen > fullLen)
667
0
          r = -1;
668
0
        else if (queryLen < fullLen)
669
0
          r = 1;
670
0
      }
671
0
    }
672
673
0
    switch (strategy)
674
0
    {
675
0
      case BTLessStrategyNumber:
676
0
        res = (r < 0);
677
0
        break;
678
0
      case BTLessEqualStrategyNumber:
679
0
        res = (r <= 0);
680
0
        break;
681
0
      case BTEqualStrategyNumber:
682
0
        res = (r == 0);
683
0
        break;
684
0
      case BTGreaterEqualStrategyNumber:
685
0
        res = (r >= 0);
686
0
        break;
687
0
      case BTGreaterStrategyNumber:
688
0
        res = (r > 0);
689
0
        break;
690
0
      default:
691
0
        elog(ERROR, "unrecognized strategy number: %d",
692
0
           in->scankeys[j].sk_strategy);
693
0
        res = false;
694
0
        break;
695
0
    }
696
697
0
    if (!res)
698
0
      break;       /* no need to consider remaining conditions */
699
0
  }
700
701
0
  PG_RETURN_BOOL(res);
702
0
}