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