1 /*-------------------------------------------------------------------------
2 *
3 * ginscan.c
4 * routines to manage scans of inverted index relations
5 *
6 *
7 * Portions Copyright (c) 1996-2017, 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
ginbeginscan(Relation rel,int nkeys,int norderbys)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
ginFillScanEntry(GinScanOpaque so,OffsetNumber attnum,StrategyNumber strategy,int32 searchMode,Datum queryKey,GinNullCategory queryCategory,bool isPartialMatch,Pointer extra_data)57 ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum,
58 StrategyNumber strategy, int32 searchMode,
59 Datum queryKey, GinNullCategory queryCategory,
60 bool isPartialMatch, Pointer extra_data)
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
ginFillScanKey(GinScanOpaque so,OffsetNumber attnum,StrategyNumber strategy,int32 searchMode,Datum query,uint32 nQueryValues,Datum * queryValues,GinNullCategory * queryCategories,bool * partial_matches,Pointer * extra_data)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 *extra_data)
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 this_extra;
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
ginFreeScanKeys(GinScanOpaque so)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
ginNewScanKey(IndexScanDesc scan)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 *extra_data = NULL;
297 bool *nullFlags = NULL;
298 int32 searchMode = GIN_SEARCH_MODE_DEFAULT;
299
300 /*
301 * We assume that GIN-indexable operators are strict, so a null query
302 * argument means an unsatisfiable query.
303 */
304 if (skey->sk_flags & SK_ISNULL)
305 {
306 so->isVoidRes = true;
307 break;
308 }
309
310 /* OK to call the extractQueryFn */
311 queryValues = (Datum *)
312 DatumGetPointer(FunctionCall7Coll(&so->ginstate.extractQueryFn[skey->sk_attno - 1],
313 so->ginstate.supportCollation[skey->sk_attno - 1],
314 skey->sk_argument,
315 PointerGetDatum(&nQueryValues),
316 UInt16GetDatum(skey->sk_strategy),
317 PointerGetDatum(&partial_matches),
318 PointerGetDatum(&extra_data),
319 PointerGetDatum(&nullFlags),
320 PointerGetDatum(&searchMode)));
321
322 /*
323 * If bogus searchMode is returned, treat as GIN_SEARCH_MODE_ALL; note
324 * in particular we don't allow extractQueryFn to select
325 * GIN_SEARCH_MODE_EVERYTHING.
326 */
327 if (searchMode < GIN_SEARCH_MODE_DEFAULT ||
328 searchMode > GIN_SEARCH_MODE_ALL)
329 searchMode = GIN_SEARCH_MODE_ALL;
330
331 /* Non-default modes require the index to have placeholders */
332 if (searchMode != GIN_SEARCH_MODE_DEFAULT)
333 hasNullQuery = true;
334
335 /*
336 * In default mode, no keys means an unsatisfiable query.
337 */
338 if (queryValues == NULL || nQueryValues <= 0)
339 {
340 if (searchMode == GIN_SEARCH_MODE_DEFAULT)
341 {
342 so->isVoidRes = true;
343 break;
344 }
345 nQueryValues = 0; /* ensure sane value */
346 }
347
348 /*
349 * If the extractQueryFn didn't create a nullFlags array, create one,
350 * assuming that everything's non-null. Otherwise, run through the
351 * array and make sure each value is exactly 0 or 1; this ensures
352 * binary compatibility with the GinNullCategory representation. While
353 * at it, detect whether any null keys are present.
354 */
355 if (nullFlags == NULL)
356 nullFlags = (bool *) palloc0(nQueryValues * sizeof(bool));
357 else
358 {
359 int32 j;
360
361 for (j = 0; j < nQueryValues; j++)
362 {
363 if (nullFlags[j])
364 {
365 nullFlags[j] = true; /* not any other nonzero value */
366 hasNullQuery = true;
367 }
368 }
369 }
370 /* now we can use the nullFlags as category codes */
371
372 ginFillScanKey(so, skey->sk_attno,
373 skey->sk_strategy, searchMode,
374 skey->sk_argument, nQueryValues,
375 queryValues, (GinNullCategory *) nullFlags,
376 partial_matches, extra_data);
377 }
378
379 /*
380 * If there are no regular scan keys, generate an EVERYTHING scankey to
381 * drive a full-index scan.
382 */
383 if (so->nkeys == 0 && !so->isVoidRes)
384 {
385 hasNullQuery = true;
386 ginFillScanKey(so, FirstOffsetNumber,
387 InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING,
388 (Datum) 0, 0,
389 NULL, NULL, NULL, NULL);
390 }
391
392 /*
393 * If the index is version 0, it may be missing null and placeholder
394 * entries, which would render searches for nulls and full-index scans
395 * unreliable. Throw an error if so.
396 */
397 if (hasNullQuery && !so->isVoidRes)
398 {
399 GinStatsData ginStats;
400
401 ginGetStats(scan->indexRelation, &ginStats);
402 if (ginStats.ginVersion < 1)
403 ereport(ERROR,
404 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
405 errmsg("old GIN indexes do not support whole-index scans nor searches for nulls"),
406 errhint("To fix this, do REINDEX INDEX \"%s\".",
407 RelationGetRelationName(scan->indexRelation))));
408 }
409
410 MemoryContextSwitchTo(oldCtx);
411
412 pgstat_count_index_scan(scan->indexRelation);
413 }
414
415 void
ginrescan(IndexScanDesc scan,ScanKey scankey,int nscankeys,ScanKey orderbys,int norderbys)416 ginrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
417 ScanKey orderbys, int norderbys)
418 {
419 GinScanOpaque so = (GinScanOpaque) scan->opaque;
420
421 ginFreeScanKeys(so);
422
423 if (scankey && scan->numberOfKeys > 0)
424 {
425 memmove(scan->keyData, scankey,
426 scan->numberOfKeys * sizeof(ScanKeyData));
427 }
428 }
429
430
431 void
ginendscan(IndexScanDesc scan)432 ginendscan(IndexScanDesc scan)
433 {
434 GinScanOpaque so = (GinScanOpaque) scan->opaque;
435
436 ginFreeScanKeys(so);
437
438 MemoryContextDelete(so->tempCtx);
439 MemoryContextDelete(so->keyCtx);
440
441 pfree(so);
442 }
443