1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * ginscan.c |
4 | * routines to manage scans of inverted index relations |
5 | * |
6 | * |
7 | * Portions Copyright (c) 1996-2019, 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 | { |
27 | IndexScanDesc scan; |
28 | GinScanOpaque so; |
29 | |
30 | /* no order by operators allowed */ |
31 | Assert(norderbys == 0); |
32 | |
33 | scan = RelationGetIndexScan(rel, nkeys, norderbys); |
34 | |
35 | /* allocate private workspace */ |
36 | so = (GinScanOpaque) palloc(sizeof(GinScanOpaqueData)); |
37 | so->keys = NULL; |
38 | so->nkeys = 0; |
39 | so->tempCtx = AllocSetContextCreate(CurrentMemoryContext, |
40 | "Gin scan temporary context" , |
41 | ALLOCSET_DEFAULT_SIZES); |
42 | so->keyCtx = AllocSetContextCreate(CurrentMemoryContext, |
43 | "Gin scan key context" , |
44 | ALLOCSET_DEFAULT_SIZES); |
45 | initGinState(&so->ginstate, scan->indexRelation); |
46 | |
47 | scan->opaque = so; |
48 | |
49 | 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 |
57 | ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum, |
58 | StrategyNumber strategy, int32 searchMode, |
59 | Datum queryKey, GinNullCategory queryCategory, |
60 | bool isPartialMatch, Pointer ) |
61 | { |
62 | 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 | if (extra_data == NULL) |
73 | { |
74 | for (i = 0; i < so->totalentries; i++) |
75 | { |
76 | GinScanEntry prevEntry = so->entries[i]; |
77 | |
78 | if (prevEntry->extra_data == NULL && |
79 | prevEntry->isPartialMatch == isPartialMatch && |
80 | prevEntry->strategy == strategy && |
81 | prevEntry->searchMode == searchMode && |
82 | prevEntry->attnum == attnum && |
83 | ginCompareEntries(ginstate, attnum, |
84 | prevEntry->queryKey, |
85 | prevEntry->queryCategory, |
86 | queryKey, |
87 | queryCategory) == 0) |
88 | { |
89 | /* Successful match */ |
90 | return prevEntry; |
91 | } |
92 | } |
93 | } |
94 | |
95 | /* Nope, create a new entry */ |
96 | scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData)); |
97 | scanEntry->queryKey = queryKey; |
98 | scanEntry->queryCategory = queryCategory; |
99 | scanEntry->isPartialMatch = isPartialMatch; |
100 | scanEntry->extra_data = extra_data; |
101 | scanEntry->strategy = strategy; |
102 | scanEntry->searchMode = searchMode; |
103 | scanEntry->attnum = attnum; |
104 | |
105 | scanEntry->buffer = InvalidBuffer; |
106 | ItemPointerSetMin(&scanEntry->curItem); |
107 | scanEntry->matchBitmap = NULL; |
108 | scanEntry->matchIterator = NULL; |
109 | scanEntry->matchResult = NULL; |
110 | scanEntry->list = NULL; |
111 | scanEntry->nlist = 0; |
112 | scanEntry->offset = InvalidOffsetNumber; |
113 | scanEntry->isFinished = false; |
114 | scanEntry->reduceResult = false; |
115 | |
116 | /* Add it to so's array */ |
117 | if (so->totalentries >= so->allocentries) |
118 | { |
119 | so->allocentries *= 2; |
120 | so->entries = (GinScanEntry *) |
121 | repalloc(so->entries, so->allocentries * sizeof(GinScanEntry)); |
122 | } |
123 | so->entries[so->totalentries++] = scanEntry; |
124 | |
125 | return scanEntry; |
126 | } |
127 | |
128 | /* |
129 | * Initialize the next GinScanKey using the output from the extractQueryFn |
130 | */ |
131 | static void |
132 | ginFillScanKey(GinScanOpaque so, OffsetNumber attnum, |
133 | StrategyNumber strategy, int32 searchMode, |
134 | Datum query, uint32 nQueryValues, |
135 | Datum *queryValues, GinNullCategory *queryCategories, |
136 | bool *partial_matches, Pointer *) |
137 | { |
138 | GinScanKey key = &(so->keys[so->nkeys++]); |
139 | GinState *ginstate = &so->ginstate; |
140 | uint32 nUserQueryValues = nQueryValues; |
141 | uint32 i; |
142 | |
143 | /* Non-default search modes add one "hidden" entry to each key */ |
144 | if (searchMode != GIN_SEARCH_MODE_DEFAULT) |
145 | nQueryValues++; |
146 | key->nentries = nQueryValues; |
147 | key->nuserentries = nUserQueryValues; |
148 | |
149 | key->scanEntry = (GinScanEntry *) palloc(sizeof(GinScanEntry) * nQueryValues); |
150 | key->entryRes = (GinTernaryValue *) palloc0(sizeof(GinTernaryValue) * nQueryValues); |
151 | |
152 | key->query = query; |
153 | key->queryValues = queryValues; |
154 | key->queryCategories = queryCategories; |
155 | key->extra_data = extra_data; |
156 | key->strategy = strategy; |
157 | key->searchMode = searchMode; |
158 | key->attnum = attnum; |
159 | |
160 | ItemPointerSetMin(&key->curItem); |
161 | key->curItemMatches = false; |
162 | key->recheckCurItem = false; |
163 | key->isFinished = false; |
164 | key->nrequired = 0; |
165 | key->nadditional = 0; |
166 | key->requiredEntries = NULL; |
167 | key->additionalEntries = NULL; |
168 | |
169 | ginInitConsistentFunction(ginstate, key); |
170 | |
171 | for (i = 0; i < nQueryValues; i++) |
172 | { |
173 | Datum queryKey; |
174 | GinNullCategory queryCategory; |
175 | bool isPartialMatch; |
176 | Pointer ; |
177 | |
178 | if (i < nUserQueryValues) |
179 | { |
180 | /* set up normal entry using extractQueryFn's outputs */ |
181 | queryKey = queryValues[i]; |
182 | queryCategory = queryCategories[i]; |
183 | isPartialMatch = |
184 | (ginstate->canPartialMatch[attnum - 1] && partial_matches) |
185 | ? partial_matches[i] : false; |
186 | this_extra = (extra_data) ? extra_data[i] : NULL; |
187 | } |
188 | else |
189 | { |
190 | /* set up hidden entry */ |
191 | queryKey = (Datum) 0; |
192 | switch (searchMode) |
193 | { |
194 | case GIN_SEARCH_MODE_INCLUDE_EMPTY: |
195 | queryCategory = GIN_CAT_EMPTY_ITEM; |
196 | break; |
197 | case GIN_SEARCH_MODE_ALL: |
198 | queryCategory = GIN_CAT_EMPTY_QUERY; |
199 | break; |
200 | case GIN_SEARCH_MODE_EVERYTHING: |
201 | queryCategory = GIN_CAT_EMPTY_QUERY; |
202 | break; |
203 | default: |
204 | elog(ERROR, "unexpected searchMode: %d" , searchMode); |
205 | queryCategory = 0; /* keep compiler quiet */ |
206 | break; |
207 | } |
208 | isPartialMatch = false; |
209 | this_extra = NULL; |
210 | |
211 | /* |
212 | * We set the strategy to a fixed value so that ginFillScanEntry |
213 | * can combine these entries for different scan keys. This is |
214 | * safe because the strategy value in the entry struct is only |
215 | * used for partial-match cases. It's OK to overwrite our local |
216 | * variable here because this is the last loop iteration. |
217 | */ |
218 | strategy = InvalidStrategy; |
219 | } |
220 | |
221 | key->scanEntry[i] = ginFillScanEntry(so, attnum, |
222 | strategy, searchMode, |
223 | queryKey, queryCategory, |
224 | isPartialMatch, this_extra); |
225 | } |
226 | } |
227 | |
228 | /* |
229 | * Release current scan keys, if any. |
230 | */ |
231 | void |
232 | ginFreeScanKeys(GinScanOpaque so) |
233 | { |
234 | uint32 i; |
235 | |
236 | if (so->keys == NULL) |
237 | return; |
238 | |
239 | for (i = 0; i < so->totalentries; i++) |
240 | { |
241 | GinScanEntry entry = so->entries[i]; |
242 | |
243 | if (entry->buffer != InvalidBuffer) |
244 | ReleaseBuffer(entry->buffer); |
245 | if (entry->list) |
246 | pfree(entry->list); |
247 | if (entry->matchIterator) |
248 | tbm_end_iterate(entry->matchIterator); |
249 | if (entry->matchBitmap) |
250 | tbm_free(entry->matchBitmap); |
251 | } |
252 | |
253 | MemoryContextResetAndDeleteChildren(so->keyCtx); |
254 | |
255 | so->keys = NULL; |
256 | so->nkeys = 0; |
257 | so->entries = NULL; |
258 | so->totalentries = 0; |
259 | } |
260 | |
261 | void |
262 | ginNewScanKey(IndexScanDesc scan) |
263 | { |
264 | ScanKey scankey = scan->keyData; |
265 | GinScanOpaque so = (GinScanOpaque) scan->opaque; |
266 | int i; |
267 | bool hasNullQuery = false; |
268 | MemoryContext oldCtx; |
269 | |
270 | /* |
271 | * Allocate all the scan key information in the key context. (If |
272 | * extractQuery leaks anything there, it won't be reset until the end of |
273 | * scan or rescan, but that's OK.) |
274 | */ |
275 | oldCtx = MemoryContextSwitchTo(so->keyCtx); |
276 | |
277 | /* if no scan keys provided, allocate extra EVERYTHING GinScanKey */ |
278 | so->keys = (GinScanKey) |
279 | palloc(Max(scan->numberOfKeys, 1) * sizeof(GinScanKeyData)); |
280 | so->nkeys = 0; |
281 | |
282 | /* initialize expansible array of GinScanEntry pointers */ |
283 | so->totalentries = 0; |
284 | so->allocentries = 32; |
285 | so->entries = (GinScanEntry *) |
286 | palloc(so->allocentries * sizeof(GinScanEntry)); |
287 | |
288 | so->isVoidRes = false; |
289 | |
290 | for (i = 0; i < scan->numberOfKeys; i++) |
291 | { |
292 | ScanKey skey = &scankey[i]; |
293 | Datum *queryValues; |
294 | int32 nQueryValues = 0; |
295 | bool *partial_matches = NULL; |
296 | Pointer * = NULL; |
297 | bool *nullFlags = NULL; |
298 | GinNullCategory *categories; |
299 | int32 searchMode = GIN_SEARCH_MODE_DEFAULT; |
300 | |
301 | /* |
302 | * We assume that GIN-indexable operators are strict, so a null query |
303 | * argument means an unsatisfiable query. |
304 | */ |
305 | if (skey->sk_flags & SK_ISNULL) |
306 | { |
307 | so->isVoidRes = true; |
308 | break; |
309 | } |
310 | |
311 | /* OK to call the extractQueryFn */ |
312 | queryValues = (Datum *) |
313 | DatumGetPointer(FunctionCall7Coll(&so->ginstate.extractQueryFn[skey->sk_attno - 1], |
314 | so->ginstate.supportCollation[skey->sk_attno - 1], |
315 | skey->sk_argument, |
316 | PointerGetDatum(&nQueryValues), |
317 | UInt16GetDatum(skey->sk_strategy), |
318 | PointerGetDatum(&partial_matches), |
319 | PointerGetDatum(&extra_data), |
320 | PointerGetDatum(&nullFlags), |
321 | PointerGetDatum(&searchMode))); |
322 | |
323 | /* |
324 | * If bogus searchMode is returned, treat as GIN_SEARCH_MODE_ALL; note |
325 | * in particular we don't allow extractQueryFn to select |
326 | * GIN_SEARCH_MODE_EVERYTHING. |
327 | */ |
328 | if (searchMode < GIN_SEARCH_MODE_DEFAULT || |
329 | searchMode > GIN_SEARCH_MODE_ALL) |
330 | searchMode = GIN_SEARCH_MODE_ALL; |
331 | |
332 | /* Non-default modes require the index to have placeholders */ |
333 | if (searchMode != GIN_SEARCH_MODE_DEFAULT) |
334 | hasNullQuery = true; |
335 | |
336 | /* |
337 | * In default mode, no keys means an unsatisfiable query. |
338 | */ |
339 | if (queryValues == NULL || nQueryValues <= 0) |
340 | { |
341 | if (searchMode == GIN_SEARCH_MODE_DEFAULT) |
342 | { |
343 | so->isVoidRes = true; |
344 | break; |
345 | } |
346 | nQueryValues = 0; /* ensure sane value */ |
347 | } |
348 | |
349 | /* |
350 | * Create GinNullCategory representation. If the extractQueryFn |
351 | * didn't create a nullFlags array, we assume everything is non-null. |
352 | * While at it, detect whether any null keys are present. |
353 | */ |
354 | categories = (GinNullCategory *) palloc0(nQueryValues * sizeof(GinNullCategory)); |
355 | if (nullFlags) |
356 | { |
357 | int32 j; |
358 | |
359 | for (j = 0; j < nQueryValues; j++) |
360 | { |
361 | if (nullFlags[j]) |
362 | { |
363 | categories[j] = GIN_CAT_NULL_KEY; |
364 | hasNullQuery = true; |
365 | } |
366 | } |
367 | } |
368 | |
369 | ginFillScanKey(so, skey->sk_attno, |
370 | skey->sk_strategy, searchMode, |
371 | skey->sk_argument, nQueryValues, |
372 | queryValues, categories, |
373 | partial_matches, extra_data); |
374 | } |
375 | |
376 | /* |
377 | * If there are no regular scan keys, generate an EVERYTHING scankey to |
378 | * drive a full-index scan. |
379 | */ |
380 | if (so->nkeys == 0 && !so->isVoidRes) |
381 | { |
382 | hasNullQuery = true; |
383 | ginFillScanKey(so, FirstOffsetNumber, |
384 | InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING, |
385 | (Datum) 0, 0, |
386 | NULL, NULL, NULL, NULL); |
387 | } |
388 | |
389 | /* |
390 | * If the index is version 0, it may be missing null and placeholder |
391 | * entries, which would render searches for nulls and full-index scans |
392 | * unreliable. Throw an error if so. |
393 | */ |
394 | if (hasNullQuery && !so->isVoidRes) |
395 | { |
396 | GinStatsData ginStats; |
397 | |
398 | ginGetStats(scan->indexRelation, &ginStats); |
399 | if (ginStats.ginVersion < 1) |
400 | ereport(ERROR, |
401 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
402 | errmsg("old GIN indexes do not support whole-index scans nor searches for nulls" ), |
403 | errhint("To fix this, do REINDEX INDEX \"%s\"." , |
404 | RelationGetRelationName(scan->indexRelation)))); |
405 | } |
406 | |
407 | MemoryContextSwitchTo(oldCtx); |
408 | |
409 | pgstat_count_index_scan(scan->indexRelation); |
410 | } |
411 | |
412 | void |
413 | ginrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, |
414 | ScanKey orderbys, int norderbys) |
415 | { |
416 | GinScanOpaque so = (GinScanOpaque) scan->opaque; |
417 | |
418 | ginFreeScanKeys(so); |
419 | |
420 | if (scankey && scan->numberOfKeys > 0) |
421 | { |
422 | memmove(scan->keyData, scankey, |
423 | scan->numberOfKeys * sizeof(ScanKeyData)); |
424 | } |
425 | } |
426 | |
427 | |
428 | void |
429 | ginendscan(IndexScanDesc scan) |
430 | { |
431 | GinScanOpaque so = (GinScanOpaque) scan->opaque; |
432 | |
433 | ginFreeScanKeys(so); |
434 | |
435 | MemoryContextDelete(so->tempCtx); |
436 | MemoryContextDelete(so->keyCtx); |
437 | |
438 | pfree(so); |
439 | } |
440 | |