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