/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 | } |