Age Owner Branch data TLA Line data Source code
1 : : /*--------------------------------------------------------------------------
2 : : *
3 : : * spgist_name_ops.c
4 : : * Test opclass for SP-GiST
5 : : *
6 : : * This indexes input values of type "name", but the index storage is "text",
7 : : * with the same choices as made in the core SP-GiST text_ops opclass.
8 : : * Much of the code is identical to src/backend/access/spgist/spgtextproc.c,
9 : : * which see for a more detailed header comment.
10 : : *
11 : : * Unlike spgtextproc.c, we don't bother with collation-aware logic.
12 : : *
13 : : *
14 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
15 : : * Portions Copyright (c) 1994, Regents of the University of California
16 : : *
17 : : * IDENTIFICATION
18 : : * src/test/modules/spgist_name_ops/spgist_name_ops.c
19 : : *
20 : : * -------------------------------------------------------------------------
21 : : */
22 : : #include "postgres.h"
23 : :
24 : : #include "access/spgist.h"
25 : : #include "catalog/pg_type.h"
26 : : #include "utils/datum.h"
27 : : #include "varatt.h"
28 : :
1806 tgl@sss.pgh.pa.us 29 :CBC 1 : PG_MODULE_MAGIC;
30 : :
31 : :
32 : 2 : PG_FUNCTION_INFO_V1(spgist_name_config);
33 : : Datum
34 : 7 : spgist_name_config(PG_FUNCTION_ARGS)
35 : : {
36 : : #ifdef NOT_USED
37 : : spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0);
38 : : #endif
39 : 7 : spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
40 : :
41 : 7 : cfg->prefixType = TEXTOID;
42 : 7 : cfg->labelType = INT2OID;
43 : 7 : cfg->leafType = TEXTOID;
44 : 7 : cfg->canReturnData = true;
45 : 7 : cfg->longValuesOK = true; /* suffixing will shorten long values */
46 : 7 : PG_RETURN_VOID();
47 : : }
48 : :
49 : : /*
50 : : * Form a text datum from the given not-necessarily-null-terminated string,
51 : : * using short varlena header format if possible
52 : : */
53 : : static Datum
54 : 18284 : formTextDatum(const char *data, int datalen)
55 : : {
56 : : char *p;
57 : :
58 : 18284 : p = (char *) palloc(datalen + VARHDRSZ);
59 : :
60 [ + - ]: 18284 : if (datalen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)
61 : : {
62 : 18284 : SET_VARSIZE_SHORT(p, datalen + VARHDRSZ_SHORT);
63 [ + + ]: 18284 : if (datalen)
64 : 18241 : memcpy(p + VARHDRSZ_SHORT, data, datalen);
65 : : }
66 : : else
67 : : {
1806 tgl@sss.pgh.pa.us 68 :UBC 0 : SET_VARSIZE(p, datalen + VARHDRSZ);
69 : 0 : memcpy(p + VARHDRSZ, data, datalen);
70 : : }
71 : :
1806 tgl@sss.pgh.pa.us 72 :CBC 18284 : return PointerGetDatum(p);
73 : : }
74 : :
75 : : /*
76 : : * Find the length of the common prefix of a and b
77 : : */
78 : : static int
79 : 1051 : commonPrefix(const char *a, const char *b, int lena, int lenb)
80 : : {
81 : 1051 : int i = 0;
82 : :
83 [ + + + + : 2370 : while (i < lena && i < lenb && *a == *b)
+ + ]
84 : : {
85 : 1319 : a++;
86 : 1319 : b++;
87 : 1319 : i++;
88 : : }
89 : :
90 : 1051 : return i;
91 : : }
92 : :
93 : : /*
94 : : * Binary search an array of int16 datums for a match to c
95 : : *
96 : : * On success, *i gets the match location; on failure, it gets where to insert
97 : : */
98 : : static bool
135 peter@eisentraut.org 99 :GNC 11473 : searchChar(const Datum *nodeLabels, int nNodes, int16 c, int *i)
100 : : {
1806 tgl@sss.pgh.pa.us 101 :CBC 11473 : int StopLow = 0,
102 : 11473 : StopHigh = nNodes;
103 : :
104 [ + + ]: 36981 : while (StopLow < StopHigh)
105 : : {
106 : 36872 : int StopMiddle = (StopLow + StopHigh) >> 1;
107 : 36872 : int16 middle = DatumGetInt16(nodeLabels[StopMiddle]);
108 : :
109 [ + + ]: 36872 : if (c < middle)
110 : 14901 : StopHigh = StopMiddle;
111 [ + + ]: 21971 : else if (c > middle)
112 : 10607 : StopLow = StopMiddle + 1;
113 : : else
114 : : {
115 : 11364 : *i = StopMiddle;
116 : 11364 : return true;
117 : : }
118 : : }
119 : :
120 : 109 : *i = StopHigh;
121 : 109 : return false;
122 : : }
123 : :
124 : 2 : PG_FUNCTION_INFO_V1(spgist_name_choose);
125 : : Datum
126 : 11477 : spgist_name_choose(PG_FUNCTION_ARGS)
127 : : {
128 : 11477 : spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0);
129 : 11477 : spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1);
130 : 11477 : Name inName = DatumGetName(in->datum);
131 : 11477 : char *inStr = NameStr(*inName);
132 : 11477 : int inSize = strlen(inStr);
133 : 11477 : char *prefixStr = NULL;
134 : 11477 : int prefixSize = 0;
135 : 11477 : int commonLen = 0;
136 : 11477 : int16 nodeChar = 0;
137 : 11477 : int i = 0;
138 : :
139 : : /* Check for prefix match, set nodeChar to first byte after prefix */
140 [ + + ]: 11477 : if (in->hasPrefix)
141 : : {
142 : 1051 : text *prefixText = DatumGetTextPP(in->prefixDatum);
143 : :
144 [ + - ]: 1051 : prefixStr = VARDATA_ANY(prefixText);
145 [ - + - - : 1051 : prefixSize = VARSIZE_ANY_EXHDR(prefixText);
- - - - +
- ]
146 : :
147 : 1051 : commonLen = commonPrefix(inStr + in->level,
148 : : prefixStr,
149 : 1051 : inSize - in->level,
150 : : prefixSize);
151 : :
152 [ + + ]: 1051 : if (commonLen == prefixSize)
153 : : {
154 [ + + ]: 1047 : if (inSize - in->level > commonLen)
155 : 1045 : nodeChar = *(unsigned char *) (inStr + in->level + commonLen);
156 : : else
157 : 2 : nodeChar = -1;
158 : : }
159 : : else
160 : : {
161 : : /* Must split tuple because incoming value doesn't match prefix */
162 : 4 : out->resultType = spgSplitTuple;
163 : :
164 [ + + ]: 4 : if (commonLen == 0)
165 : : {
166 : 2 : out->result.splitTuple.prefixHasPrefix = false;
167 : : }
168 : : else
169 : : {
170 : 2 : out->result.splitTuple.prefixHasPrefix = true;
171 : 2 : out->result.splitTuple.prefixPrefixDatum =
172 : 2 : formTextDatum(prefixStr, commonLen);
173 : : }
174 : 4 : out->result.splitTuple.prefixNNodes = 1;
175 : 4 : out->result.splitTuple.prefixNodeLabels =
96 michael@paquier.xyz 176 :GNC 4 : palloc_object(Datum);
1806 tgl@sss.pgh.pa.us 177 :CBC 8 : out->result.splitTuple.prefixNodeLabels[0] =
178 : 4 : Int16GetDatum(*(unsigned char *) (prefixStr + commonLen));
179 : :
180 : 4 : out->result.splitTuple.childNodeN = 0;
181 : :
182 [ + - ]: 4 : if (prefixSize - commonLen == 1)
183 : : {
184 : 4 : out->result.splitTuple.postfixHasPrefix = false;
185 : : }
186 : : else
187 : : {
1806 tgl@sss.pgh.pa.us 188 :LBC (2) : out->result.splitTuple.postfixHasPrefix = true;
189 : (2) : out->result.splitTuple.postfixPrefixDatum =
190 : (2) : formTextDatum(prefixStr + commonLen + 1,
191 : (2) : prefixSize - commonLen - 1);
192 : : }
193 : :
1806 tgl@sss.pgh.pa.us 194 :CBC 4 : PG_RETURN_VOID();
195 : : }
196 : : }
197 [ + + ]: 10426 : else if (inSize > in->level)
198 : : {
199 : 10406 : nodeChar = *(unsigned char *) (inStr + in->level);
200 : : }
201 : : else
202 : : {
203 : 20 : nodeChar = -1;
204 : : }
205 : :
206 : : /* Look up nodeChar in the node label array */
207 [ + + ]: 11473 : if (searchChar(in->nodeLabels, in->nNodes, nodeChar, &i))
208 : : {
209 : : /*
210 : : * Descend to existing node. (If in->allTheSame, the core code will
211 : : * ignore our nodeN specification here, but that's OK. We still have
212 : : * to provide the correct levelAdd and restDatum values, and those are
213 : : * the same regardless of which node gets chosen by core.)
214 : : */
215 : : int levelAdd;
216 : :
217 : 11364 : out->resultType = spgMatchNode;
218 : 11364 : out->result.matchNode.nodeN = i;
219 : 11364 : levelAdd = commonLen;
220 [ + + ]: 11364 : if (nodeChar >= 0)
221 : 11342 : levelAdd++;
222 : 11364 : out->result.matchNode.levelAdd = levelAdd;
223 [ + + ]: 11364 : if (inSize - in->level - levelAdd > 0)
224 : 11321 : out->result.matchNode.restDatum =
225 : 11321 : formTextDatum(inStr + in->level + levelAdd,
226 : 11321 : inSize - in->level - levelAdd);
227 : : else
228 : 43 : out->result.matchNode.restDatum =
229 : 43 : formTextDatum(NULL, 0);
230 : : }
231 [ - + ]: 109 : else if (in->allTheSame)
232 : : {
233 : : /*
234 : : * Can't use AddNode action, so split the tuple. The upper tuple has
235 : : * the same prefix as before and uses a dummy node label -2 for the
236 : : * lower tuple. The lower tuple has no prefix and the same node
237 : : * labels as the original tuple.
238 : : *
239 : : * Note: it might seem tempting to shorten the upper tuple's prefix,
240 : : * if it has one, then use its last byte as label for the lower tuple.
241 : : * But that doesn't win since we know the incoming value matches the
242 : : * whole prefix: we'd just end up splitting the lower tuple again.
243 : : */
1806 tgl@sss.pgh.pa.us 244 :UBC 0 : out->resultType = spgSplitTuple;
245 : 0 : out->result.splitTuple.prefixHasPrefix = in->hasPrefix;
246 : 0 : out->result.splitTuple.prefixPrefixDatum = in->prefixDatum;
247 : 0 : out->result.splitTuple.prefixNNodes = 1;
96 michael@paquier.xyz 248 :UNC 0 : out->result.splitTuple.prefixNodeLabels = palloc_object(Datum);
1806 tgl@sss.pgh.pa.us 249 :UBC 0 : out->result.splitTuple.prefixNodeLabels[0] = Int16GetDatum(-2);
250 : 0 : out->result.splitTuple.childNodeN = 0;
251 : 0 : out->result.splitTuple.postfixHasPrefix = false;
252 : : }
253 : : else
254 : : {
255 : : /* Add a node for the not-previously-seen nodeChar value */
1806 tgl@sss.pgh.pa.us 256 :CBC 109 : out->resultType = spgAddNode;
257 : 109 : out->result.addNode.nodeLabel = Int16GetDatum(nodeChar);
258 : 109 : out->result.addNode.nodeN = i;
259 : : }
260 : :
261 : 11473 : PG_RETURN_VOID();
262 : : }
263 : :
264 : : /* The picksplit function is identical to the core opclass, so just use that */
265 : :
266 : 2 : PG_FUNCTION_INFO_V1(spgist_name_inner_consistent);
267 : : Datum
268 : 4 : spgist_name_inner_consistent(PG_FUNCTION_ARGS)
269 : : {
270 : 4 : spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
271 : 4 : spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
272 : : text *reconstructedValue;
273 : : text *reconstrText;
274 : : int maxReconstrLen;
275 : 4 : text *prefixText = NULL;
276 : 4 : int prefixSize = 0;
277 : : int i;
278 : :
279 : : /*
280 : : * Reconstruct values represented at this tuple, including parent data,
281 : : * prefix of this tuple if any, and the node label if it's non-dummy.
282 : : * in->level should be the length of the previously reconstructed value,
283 : : * and the number of bytes added here is prefixSize or prefixSize + 1.
284 : : *
285 : : * Recall that reconstructedValues are assumed to be the same type as leaf
286 : : * datums, so we must use "text" not "name" for them.
287 : : *
288 : : * Note: we assume that in->reconstructedValue isn't toasted and doesn't
289 : : * have a short varlena header. This is okay because it must have been
290 : : * created by a previous invocation of this routine, and we always emit
291 : : * long-format reconstructed values.
292 : : */
293 : 4 : reconstructedValue = (text *) DatumGetPointer(in->reconstructedValue);
294 [ + + - + : 4 : Assert(reconstructedValue == NULL ? in->level == 0 :
- - - - -
- - + -
+ ]
295 : : VARSIZE_ANY_EXHDR(reconstructedValue) == in->level);
296 : :
297 : 4 : maxReconstrLen = in->level + 1;
298 [ - + ]: 4 : if (in->hasPrefix)
299 : : {
1806 tgl@sss.pgh.pa.us 300 :UBC 0 : prefixText = DatumGetTextPP(in->prefixDatum);
301 [ # # # # : 0 : prefixSize = VARSIZE_ANY_EXHDR(prefixText);
# # # # #
# ]
302 : 0 : maxReconstrLen += prefixSize;
303 : : }
304 : :
1806 tgl@sss.pgh.pa.us 305 :CBC 4 : reconstrText = palloc(VARHDRSZ + maxReconstrLen);
306 : 4 : SET_VARSIZE(reconstrText, VARHDRSZ + maxReconstrLen);
307 : :
308 [ + + ]: 4 : if (in->level)
309 : 2 : memcpy(VARDATA(reconstrText),
310 : 2 : VARDATA(reconstructedValue),
311 : 2 : in->level);
312 [ - + ]: 4 : if (prefixSize)
1806 tgl@sss.pgh.pa.us 313 :UBC 0 : memcpy(((char *) VARDATA(reconstrText)) + in->level,
314 [ # # ]: 0 : VARDATA_ANY(prefixText),
315 : : prefixSize);
316 : : /* last byte of reconstrText will be filled in below */
317 : :
318 : : /*
319 : : * Scan the child nodes. For each one, complete the reconstructed value
320 : : * and see if it's consistent with the query. If so, emit an entry into
321 : : * the output arrays.
322 : : */
96 michael@paquier.xyz 323 :GNC 4 : out->nodeNumbers = palloc_array(int, in->nNodes);
324 : 4 : out->levelAdds = palloc_array(int, in->nNodes);
325 : 4 : out->reconstructedValues = palloc_array(Datum, in->nNodes);
1806 tgl@sss.pgh.pa.us 326 :CBC 4 : out->nNodes = 0;
327 : :
328 [ + + ]: 70 : for (i = 0; i < in->nNodes; i++)
329 : : {
330 : 66 : int16 nodeChar = DatumGetInt16(in->nodeLabels[i]);
331 : : int thisLen;
332 : 66 : bool res = true;
333 : : int j;
334 : :
335 : : /* If nodeChar is a dummy value, don't include it in data */
336 [ - + ]: 66 : if (nodeChar <= 0)
1806 tgl@sss.pgh.pa.us 337 :UBC 0 : thisLen = maxReconstrLen - 1;
338 : : else
339 : : {
1806 tgl@sss.pgh.pa.us 340 :CBC 66 : ((unsigned char *) VARDATA(reconstrText))[maxReconstrLen - 1] = nodeChar;
341 : 66 : thisLen = maxReconstrLen;
342 : : }
343 : :
344 [ + + ]: 128 : for (j = 0; j < in->nkeys; j++)
345 : : {
346 : 124 : StrategyNumber strategy = in->scankeys[j].sk_strategy;
347 : : Name inName;
348 : : char *inStr;
349 : : int inSize;
350 : : int r;
351 : :
352 : 124 : inName = DatumGetName(in->scankeys[j].sk_argument);
353 : 124 : inStr = NameStr(*inName);
354 : 124 : inSize = strlen(inStr);
355 : :
356 : 248 : r = memcmp(VARDATA(reconstrText), inStr,
357 : 124 : Min(inSize, thisLen));
358 : :
359 [ + - + - ]: 124 : switch (strategy)
360 : : {
361 : 58 : case BTLessStrategyNumber:
362 : : case BTLessEqualStrategyNumber:
363 [ + + ]: 58 : if (r > 0)
364 : 54 : res = false;
365 : 58 : break;
1806 tgl@sss.pgh.pa.us 366 :UBC 0 : case BTEqualStrategyNumber:
367 [ # # # # ]: 0 : if (r != 0 || inSize < thisLen)
368 : 0 : res = false;
369 : 0 : break;
1806 tgl@sss.pgh.pa.us 370 :CBC 66 : case BTGreaterEqualStrategyNumber:
371 : : case BTGreaterStrategyNumber:
372 [ + + ]: 66 : if (r < 0)
373 : 8 : res = false;
374 : 66 : break;
1806 tgl@sss.pgh.pa.us 375 :UBC 0 : default:
376 [ # # ]: 0 : elog(ERROR, "unrecognized strategy number: %d",
377 : : in->scankeys[j].sk_strategy);
378 : : break;
379 : : }
380 : :
1806 tgl@sss.pgh.pa.us 381 [ + + ]:CBC 124 : if (!res)
382 : 62 : break; /* no need to consider remaining conditions */
383 : : }
384 : :
385 [ + + ]: 66 : if (res)
386 : : {
387 : 4 : out->nodeNumbers[out->nNodes] = i;
388 : 4 : out->levelAdds[out->nNodes] = thisLen - in->level;
389 : 4 : SET_VARSIZE(reconstrText, VARHDRSZ + thisLen);
390 : 8 : out->reconstructedValues[out->nNodes] =
391 : 4 : datumCopy(PointerGetDatum(reconstrText), false, -1);
392 : 4 : out->nNodes++;
393 : : }
394 : : }
395 : :
396 : 4 : PG_RETURN_VOID();
397 : : }
398 : :
399 : 2 : PG_FUNCTION_INFO_V1(spgist_name_leaf_consistent);
400 : : Datum
401 : 126 : spgist_name_leaf_consistent(PG_FUNCTION_ARGS)
402 : : {
403 : 126 : spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
404 : 126 : spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
405 : 126 : int level = in->level;
406 : : text *leafValue,
407 : 126 : *reconstrValue = NULL;
408 : : char *fullValue;
409 : : int fullLen;
410 : : bool res;
411 : : int j;
412 : :
413 : : /* all tests are exact */
414 : 126 : out->recheck = false;
415 : :
416 : 126 : leafValue = DatumGetTextPP(in->leafDatum);
417 : :
418 : : /* As above, in->reconstructedValue isn't toasted or short. */
419 [ + - ]: 126 : if (DatumGetPointer(in->reconstructedValue))
420 : 126 : reconstrValue = (text *) DatumGetPointer(in->reconstructedValue);
421 : :
422 [ - + - + : 126 : Assert(reconstrValue == NULL ? level == 0 :
- - - - -
- - + -
+ ]
423 : : VARSIZE_ANY_EXHDR(reconstrValue) == level);
424 : :
425 : : /* Reconstruct the Name represented by this leaf tuple */
426 : 126 : fullValue = palloc0(NAMEDATALEN);
427 [ - + - - : 126 : fullLen = level + VARSIZE_ANY_EXHDR(leafValue);
- - - - +
- ]
428 [ - + ]: 126 : Assert(fullLen < NAMEDATALEN);
429 [ - + - - : 126 : if (VARSIZE_ANY_EXHDR(leafValue) == 0 && level > 0)
- - - - +
- - + - -
- - ]
430 : : {
1806 tgl@sss.pgh.pa.us 431 :UBC 0 : memcpy(fullValue, VARDATA(reconstrValue),
1806 tgl@sss.pgh.pa.us 432 [ # # # # :EUB : VARSIZE_ANY_EXHDR(reconstrValue));
# # # # #
# ]
433 : : }
434 : : else
435 : : {
1806 tgl@sss.pgh.pa.us 436 [ + - ]:CBC 126 : if (level)
437 : 126 : memcpy(fullValue, VARDATA(reconstrValue), level);
438 [ + - - - : 126 : if (VARSIZE_ANY_EXHDR(leafValue) > 0)
- - - - +
- + - ]
439 [ + - ]: 126 : memcpy(fullValue + level, VARDATA_ANY(leafValue),
1806 tgl@sss.pgh.pa.us 440 [ - + - - :ECB (124) : VARSIZE_ANY_EXHDR(leafValue));
- - - - +
- ]
441 : : }
1806 tgl@sss.pgh.pa.us 442 :CBC 126 : out->leafValue = PointerGetDatum(fullValue);
443 : :
444 : : /* Perform the required comparison(s) */
445 : 126 : res = true;
446 [ + + ]: 260 : for (j = 0; j < in->nkeys; j++)
447 : : {
448 : 234 : StrategyNumber strategy = in->scankeys[j].sk_strategy;
449 : 234 : Name queryName = DatumGetName(in->scankeys[j].sk_argument);
450 : 234 : char *queryStr = NameStr(*queryName);
451 : 234 : int queryLen = strlen(queryStr);
452 : : int r;
453 : :
454 : : /* Non-collation-aware comparison */
455 : 234 : r = memcmp(fullValue, queryStr, Min(queryLen, fullLen));
456 : :
457 [ + + ]: 234 : if (r == 0)
458 : : {
459 [ - + ]: 26 : if (queryLen > fullLen)
1806 tgl@sss.pgh.pa.us 460 :UBC 0 : r = -1;
1806 tgl@sss.pgh.pa.us 461 [ + - ]:CBC 26 : else if (queryLen < fullLen)
462 : 26 : r = 1;
463 : : }
464 : :
465 [ + - - - : 234 : switch (strategy)
+ - ]
466 : : {
467 : 108 : case BTLessStrategyNumber:
468 : 108 : res = (r < 0);
469 : 108 : break;
1806 tgl@sss.pgh.pa.us 470 :UBC 0 : case BTLessEqualStrategyNumber:
471 : 0 : res = (r <= 0);
472 : 0 : break;
473 : 0 : case BTEqualStrategyNumber:
474 : 0 : res = (r == 0);
475 : 0 : break;
476 : 0 : case BTGreaterEqualStrategyNumber:
477 : 0 : res = (r >= 0);
478 : 0 : break;
1806 tgl@sss.pgh.pa.us 479 :CBC 126 : case BTGreaterStrategyNumber:
480 : 126 : res = (r > 0);
481 : 126 : break;
1806 tgl@sss.pgh.pa.us 482 :UBC 0 : default:
483 [ # # ]: 0 : elog(ERROR, "unrecognized strategy number: %d",
484 : : in->scankeys[j].sk_strategy);
485 : : res = false;
486 : : break;
487 : : }
488 : :
1806 tgl@sss.pgh.pa.us 489 [ + + ]:CBC 234 : if (!res)
490 : 100 : break; /* no need to consider remaining conditions */
491 : : }
492 : :
493 : 126 : PG_RETURN_BOOL(res);
494 : : }
495 : :
496 : 2 : PG_FUNCTION_INFO_V1(spgist_name_compress);
497 : : Datum
498 : 6918 : spgist_name_compress(PG_FUNCTION_ARGS)
499 : : {
500 : 6918 : Name inName = PG_GETARG_NAME(0);
501 : 6918 : char *inStr = NameStr(*inName);
502 : :
503 : 6918 : PG_RETURN_DATUM(formTextDatum(inStr, strlen(inStr)));
504 : : }
|