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