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