Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * ginscan.c
4 : * routines to manage scans of inverted index relations
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * src/backend/access/gin/ginscan.c
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres.h"
16 :
17 : #include "access/gin_private.h"
18 : #include "access/relscan.h"
19 : #include "pgstat.h"
20 : #include "utils/memutils.h"
21 : #include "utils/rel.h"
22 :
23 :
24 : IndexScanDesc
2639 tgl 25 CBC 791 : ginbeginscan(Relation rel, int nkeys, int norderbys)
26 : {
27 : IndexScanDesc scan;
28 : GinScanOpaque so;
29 :
30 : /* no order by operators allowed */
4511 31 791 : Assert(norderbys == 0);
32 :
33 791 : scan = RelationGetIndexScan(rel, nkeys, norderbys);
34 :
35 : /* allocate private workspace */
36 791 : so = (GinScanOpaque) palloc(sizeof(GinScanOpaqueData));
37 791 : so->keys = NULL;
38 791 : so->nkeys = 0;
39 791 : so->tempCtx = AllocSetContextCreate(CurrentMemoryContext,
40 : "Gin scan temporary context",
41 : ALLOCSET_DEFAULT_SIZES);
2986 heikki.linnakangas 42 791 : so->keyCtx = AllocSetContextCreate(CurrentMemoryContext,
43 : "Gin scan key context",
44 : ALLOCSET_DEFAULT_SIZES);
4511 tgl 45 791 : initGinState(&so->ginstate, scan->indexRelation);
46 :
47 791 : scan->opaque = so;
48 :
2639 49 791 : return scan;
50 : }
51 :
52 : /*
53 : * Create a new GinScanEntry, unless an equivalent one already exists,
54 : * in which case just return it
55 : */
56 : static GinScanEntry
4474 57 1846 : ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum,
58 : StrategyNumber strategy, int32 searchMode,
59 : Datum queryKey, GinNullCategory queryCategory,
60 : bool isPartialMatch, Pointer extra_data)
61 : {
62 1846 : GinState *ginstate = &so->ginstate;
63 : GinScanEntry scanEntry;
64 : uint32 i;
65 :
66 : /*
67 : * Look for an existing equivalent entry.
68 : *
69 : * Entries with non-null extra_data are never considered identical, since
70 : * we can't know exactly what the opclass might be doing with that.
71 : */
72 1846 : if (extra_data == NULL)
73 : {
74 2755 : for (i = 0; i < so->totalentries; i++)
75 : {
76 1750 : GinScanEntry prevEntry = so->entries[i];
77 :
78 1750 : if (prevEntry->extra_data == NULL &&
79 1600 : prevEntry->isPartialMatch == isPartialMatch &&
80 1600 : prevEntry->strategy == strategy &&
81 1530 : prevEntry->searchMode == searchMode &&
82 3048 : prevEntry->attnum == attnum &&
83 1518 : ginCompareEntries(ginstate, attnum,
84 : prevEntry->queryKey,
85 1518 : prevEntry->queryCategory,
86 : queryKey,
87 : queryCategory) == 0)
88 : {
89 : /* Successful match */
4474 tgl 90 UBC 0 : return prevEntry;
91 : }
92 : }
93 : }
94 :
95 : /* Nope, create a new entry */
4474 tgl 96 CBC 1846 : scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData));
97 1846 : scanEntry->queryKey = queryKey;
98 1846 : scanEntry->queryCategory = queryCategory;
99 1846 : scanEntry->isPartialMatch = isPartialMatch;
100 1846 : scanEntry->extra_data = extra_data;
101 1846 : scanEntry->strategy = strategy;
102 1846 : scanEntry->searchMode = searchMode;
103 1846 : scanEntry->attnum = attnum;
104 :
105 1846 : scanEntry->buffer = InvalidBuffer;
106 1846 : ItemPointerSetMin(&scanEntry->curItem);
107 1846 : scanEntry->matchBitmap = NULL;
108 1846 : scanEntry->matchIterator = NULL;
109 1846 : scanEntry->matchResult = NULL;
110 1846 : scanEntry->list = NULL;
111 1846 : scanEntry->nlist = 0;
112 1846 : scanEntry->offset = InvalidOffsetNumber;
113 1846 : scanEntry->isFinished = false;
114 1846 : scanEntry->reduceResult = false;
115 :
116 : /* Add it to so's array */
117 1846 : if (so->totalentries >= so->allocentries)
118 : {
119 3 : so->allocentries *= 2;
120 3 : so->entries = (GinScanEntry *)
121 3 : repalloc(so->entries, so->allocentries * sizeof(GinScanEntry));
122 : }
123 1846 : so->entries[so->totalentries++] = scanEntry;
124 :
125 1846 : return scanEntry;
126 : }
127 :
128 : /*
129 : * Append hidden scan entry of given category to the scan key.
130 : *
131 : * NB: this had better be called at most once per scan key, since
132 : * ginFillScanKey leaves room for only one hidden entry. Currently,
133 : * it seems sufficiently clear that this is true that we don't bother
134 : * with any cross-check logic.
135 : */
136 : static void
1177 akorotkov 137 163 : ginScanKeyAddHiddenEntry(GinScanOpaque so, GinScanKey key,
138 : GinNullCategory queryCategory)
139 : {
140 163 : int i = key->nentries++;
141 :
142 : /* strategy is of no interest because this is not a partial-match item */
143 163 : key->scanEntry[i] = ginFillScanEntry(so, key->attnum,
144 : InvalidStrategy, key->searchMode,
145 : (Datum) 0, queryCategory,
146 : false, NULL);
147 163 : }
148 :
149 : /*
150 : * Initialize the next GinScanKey using the output from the extractQueryFn
151 : */
152 : static void
4474 tgl 153 852 : ginFillScanKey(GinScanOpaque so, OffsetNumber attnum,
154 : StrategyNumber strategy, int32 searchMode,
155 : Datum query, uint32 nQueryValues,
156 : Datum *queryValues, GinNullCategory *queryCategories,
157 : bool *partial_matches, Pointer *extra_data)
158 : {
159 852 : GinScanKey key = &(so->keys[so->nkeys++]);
160 852 : GinState *ginstate = &so->ginstate;
161 : uint32 i;
162 :
4475 163 852 : key->nentries = nQueryValues;
1177 akorotkov 164 852 : key->nuserentries = nQueryValues;
165 :
166 : /* Allocate one extra array slot for possible "hidden" entry */
167 1704 : key->scanEntry = (GinScanEntry *) palloc(sizeof(GinScanEntry) *
168 852 : (nQueryValues + 1));
169 1704 : key->entryRes = (GinTernaryValue *) palloc0(sizeof(GinTernaryValue) *
170 852 : (nQueryValues + 1));
171 :
4475 tgl 172 852 : key->query = query;
173 852 : key->queryValues = queryValues;
174 852 : key->queryCategories = queryCategories;
175 852 : key->extra_data = extra_data;
6186 teodor 176 852 : key->strategy = strategy;
4475 tgl 177 852 : key->searchMode = searchMode;
5385 178 852 : key->attnum = attnum;
179 :
180 : /*
181 : * Initially, scan keys of GIN_SEARCH_MODE_ALL mode are marked
182 : * excludeOnly. This might get changed later.
183 : */
1177 akorotkov 184 852 : key->excludeOnly = (searchMode == GIN_SEARCH_MODE_ALL);
185 :
4635 tgl 186 852 : ItemPointerSetMin(&key->curItem);
4474 187 852 : key->curItemMatches = false;
188 852 : key->recheckCurItem = false;
189 852 : key->isFinished = false;
2991 heikki.linnakangas 190 852 : key->nrequired = 0;
191 852 : key->nadditional = 0;
192 852 : key->requiredEntries = NULL;
193 852 : key->additionalEntries = NULL;
194 :
3348 195 852 : ginInitConsistentFunction(ginstate, key);
196 :
197 : /* Set up normal scan entries using extractQueryFn's outputs */
4475 tgl 198 2535 : for (i = 0; i < nQueryValues; i++)
199 : {
200 : Datum queryKey;
201 : GinNullCategory queryCategory;
202 : bool isPartialMatch;
203 : Pointer this_extra;
204 :
1177 akorotkov 205 1683 : queryKey = queryValues[i];
206 1683 : queryCategory = queryCategories[i];
207 1683 : isPartialMatch =
208 1683 : (ginstate->canPartialMatch[attnum - 1] && partial_matches)
209 1683 : ? partial_matches[i] : false;
210 1683 : this_extra = (extra_data) ? extra_data[i] : NULL;
211 :
4474 tgl 212 1683 : key->scanEntry[i] = ginFillScanEntry(so, attnum,
213 : strategy, searchMode,
214 : queryKey, queryCategory,
215 : isPartialMatch, this_extra);
216 : }
217 :
218 : /*
219 : * For GIN_SEARCH_MODE_INCLUDE_EMPTY and GIN_SEARCH_MODE_EVERYTHING search
220 : * modes, we add the "hidden" entry immediately. GIN_SEARCH_MODE_ALL is
221 : * handled later, since we might be able to omit the hidden entry for it.
222 : */
1177 akorotkov 223 852 : if (searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY)
224 22 : ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_ITEM);
225 830 : else if (searchMode == GIN_SEARCH_MODE_EVERYTHING)
1177 akorotkov 226 UBC 0 : ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY);
6186 teodor 227 CBC 852 : }
228 :
229 : /*
230 : * Release current scan keys, if any.
231 : */
232 : void
2991 heikki.linnakangas 233 2379 : ginFreeScanKeys(GinScanOpaque so)
234 : {
235 : uint32 i;
236 :
4474 tgl 237 2379 : if (so->keys == NULL)
6186 teodor 238 1585 : return;
239 :
4474 tgl 240 2640 : for (i = 0; i < so->totalentries; i++)
241 : {
242 1846 : GinScanEntry entry = so->entries[i];
243 :
244 1846 : if (entry->buffer != InvalidBuffer)
4474 tgl 245 UBC 0 : ReleaseBuffer(entry->buffer);
2583 tgl 246 CBC 1846 : if (entry->list)
247 1083 : pfree(entry->list);
4474 248 1846 : if (entry->matchIterator)
4474 tgl 249 UBC 0 : tbm_end_iterate(entry->matchIterator);
4474 tgl 250 CBC 1846 : if (entry->matchBitmap)
251 267 : tbm_free(entry->matchBitmap);
252 : }
253 :
2986 heikki.linnakangas 254 794 : MemoryContextResetAndDeleteChildren(so->keyCtx);
255 :
256 794 : so->keys = NULL;
257 794 : so->nkeys = 0;
4474 tgl 258 794 : so->entries = NULL;
259 794 : so->totalentries = 0;
260 : }
261 :
262 : void
4557 263 794 : ginNewScanKey(IndexScanDesc scan)
264 : {
6031 bruce 265 794 : ScanKey scankey = scan->keyData;
6186 teodor 266 794 : GinScanOpaque so = (GinScanOpaque) scan->opaque;
267 : int i;
4475 tgl 268 794 : bool hasNullQuery = false;
1177 akorotkov 269 794 : bool attrHasNormalScan[INDEX_MAX_KEYS] = {false};
270 : MemoryContext oldCtx;
271 :
272 : /*
273 : * Allocate all the scan key information in the key context. (If
274 : * extractQuery leaks anything there, it won't be reset until the end of
275 : * scan or rescan, but that's OK.)
276 : */
2986 heikki.linnakangas 277 794 : oldCtx = MemoryContextSwitchTo(so->keyCtx);
278 :
279 : /* if no scan keys provided, allocate extra EVERYTHING GinScanKey */
4475 tgl 280 794 : so->keys = (GinScanKey)
281 794 : palloc(Max(scan->numberOfKeys, 1) * sizeof(GinScanKeyData));
4474 282 794 : so->nkeys = 0;
283 :
284 : /* initialize expansible array of GinScanEntry pointers */
285 794 : so->totalentries = 0;
286 794 : so->allocentries = 32;
287 794 : so->entries = (GinScanEntry *)
2583 288 794 : palloc(so->allocentries * sizeof(GinScanEntry));
289 :
5912 teodor 290 794 : so->isVoidRes = false;
291 :
6031 bruce 292 1646 : for (i = 0; i < scan->numberOfKeys; i++)
293 : {
5128 tgl 294 858 : ScanKey skey = &scankey[i];
295 : Datum *queryValues;
4475 296 858 : int32 nQueryValues = 0;
5050 bruce 297 858 : bool *partial_matches = NULL;
298 858 : Pointer *extra_data = NULL;
4475 tgl 299 858 : bool *nullFlags = NULL;
300 : GinNullCategory *categories;
301 858 : int32 searchMode = GIN_SEARCH_MODE_DEFAULT;
302 :
303 : /*
304 : * We assume that GIN-indexable operators are strict, so a null query
305 : * argument means an unsatisfiable query.
306 : */
5128 307 858 : if (skey->sk_flags & SK_ISNULL)
308 : {
5117 teodor 309 UBC 0 : so->isVoidRes = true;
5117 teodor 310 CBC 6 : break;
311 : }
312 :
313 : /* OK to call the extractQueryFn */
314 : queryValues = (Datum *)
4370 tgl 315 2574 : DatumGetPointer(FunctionCall7Coll(&so->ginstate.extractQueryFn[skey->sk_attno - 1],
2118 316 858 : so->ginstate.supportCollation[skey->sk_attno - 1],
317 : skey->sk_argument,
318 : PointerGetDatum(&nQueryValues),
319 858 : UInt16GetDatum(skey->sk_strategy),
320 : PointerGetDatum(&partial_matches),
321 : PointerGetDatum(&extra_data),
322 : PointerGetDatum(&nullFlags),
323 : PointerGetDatum(&searchMode)));
324 :
325 : /*
326 : * If bogus searchMode is returned, treat as GIN_SEARCH_MODE_ALL; note
327 : * in particular we don't allow extractQueryFn to select
328 : * GIN_SEARCH_MODE_EVERYTHING.
329 : */
4475 330 858 : if (searchMode < GIN_SEARCH_MODE_DEFAULT ||
331 858 : searchMode > GIN_SEARCH_MODE_ALL)
4475 tgl 332 UBC 0 : searchMode = GIN_SEARCH_MODE_ALL;
333 :
334 : /* Non-default modes require the index to have placeholders */
4475 tgl 335 CBC 858 : if (searchMode != GIN_SEARCH_MODE_DEFAULT)
336 182 : hasNullQuery = true;
337 :
338 : /*
339 : * In default mode, no keys means an unsatisfiable query.
340 : */
341 858 : if (queryValues == NULL || nQueryValues <= 0)
342 : {
343 152 : if (searchMode == GIN_SEARCH_MODE_DEFAULT)
344 : {
345 6 : so->isVoidRes = true;
346 6 : break;
347 : }
348 146 : nQueryValues = 0; /* ensure sane value */
349 : }
350 :
351 : /*
352 : * Create GinNullCategory representation. If the extractQueryFn
353 : * didn't create a nullFlags array, we assume everything is non-null.
354 : * While at it, detect whether any null keys are present.
355 : */
1930 peter_e 356 852 : categories = (GinNullCategory *) palloc0(nQueryValues * sizeof(GinNullCategory));
357 852 : if (nullFlags)
358 : {
359 : int32 j;
360 :
4475 tgl 361 512 : for (j = 0; j < nQueryValues; j++)
362 : {
363 232 : if (nullFlags[j])
364 : {
1930 peter_e 365 UBC 0 : categories[j] = GIN_CAT_NULL_KEY;
4475 tgl 366 0 : hasNullQuery = true;
367 : }
368 : }
369 : }
370 :
4474 tgl 371 CBC 852 : ginFillScanKey(so, skey->sk_attno,
372 852 : skey->sk_strategy, searchMode,
373 : skey->sk_argument, nQueryValues,
374 : queryValues, categories,
375 : partial_matches, extra_data);
376 :
377 : /* Remember if we had any non-excludeOnly keys */
1177 akorotkov 378 852 : if (searchMode != GIN_SEARCH_MODE_ALL)
379 692 : attrHasNormalScan[skey->sk_attno - 1] = true;
380 : }
381 :
382 : /*
383 : * Processing GIN_SEARCH_MODE_ALL scan keys requires us to make a second
384 : * pass over the scan keys. Above we marked each such scan key as
385 : * excludeOnly. If the involved column has any normal (not excludeOnly)
386 : * scan key as well, then we can leave it like that. Otherwise, one
387 : * excludeOnly scan key must receive a GIN_CAT_EMPTY_QUERY hidden entry
388 : * and be set to normal (excludeOnly = false).
389 : */
390 1646 : for (i = 0; i < so->nkeys; i++)
391 : {
392 852 : GinScanKey key = &so->keys[i];
393 :
394 852 : if (key->searchMode != GIN_SEARCH_MODE_ALL)
395 692 : continue;
396 :
397 160 : if (!attrHasNormalScan[key->attnum - 1])
398 : {
399 141 : key->excludeOnly = false;
400 141 : ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY);
401 141 : attrHasNormalScan[key->attnum - 1] = true;
402 : }
403 : }
404 :
405 : /*
406 : * If there are no regular scan keys, generate an EVERYTHING scankey to
407 : * drive a full-index scan.
408 : */
4474 tgl 409 794 : if (so->nkeys == 0 && !so->isVoidRes)
410 : {
4475 tgl 411 UBC 0 : hasNullQuery = true;
4474 412 0 : ginFillScanKey(so, FirstOffsetNumber,
413 : InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING,
414 : (Datum) 0, 0,
415 : NULL, NULL, NULL, NULL);
416 : }
417 :
418 : /*
419 : * If the index is version 0, it may be missing null and placeholder
420 : * entries, which would render searches for nulls and full-index scans
421 : * unreliable. Throw an error if so.
422 : */
4475 tgl 423 CBC 794 : if (hasNullQuery && !so->isVoidRes)
424 : {
425 : GinStatsData ginStats;
426 :
427 162 : ginGetStats(scan->indexRelation, &ginStats);
428 162 : if (ginStats.ginVersion < 1)
4475 tgl 429 UBC 0 : ereport(ERROR,
430 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
431 : errmsg("old GIN indexes do not support whole-index scans nor searches for nulls"),
432 : errhint("To fix this, do REINDEX INDEX \"%s\".",
433 : RelationGetRelationName(scan->indexRelation))));
434 : }
435 :
2986 heikki.linnakangas 436 CBC 794 : MemoryContextSwitchTo(oldCtx);
437 :
5796 tgl 438 794 : pgstat_count_index_scan(scan->indexRelation);
6186 teodor 439 794 : }
440 :
441 : void
2639 tgl 442 794 : ginrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
443 : ScanKey orderbys, int norderbys)
444 : {
4511 445 794 : GinScanOpaque so = (GinScanOpaque) scan->opaque;
446 :
2991 heikki.linnakangas 447 794 : ginFreeScanKeys(so);
448 :
6031 bruce 449 794 : if (scankey && scan->numberOfKeys > 0)
450 : {
6186 teodor 451 794 : memmove(scan->keyData, scankey,
6031 bruce 452 794 : scan->numberOfKeys * sizeof(ScanKeyData));
453 : }
6186 teodor 454 794 : }
455 :
456 :
457 : void
2639 tgl 458 791 : ginendscan(IndexScanDesc scan)
459 : {
6031 bruce 460 791 : GinScanOpaque so = (GinScanOpaque) scan->opaque;
461 :
2991 heikki.linnakangas 462 791 : ginFreeScanKeys(so);
463 :
4511 tgl 464 791 : MemoryContextDelete(so->tempCtx);
2986 heikki.linnakangas 465 791 : MemoryContextDelete(so->keyCtx);
466 :
4511 tgl 467 791 : pfree(so);
6186 teodor 468 791 : }
|