PostgreSQL Source Code git master
Loading...
Searching...
No Matches
spgtextproc.c
Go to the documentation of this file.
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#define SPG_STRATEGY_ADDITION (10)
83#define SPG_IS_COLLATION_AWARE_STRATEGY(s) ((s) > SPG_STRATEGY_ADDITION \
84 && (s) != RTPrefixStrategyNumber)
85
86/* Struct for sorting values in picksplit */
87typedef struct spgNodePtr
88{
90 int i;
93
94
97{
98#ifdef NOT_USED
100#endif
102
103 cfg->prefixType = TEXTOID;
104 cfg->labelType = INT2OID;
105 cfg->canReturnData = true;
106 cfg->longValuesOK = true; /* suffixing will shorten long values */
108}
109
110/*
111 * Form a text datum from the given not-necessarily-null-terminated string,
112 * using short varlena header format if possible
113 */
114static Datum
115formTextDatum(const char *data, int datalen)
116{
117 char *p;
118
119 p = (char *) palloc(datalen + VARHDRSZ);
120
121 if (datalen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)
122 {
123 SET_VARSIZE_SHORT(p, datalen + VARHDRSZ_SHORT);
124 if (datalen)
125 memcpy(p + VARHDRSZ_SHORT, data, datalen);
126 }
127 else
128 {
129 SET_VARSIZE(p, datalen + VARHDRSZ);
130 memcpy(p + VARHDRSZ, data, datalen);
131 }
132
133 return PointerGetDatum(p);
134}
135
136/*
137 * Find the length of the common prefix of a and b
138 */
139static int
140commonPrefix(const char *a, const char *b, int lena, int lenb)
141{
142 int i = 0;
143
144 while (i < lena && i < lenb && *a == *b)
145 {
146 a++;
147 b++;
148 i++;
149 }
150
151 return i;
152}
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 */
159static bool
160searchChar(const Datum *nodeLabels, int nNodes, int16 c, int *i)
161{
162 int StopLow = 0,
163 StopHigh = nNodes;
164
165 while (StopLow < StopHigh)
166 {
167 int StopMiddle = (StopLow + StopHigh) >> 1;
168 int16 middle = DatumGetInt16(nodeLabels[StopMiddle]);
169
170 if (c < middle)
172 else if (c > middle)
173 StopLow = StopMiddle + 1;
174 else
175 {
176 *i = StopMiddle;
177 return true;
178 }
179 }
180
181 *i = StopHigh;
182 return false;
183}
184
185Datum
187{
191 char *inStr = VARDATA_ANY(inText);
193 char *prefixStr = NULL;
194 int prefixSize = 0;
195 int commonLen = 0;
196 int16 nodeChar = 0;
197 int i = 0;
198
199 /* Check for prefix match, set nodeChar to first byte after prefix */
200 if (in->hasPrefix)
201 {
203
205 prefixSize = VARSIZE_ANY_EXHDR(prefixText);
206
208 prefixStr,
209 inSize - in->level,
210 prefixSize);
211
212 if (commonLen == prefixSize)
213 {
214 if (inSize - in->level > commonLen)
215 nodeChar = *(unsigned char *) (inStr + in->level + commonLen);
216 else
217 nodeChar = -1;
218 }
219 else
220 {
221 /* Must split tuple because incoming value doesn't match prefix */
223
224 if (commonLen == 0)
225 {
226 out->result.splitTuple.prefixHasPrefix = false;
227 }
228 else
229 {
233 }
237 Int16GetDatum(*(unsigned char *) (prefixStr + commonLen));
238
240
241 if (prefixSize - commonLen == 1)
242 {
244 }
245 else
246 {
250 prefixSize - commonLen - 1);
251 }
252
254 }
255 }
256 else if (inSize > in->level)
257 {
258 nodeChar = *(unsigned char *) (inStr + in->level);
259 }
260 else
261 {
262 nodeChar = -1;
263 }
264
265 /* Look up nodeChar in the node label array */
266 if (searchChar(in->nodeLabels, in->nNodes, nodeChar, &i))
267 {
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 int levelAdd;
275
277 out->result.matchNode.nodeN = i;
278 levelAdd = commonLen;
279 if (nodeChar >= 0)
280 levelAdd++;
281 out->result.matchNode.levelAdd = levelAdd;
282 if (inSize - in->level - levelAdd > 0)
284 formTextDatum(inStr + in->level + levelAdd,
285 inSize - in->level - levelAdd);
286 else
289 }
290 else if (in->allTheSame)
291 {
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 */
311 }
312 else
313 {
314 /* Add a node for the not-previously-seen nodeChar value */
315 out->resultType = spgAddNode;
317 out->result.addNode.nodeN = i;
318 }
319
321}
322
323/* qsort comparator to sort spgNodePtr structs by "c" */
324static int
325cmpNodePtr(const void *a, const void *b)
326{
327 const spgNodePtr *aa = (const spgNodePtr *) a;
328 const spgNodePtr *bb = (const spgNodePtr *) b;
329
330 return pg_cmp_s16(aa->c, bb->c);
331}
332
333Datum
335{
338 text *text0 = DatumGetTextPP(in->datums[0]);
339 int i,
340 commonLen;
342
343 /* Identify longest common prefix, if any */
345 for (i = 1; i < in->nTuples && commonLen > 0; i++)
346 {
348 int tmp = commonPrefix(VARDATA_ANY(text0),
352
353 if (tmp < commonLen)
354 commonLen = tmp;
355 }
356
357 /*
358 * Limit the prefix length, if necessary, to ensure that the resulting
359 * inner tuple will fit on a page.
360 */
362
363 /* Set node prefix to be that string, if it's not empty */
364 if (commonLen == 0)
365 {
366 out->hasPrefix = false;
367 }
368 else
369 {
370 out->hasPrefix = true;
372 }
373
374 /* Extract the node label (first non-common byte) from each value */
376
377 for (i = 0; i < in->nTuples; i++)
378 {
380
382 nodes[i].c = *(unsigned char *) (VARDATA_ANY(texti) + commonLen);
383 else
384 nodes[i].c = -1; /* use -1 if string is all common */
385 nodes[i].i = i;
386 nodes[i].d = in->datums[i];
387 }
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 qsort(nodes, in->nTuples, sizeof(*nodes), cmpNodePtr);
395
396 /* And emit results */
397 out->nNodes = 0;
399 out->mapTuplesToNodes = palloc_array(int, in->nTuples);
401
402 for (i = 0; i < in->nTuples; i++)
403 {
405 Datum leafD;
406
407 if (i == 0 || nodes[i].c != nodes[i - 1].c)
408 {
409 out->nodeLabels[out->nNodes] = Int16GetDatum(nodes[i].c);
410 out->nNodes++;
411 }
412
416 else
418
419 out->leafTupleDatums[nodes[i].i] = leafD;
420 out->mapTuplesToNodes[nodes[i].i] = out->nNodes - 1;
421 }
422
424}
425
426Datum
428{
432 text *reconstructedValue;
434 int maxReconstrLen;
436 int prefixSize = 0;
437 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 reconstructedValue = (text *) DatumGetPointer(in->reconstructedValue);
451 Assert(reconstructedValue == NULL ? in->level == 0 :
452 VARSIZE_ANY_EXHDR(reconstructedValue) == in->level);
453
454 maxReconstrLen = in->level + 1;
455 if (in->hasPrefix)
456 {
458 prefixSize = VARSIZE_ANY_EXHDR(prefixText);
459 maxReconstrLen += prefixSize;
460 }
461
464
465 if (in->level)
467 VARDATA(reconstructedValue),
468 in->level);
469 if (prefixSize)
470 memcpy(((char *) VARDATA(reconstrText)) + in->level,
472 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 out->nodeNumbers = palloc_array(int, in->nNodes);
481 out->levelAdds = palloc_array(int, in->nNodes);
483 out->nNodes = 0;
484
485 for (i = 0; i < in->nNodes; i++)
486 {
488 int thisLen;
489 bool res = true;
490 int j;
491
492 /* If nodeChar is a dummy value, don't include it in data */
493 if (nodeChar <= 0)
495 else
496 {
497 ((unsigned char *) VARDATA(reconstrText))[maxReconstrLen - 1] = nodeChar;
499 }
500
501 for (j = 0; j < in->nkeys; j++)
502 {
503 StrategyNumber strategy = in->scankeys[j].sk_strategy;
504 text *inText;
505 int inSize;
506 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 */
517 {
518 if (collate_is_c)
519 strategy -= SPG_STRATEGY_ADDITION;
520 else
521 continue;
522 }
523
526
528 Min(inSize, thisLen));
529
530 switch (strategy)
531 {
534 if (r > 0)
535 res = false;
536 break;
538 if (r != 0 || inSize < thisLen)
539 res = false;
540 break;
543 if (r < 0)
544 res = false;
545 break;
547 if (r != 0)
548 res = false;
549 break;
550 default:
551 elog(ERROR, "unrecognized strategy number: %d",
552 in->scankeys[j].sk_strategy);
553 break;
554 }
555
556 if (!res)
557 break; /* no need to consider remaining conditions */
558 }
559
560 if (res)
561 {
562 out->nodeNumbers[out->nNodes] = i;
563 out->levelAdds[out->nNodes] = thisLen - in->level;
565 out->reconstructedValues[out->nNodes] =
567 out->nNodes++;
568 }
569 }
570
572}
573
574Datum
576{
579 int level = in->level;
580 text *leafValue,
582 char *fullValue;
583 int fullLen;
584 bool res;
585 int j;
586
587 /* all tests are exact */
588 out->recheck = false;
589
590 leafValue = DatumGetTextPP(in->leafDatum);
591
592 /* As above, in->reconstructedValue isn't toasted or short. */
595
596 Assert(reconstrValue == NULL ? level == 0 :
598
599 /* Reconstruct the full string represented by this leaf tuple */
600 fullLen = level + VARSIZE_ANY_EXHDR(leafValue);
601 if (VARSIZE_ANY_EXHDR(leafValue) == 0 && level > 0)
602 {
605 }
606 else
607 {
609
612 if (level)
614 if (VARSIZE_ANY_EXHDR(leafValue) > 0)
615 memcpy(fullValue + level, VARDATA_ANY(leafValue),
616 VARSIZE_ANY_EXHDR(leafValue));
618 }
619
620 /* Perform the required comparison(s) */
621 res = true;
622 for (j = 0; j < in->nkeys; j++)
623 {
624 StrategyNumber strategy = in->scankeys[j].sk_strategy;
626 int queryLen = VARSIZE_ANY_EXHDR(query);
627 int r;
628
629 if (strategy == RTPrefixStrategyNumber)
630 {
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 res = (level >= queryLen) ||
638 out->leafValue,
639 PointerGetDatum(query)));
640
641 if (!res) /* no need to consider remaining conditions */
642 break;
643
644 continue;
645 }
646
648 {
649 /* Collation-aware comparison */
650 strategy -= SPG_STRATEGY_ADDITION;
651
652 /* If asserts enabled, verify encoding of reconstructed string */
654
656 VARDATA_ANY(query), queryLen,
658 }
659 else
660 {
661 /* Non-collation-aware comparison */
663
664 if (r == 0)
665 {
666 if (queryLen > fullLen)
667 r = -1;
668 else if (queryLen < fullLen)
669 r = 1;
670 }
671 }
672
673 switch (strategy)
674 {
676 res = (r < 0);
677 break;
679 res = (r <= 0);
680 break;
682 res = (r == 0);
683 break;
685 res = (r >= 0);
686 break;
688 res = (r > 0);
689 break;
690 default:
691 elog(ERROR, "unrecognized strategy number: %d",
692 in->scankeys[j].sk_strategy);
693 res = false;
694 break;
695 }
696
697 if (!res)
698 break; /* no need to consider remaining conditions */
699 }
700
701 PG_RETURN_BOOL(res);
702}
#define Min(x, y)
Definition c.h:997
#define VARHDRSZ
Definition c.h:711
#define Assert(condition)
Definition c.h:873
int16_t int16
Definition c.h:541
Datum datumCopy(Datum value, bool typByVal, int typLen)
Definition datum.c:132
#define ERROR
Definition elog.h:39
#define elog(elevel,...)
Definition elog.h:226
#define palloc_object(type)
Definition fe_memutils.h:74
#define palloc_array(type, count)
Definition fe_memutils.h:76
Datum DirectFunctionCall2Coll(PGFunction func, Oid collation, Datum arg1, Datum arg2)
Definition fmgr.c:813
#define PG_RETURN_VOID()
Definition fmgr.h:350
#define DatumGetTextPP(X)
Definition fmgr.h:293
#define PG_GETARG_POINTER(n)
Definition fmgr.h:277
#define PG_GET_COLLATION()
Definition fmgr.h:198
#define PG_FUNCTION_ARGS
Definition fmgr.h:193
#define PG_RETURN_BOOL(x)
Definition fmgr.h:360
static int pg_cmp_s16(int16 a, int16 b)
Definition int.h:701
int b
Definition isn.c:74
int a
Definition isn.c:73
int j
Definition isn.c:78
int i
Definition isn.c:77
bool pg_verifymbstr(const char *mbstr, int len, bool noError)
Definition mbutils.c:1559
void * palloc(Size size)
Definition mcxt.c:1387
const void * data
pg_locale_t pg_newlocale_from_collation(Oid collid)
Definition pg_locale.c:1189
#define qsort(a, b, c, d)
Definition port.h:495
static bool DatumGetBool(Datum X)
Definition postgres.h:100
static Datum PointerGetDatum(const void *X)
Definition postgres.h:352
static Datum Int16GetDatum(int16 X)
Definition postgres.h:182
uint64_t Datum
Definition postgres.h:70
static Pointer DatumGetPointer(Datum X)
Definition postgres.h:342
static int16 DatumGetInt16(Datum X)
Definition postgres.h:172
char * c
static int fb(int x)
@ spgMatchNode
Definition spgist.h:69
@ spgAddNode
Definition spgist.h:70
@ spgSplitTuple
Definition spgist.h:71
Datum spg_text_config(PG_FUNCTION_ARGS)
Definition spgtextproc.c:96
static int commonPrefix(const char *a, const char *b, int lena, int lenb)
#define SPG_IS_COLLATION_AWARE_STRATEGY(s)
Definition spgtextproc.c:83
static int cmpNodePtr(const void *a, const void *b)
#define SPGIST_MAX_PREFIX_LENGTH
Definition spgtextproc.c:70
#define SPG_STRATEGY_ADDITION
Definition spgtextproc.c:82
Datum spg_text_leaf_consistent(PG_FUNCTION_ARGS)
Datum spg_text_inner_consistent(PG_FUNCTION_ARGS)
static Datum formTextDatum(const char *data, int datalen)
Datum spg_text_choose(PG_FUNCTION_ARGS)
static bool searchChar(const Datum *nodeLabels, int nNodes, int16 c, int *i)
Datum spg_text_picksplit(PG_FUNCTION_ARGS)
uint16 StrategyNumber
Definition stratnum.h:22
#define RTPrefixStrategyNumber
Definition stratnum.h:78
#define BTGreaterStrategyNumber
Definition stratnum.h:33
#define BTLessStrategyNumber
Definition stratnum.h:29
#define BTEqualStrategyNumber
Definition stratnum.h:31
#define BTLessEqualStrategyNumber
Definition stratnum.h:30
#define BTGreaterEqualStrategyNumber
Definition stratnum.h:32
Datum sk_argument
Definition skey.h:72
StrategyNumber sk_strategy
Definition skey.h:68
Datum * nodeLabels
Definition spgist.h:64
bool hasPrefix
Definition spgist.h:61
Datum prefixDatum
Definition spgist.h:62
int nNodes
Definition spgist.h:63
Datum datum
Definition spgist.h:55
int level
Definition spgist.h:57
bool allTheSame
Definition spgist.h:60
bool postfixHasPrefix
Definition spgist.h:101
int childNodeN
Definition spgist.h:98
spgChooseResultType resultType
Definition spgist.h:76
struct spgChooseOut::@54::@57 splitTuple
int levelAdd
Definition spgist.h:82
struct spgChooseOut::@54::@56 addNode
Datum nodeLabel
Definition spgist.h:87
Datum * prefixNodeLabels
Definition spgist.h:96
Datum postfixPrefixDatum
Definition spgist.h:102
Datum restDatum
Definition spgist.h:83
int prefixNNodes
Definition spgist.h:95
int nodeN
Definition spgist.h:81
union spgChooseOut::@54 result
Datum prefixPrefixDatum
Definition spgist.h:94
bool prefixHasPrefix
Definition spgist.h:93
struct spgChooseOut::@54::@55 matchNode
bool longValuesOK
Definition spgist.h:47
bool canReturnData
Definition spgist.h:46
Oid labelType
Definition spgist.h:44
Oid prefixType
Definition spgist.h:43
Datum reconstructedValue
Definition spgist.h:140
Datum * reconstructedValues
Definition spgist.h:159
Datum reconstructedValue
Definition spgist.h:175
Datum * datums
Definition spgist.h:113
int * mapTuplesToNodes
Definition spgist.h:125
Datum * nodeLabels
Definition spgist.h:123
Datum * leafTupleDatums
Definition spgist.h:126
Datum prefixDatum
Definition spgist.h:120
Definition c.h:706
#define VARHDRSZ_SHORT
Definition varatt.h:278
static Size VARSIZE_ANY_EXHDR(const void *PTR)
Definition varatt.h:472
static char * VARDATA(const void *PTR)
Definition varatt.h:305
static char * VARDATA_ANY(const void *PTR)
Definition varatt.h:486
static void SET_VARSIZE_SHORT(void *PTR, Size len)
Definition varatt.h:439
#define VARATT_SHORT_MAX
Definition varatt.h:279
static void SET_VARSIZE(void *PTR, Size len)
Definition varatt.h:432
int varstr_cmp(const char *arg1, int len1, const char *arg2, int len2, Oid collid)
Definition varlena.c:1308
Datum text_starts_with(PG_FUNCTION_ARGS)
Definition varlena.c:1545