diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/access/gin/README | 180 | ||||
-rw-r--r-- | src/backend/access/gin/ginarrayproc.c | 168 | ||||
-rw-r--r-- | src/backend/access/gin/ginbtree.c | 6 | ||||
-rw-r--r-- | src/backend/access/gin/ginbulk.c | 134 | ||||
-rw-r--r-- | src/backend/access/gin/gindatapage.c | 53 | ||||
-rw-r--r-- | src/backend/access/gin/ginentrypage.c | 329 | ||||
-rw-r--r-- | src/backend/access/gin/ginfast.c | 181 | ||||
-rw-r--r-- | src/backend/access/gin/ginget.c | 858 | ||||
-rw-r--r-- | src/backend/access/gin/gininsert.c | 266 | ||||
-rw-r--r-- | src/backend/access/gin/ginscan.c | 283 | ||||
-rw-r--r-- | src/backend/access/gin/ginutil.c | 293 | ||||
-rw-r--r-- | src/backend/access/gin/ginvacuum.c | 21 | ||||
-rw-r--r-- | src/backend/access/gin/ginxlog.c | 21 | ||||
-rw-r--r-- | src/include/access/gin.h | 612 | ||||
-rw-r--r-- | src/include/access/gin_private.h | 710 | ||||
-rw-r--r-- | src/include/utils/builtins.h | 5 | ||||
-rw-r--r-- | src/test/regress/data/array.data | 3 | ||||
-rw-r--r-- | src/test/regress/expected/arrays.out | 276 | ||||
-rw-r--r-- | src/test/regress/expected/create_index.out | 438 | ||||
-rw-r--r-- | src/test/regress/sql/arrays.sql | 12 | ||||
-rw-r--r-- | src/test/regress/sql/create_index.sql | 54 |
21 files changed, 3135 insertions, 1768 deletions
diff --git a/src/backend/access/gin/README b/src/backend/access/gin/README index 0f634f83d17..67159d85294 100644 --- a/src/backend/access/gin/README +++ b/src/backend/access/gin/README @@ -15,23 +15,23 @@ is similar to GiST and differs from btree indices, which have predefined, comparison-based operations. An inverted index is an index structure storing a set of (key, posting list) -pairs, where 'posting list' is a set of documents in which the key occurs. +pairs, where 'posting list' is a set of heap rows in which the key occurs. (A text document would usually contain many keys.) The primary goal of Gin indices is support for highly scalable, full-text search in PostgreSQL. -Gin consists of a B-tree index constructed over entries (ET, entries tree), -where each entry is an element of the indexed value (element of array, lexeme -for tsvector) and where each tuple in a leaf page is either a pointer to a -B-tree over item pointers (PT, posting tree), or a list of item pointers -(PL, posting list) if the tuple is small enough. +A Gin index consists of a B-tree index constructed over key values, +where each key is an element of some indexed items (element of array, lexeme +for tsvector) and where each tuple in a leaf page contains either a pointer to +a B-tree over item pointers (posting tree), or a simple list of item pointers +(posting list) if the list is small enough. -Note: There is no delete operation for ET. The reason for this is that in -our experience, the set of distinct words in a large corpus changes very -rarely. This greatly simplifies the code and concurrency algorithms. +Note: There is no delete operation in the key (entry) tree. The reason for +this is that in our experience, the set of distinct words in a large corpus +changes very slowly. This greatly simplifies the code and concurrency +algorithms. -Gin comes with built-in support for one-dimensional arrays (eg. integer[], -text[]), but no support for NULL elements. The following operations are -available: +Core PostgreSQL includes built-in Gin support for one-dimensional arrays +(eg. integer[], text[]). The following operations are available: * contains: value_array @> query_array * overlaps: value_array && query_array @@ -71,7 +71,7 @@ limit). If a non-zero search limit is set, then the returned set is a subset of the whole result set, chosen at random. -"Soft" means that the actual number of returned results could slightly differ +"Soft" means that the actual number of returned results could differ from the specified limit, depending on the query and the quality of the system's random number generator. @@ -80,40 +80,156 @@ From experience, a value of 'gin_fuzzy_search_limit' in the thousands have no effect for queries returning a result set with less tuples than this number. -Limitations ------------ - - * No support for multicolumn indices - * Gin doesn't uses scan->kill_prior_tuple & scan->ignore_killed_tuples - * Gin searches entries only by equality matching. This may be improved in - future. - * Gin doesn't support full scans of indices. - * Gin doesn't index NULL values. +Index structure +--------------- -Open Items ----------- +The "items" that a GIN index indexes are composite values that contain +zero or more "keys". For example, an item might be an integer array, and +then the keys would be the individual integer values. The index actually +stores and searches for the key values, not the items per se. In the +pg_opclass entry for a GIN opclass, the opcintype is the data type of the +items, and the opckeytype is the data type of the keys. GIN is optimized +for cases where items contain many keys and the same key values appear +in many different items. + +A GIN index contains a metapage, a btree of key entries, and possibly +"posting tree" pages, which hold the overflow when a key entry acquires +too many heap tuple pointers to fit in a btree page. Additionally, if the +fast-update feature is enabled, there can be "list pages" holding "pending" +key entries that haven't yet been merged into the main btree. The list +pages have to be scanned linearly when doing a search, so the pending +entries should be merged into the main btree before there get to be too +many of them. The advantage of the pending list is that bulk insertion of +a few thousand entries can be much faster than retail insertion. (The win +comes mainly from not having to do multiple searches/insertions when the +same key appears in multiple new heap tuples.) + +Key entries are nominally of the same IndexEntry format as used in other +index types, but since a leaf key entry typically refers to multiple heap +tuples, there are significant differences. (See GinFormTuple, which works +by building a "normal" index tuple and then modifying it.) The points to +know are: + +* In a single-column index, a key tuple just contains the key datum, but +in a multi-column index, a key tuple contains the pair (column number, +key datum) where the column number is stored as an int2. This is needed +to support different key data types in different columns. This much of +the tuple is built by index_form_tuple according to the usual rules. +The column number (if present) can never be null, but the key datum can +be, in which case a null bitmap is present as usual. (As usual for index +tuples, the size of the null bitmap is fixed at INDEX_MAX_KEYS.) + +* If the key datum is null (ie, IndexTupleHasNulls() is true), then +just after the nominal index data (ie, at offset IndexInfoFindDataOffset +or IndexInfoFindDataOffset + sizeof(int2)) there is a byte indicating +the "category" of the null entry. These are the possible categories: + 1 = ordinary null key value extracted from an indexable item + 2 = placeholder for zero-key indexable item + 3 = placeholder for null indexable item +Placeholder null entries are inserted into the index because otherwise +there would be no index entry at all for an empty or null indexable item, +which would mean that full index scans couldn't be done and various corner +cases would give wrong answers. The different categories of null entries +are treated as distinct keys by the btree, but heap itempointers for the +same category of null entry are merged into one index entry just as happens +with ordinary key entries. + +* In a key entry at the btree leaf level, at the next SHORTALIGN boundary, +there is an array of zero or more ItemPointers, which store the heap tuple +TIDs for which the indexable items contain this key. This is called the +"posting list". The TIDs in a posting list must appear in sorted order. +If the list would be too big for the index tuple to fit on an index page, +the ItemPointers are pushed out to a separate posting page or pages, and +none appear in the key entry itself. The separate pages are called a +"posting tree"; they are organized as a btree of ItemPointer values. +Note that in either case, the ItemPointers associated with a key can +easily be read out in sorted order; this is relied on by the scan +algorithms. + +* The index tuple header fields of a leaf key entry are abused as follows: + +1) Posting list case: + +* ItemPointerGetBlockNumber(&itup->t_tid) contains the offset from index + tuple start to the posting list. + Access macros: GinGetPostingOffset(itup) / GinSetPostingOffset(itup,n) + +* ItemPointerGetOffsetNumber(&itup->t_tid) contains the number of elements + in the posting list (number of heap itempointers). + Access macros: GinGetNPosting(itup) / GinSetNPosting(itup,n) + +* If IndexTupleHasNulls(itup) is true, the null category byte can be + accessed/set with GinGetNullCategory(itup,gs) / GinSetNullCategory(itup,gs,c) + +* The posting list can be accessed with GinGetPosting(itup) + +2) Posting tree case: + +* ItemPointerGetBlockNumber(&itup->t_tid) contains the index block number + of the root of the posting tree. + Access macros: GinGetPostingTree(itup) / GinSetPostingTree(itup, blkno) + +* ItemPointerGetOffsetNumber(&itup->t_tid) contains the magic number + GIN_TREE_POSTING, which distinguishes this from the posting-list case + (it's large enough that that many heap itempointers couldn't possibly + fit on an index page). This value is inserted automatically by the + GinSetPostingTree macro. + +* If IndexTupleHasNulls(itup) is true, the null category byte can be + accessed/set with GinGetNullCategory(itup) / GinSetNullCategory(itup,c) + +* The posting list is not present and must not be accessed. + +Use the macro GinIsPostingTree(itup) to determine which case applies. + +In both cases, itup->t_info & INDEX_SIZE_MASK contains actual total size of +tuple, and the INDEX_VAR_MASK and INDEX_NULL_MASK bits have their normal +meanings as set by index_form_tuple. + +Index tuples in non-leaf levels of the btree contain the optional column +number, key datum, and null category byte as above. They do not contain +a posting list. ItemPointerGetBlockNumber(&itup->t_tid) is the downlink +to the next lower btree level, and ItemPointerGetOffsetNumber(&itup->t_tid) +is InvalidOffsetNumber. Use the access macros GinGetDownlink/GinSetDownlink +to get/set the downlink. + +Index entries that appear in "pending list" pages work a tad differently as +well. The optional column number, key datum, and null category byte are as +for other GIN index entries. However, there is always exactly one heap +itempointer associated with a pending entry, and it is stored in the t_tid +header field just as in non-GIN indexes. There is no posting list. +Furthermore, the code that searches the pending list assumes that all +entries for a given heap tuple appear consecutively in the pending list and +are sorted by the column-number-plus-key-datum. The GIN_LIST_FULLROW page +flag bit tells whether entries for a given heap tuple are spread across +multiple pending-list pages. If GIN_LIST_FULLROW is set, the page contains +all the entries for one or more heap tuples. If GIN_LIST_FULLROW is clear, +the page contains entries for only one heap tuple, *and* they are not all +the entries for that tuple. (Thus, a heap tuple whose entries do not all +fit on one pending-list page must have those pages to itself, even if this +results in wasting much of the space on the preceding page and the last +page for the tuple.) -We appreciate any comments, help and suggestions. +Limitations +----------- - * Teach optimizer/executor that GIN is intrinsically clustered. i.e., it - always returns ItemPointer in ascending order. - * Tweak gincostestimate. + * Gin doesn't use scan->kill_prior_tuple & scan->ignore_killed_tuples + * Gin searches entries only by equality matching, or simple range + matching using the "partial match" feature. TODO ---- Nearest future: - * Opclasses for all types (no programming, just many catalog changes). + * Opclasses for more types (no programming, just many catalog changes) Distant future: * Replace B-tree of entries to something like GiST - * Add multicolumn support - * Optimize insert operations (background index insertion) Authors ------- -All work was done by Teodor Sigaev (teodor@sigaev.ru) and Oleg Bartunov +Original work was done by Teodor Sigaev (teodor@sigaev.ru) and Oleg Bartunov (oleg@sai.msu.su). diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index 1837a0d5a10..2100c5fd0e4 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -14,7 +14,9 @@ #include "postgres.h" #include "access/gin.h" +#include "access/skey.h" #include "utils/array.h" +#include "utils/builtins.h" #include "utils/lsyscache.h" @@ -23,34 +25,23 @@ #define GinContainedStrategy 3 #define GinEqualStrategy 4 -#define ARRAYCHECK(x) do { \ - if ( ARR_HASNULL(x) ) \ - ereport(ERROR, \ - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), \ - errmsg("array must not contain null values"))); \ -} while(0) - /* - * Function used as extractValue and extractQuery both + * extractValue support function */ Datum ginarrayextract(PG_FUNCTION_ARGS) { - ArrayType *array; - int32 *nentries = (int32 *) PG_GETARG_POINTER(1); - Datum *entries = NULL; + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); + bool **nullFlags = (bool **) PG_GETARG_POINTER(2); int16 elmlen; bool elmbyval; char elmalign; - - /* - * we should guarantee that array will not be destroyed during all - * operation - */ - array = PG_GETARG_ARRAYTYPE_P_COPY(0); - - ARRAYCHECK(array); + Datum *elems; + bool *nulls; + int nelems; get_typlenbyvalalign(ARR_ELEMTYPE(array), &elmlen, &elmbyval, &elmalign); @@ -58,89 +49,140 @@ ginarrayextract(PG_FUNCTION_ARGS) deconstruct_array(array, ARR_ELEMTYPE(array), elmlen, elmbyval, elmalign, - &entries, NULL, (int *) nentries); + &elems, &nulls, &nelems); - if (*nentries == 0 && PG_NARGS() == 3) - { - switch (PG_GETARG_UINT16(2)) /* StrategyNumber */ - { - case GinOverlapStrategy: - *nentries = -1; /* nobody can be found */ - break; - case GinContainsStrategy: - case GinContainedStrategy: - case GinEqualStrategy: - default: /* require fullscan: GIN can't find void - * arrays */ - break; - } - } + *nkeys = nelems; + *nullFlags = nulls; - /* we should not free array, entries[i] points into it */ - PG_RETURN_POINTER(entries); + /* we should not free array, elems[i] points into it */ + PG_RETURN_POINTER(elems); } +/* + * extractQuery support function + */ Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - PG_RETURN_DATUM(DirectFunctionCall3(ginarrayextract, - PG_GETARG_DATUM(0), - PG_GETARG_DATUM(1), - PG_GETARG_DATUM(2))); + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = PG_GETARG_UINT16(2); + /* bool **pmatch = (bool **) PG_GETARG_POINTER(3); */ + /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ + bool **nullFlags = (bool **) PG_GETARG_POINTER(5); + int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); + int16 elmlen; + bool elmbyval; + char elmalign; + Datum *elems; + bool *nulls; + int nelems; + + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + + *nkeys = nelems; + *nullFlags = nulls; + + switch (strategy) + { + case GinOverlapStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; + case GinContainsStrategy: + if (nelems > 0) + *searchMode = GIN_SEARCH_MODE_DEFAULT; + else /* everything contains the empty set */ + *searchMode = GIN_SEARCH_MODE_ALL; + break; + case GinContainedStrategy: + /* empty set is contained in everything */ + *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; + break; + case GinEqualStrategy: + if (nelems > 0) + *searchMode = GIN_SEARCH_MODE_DEFAULT; + else + *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; + break; + default: + elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", + strategy); + } + + /* we should not free array, elems[i] points into it */ + PG_RETURN_POINTER(elems); } +/* + * consistent support function + */ Datum ginarrayconsistent(PG_FUNCTION_ARGS) { bool *check = (bool *) PG_GETARG_POINTER(0); StrategyNumber strategy = PG_GETARG_UINT16(1); - ArrayType *query = PG_GETARG_ARRAYTYPE_P(2); - - /* int32 nkeys = PG_GETARG_INT32(3); */ + /* ArrayType *query = PG_GETARG_ARRAYTYPE_P(2); */ + int32 nkeys = PG_GETARG_INT32(3); /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool *recheck = (bool *) PG_GETARG_POINTER(5); + /* Datum *queryKeys = (Datum *) PG_GETARG_POINTER(6); */ + bool *nullFlags = (bool *) PG_GETARG_POINTER(7); bool res; - int i, - nentries; - - /* ARRAYCHECK was already done by previous ginarrayextract call */ + int32 i; switch (strategy) { case GinOverlapStrategy: /* result is not lossy */ *recheck = false; - /* at least one element in check[] is true, so result = true */ - res = true; - break; - case GinContainedStrategy: - /* we will need recheck */ - *recheck = true; - /* at least one element in check[] is true, so result = true */ - res = true; + /* must have a match for at least one non-null element */ + res = false; + for (i = 0; i < nkeys; i++) + { + if (check[i] && !nullFlags[i]) + { + res = true; + break; + } + } break; case GinContainsStrategy: /* result is not lossy */ *recheck = false; - /* must have all elements in check[] true */ - nentries = ArrayGetNItems(ARR_NDIM(query), ARR_DIMS(query)); + /* must have all elements in check[] true, and no nulls */ res = true; - for (i = 0; i < nentries; i++) + for (i = 0; i < nkeys; i++) { - if (!check[i]) + if (!check[i] || nullFlags[i]) { res = false; break; } } break; + case GinContainedStrategy: + /* we will need recheck */ + *recheck = true; + /* can't do anything else useful here */ + res = true; + break; case GinEqualStrategy: /* we will need recheck */ *recheck = true; - /* must have all elements in check[] true */ - nentries = ArrayGetNItems(ARR_NDIM(query), ARR_DIMS(query)); + /* + * Must have all elements in check[] true; no discrimination + * against nulls here. This is because array_contain_compare + * and array_eq handle nulls differently ... + */ res = true; - for (i = 0; i < nentries; i++) + for (i = 0; i < nkeys; i++) { if (!check[i]) { diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c index ec038aac4b0..739fa8afff7 100644 --- a/src/backend/access/gin/ginbtree.c +++ b/src/backend/access/gin/ginbtree.c @@ -14,7 +14,7 @@ #include "postgres.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "miscadmin.h" #include "storage/bufmgr.h" #include "utils/rel.h" @@ -104,7 +104,8 @@ ginFindLeafPage(GinBtree btree, GinBtreeStack *stack) * ok, page is correctly locked, we should check to move right .., * root never has a right link, so small optimization */ - while (btree->fullScan == FALSE && stack->blkno != rootBlkno && btree->isMoveRight(btree, page)) + while (btree->fullScan == FALSE && stack->blkno != rootBlkno && + btree->isMoveRight(btree, page)) { BlockNumber rightlink = GinPageGetOpaque(page)->rightlink; @@ -226,7 +227,6 @@ ginFindParents(GinBtree btree, GinBtreeStack *stack, LockBuffer(root->buffer, GIN_UNLOCK); Assert(blkno != InvalidBlockNumber); - for (;;) { buffer = ReadBuffer(btree->index, blkno); diff --git a/src/backend/access/gin/ginbulk.c b/src/backend/access/gin/ginbulk.c index a4ee3364e10..f0c8c8e37f6 100644 --- a/src/backend/access/gin/ginbulk.c +++ b/src/backend/access/gin/ginbulk.c @@ -14,12 +14,12 @@ #include "postgres.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "utils/datum.h" #include "utils/memutils.h" -#define DEF_NENTRY 2048 /* EntryAccumulator allocation quantum */ +#define DEF_NENTRY 2048 /* GinEntryAccumulator allocation quantum */ #define DEF_NPTR 5 /* ItemPointer initial allocation quantum */ @@ -27,48 +27,49 @@ static void ginCombineData(RBNode *existing, const RBNode *newdata, void *arg) { - EntryAccumulator *eo = (EntryAccumulator *) existing; - const EntryAccumulator *en = (const EntryAccumulator *) newdata; + GinEntryAccumulator *eo = (GinEntryAccumulator *) existing; + const GinEntryAccumulator *en = (const GinEntryAccumulator *) newdata; BuildAccumulator *accum = (BuildAccumulator *) arg; /* * Note this code assumes that newdata contains only one itempointer. */ - if (eo->number >= eo->length) + if (eo->count >= eo->maxcount) { accum->allocatedMemory -= GetMemoryChunkSpace(eo->list); - eo->length *= 2; - eo->list = (ItemPointerData *) repalloc(eo->list, - sizeof(ItemPointerData) * eo->length); + eo->maxcount *= 2; + eo->list = (ItemPointerData *) + repalloc(eo->list, sizeof(ItemPointerData) * eo->maxcount); accum->allocatedMemory += GetMemoryChunkSpace(eo->list); } - /* If item pointers are not ordered, they will need to be sorted. */ + /* If item pointers are not ordered, they will need to be sorted later */ if (eo->shouldSort == FALSE) { int res; - res = ginCompareItemPointers(eo->list + eo->number - 1, en->list); + res = ginCompareItemPointers(eo->list + eo->count - 1, en->list); Assert(res != 0); if (res > 0) eo->shouldSort = TRUE; } - eo->list[eo->number] = en->list[0]; - eo->number++; + eo->list[eo->count] = en->list[0]; + eo->count++; } /* Comparator function for rbtree.c */ static int cmpEntryAccumulator(const RBNode *a, const RBNode *b, void *arg) { - const EntryAccumulator *ea = (const EntryAccumulator *) a; - const EntryAccumulator *eb = (const EntryAccumulator *) b; + const GinEntryAccumulator *ea = (const GinEntryAccumulator *) a; + const GinEntryAccumulator *eb = (const GinEntryAccumulator *) b; BuildAccumulator *accum = (BuildAccumulator *) arg; - return ginCompareAttEntries(accum->ginstate, ea->attnum, ea->value, - eb->attnum, eb->value); + return ginCompareAttEntries(accum->ginstate, + ea->attnum, ea->key, ea->category, + eb->attnum, eb->key, eb->category); } /* Allocator function for rbtree.c */ @@ -76,22 +77,22 @@ static RBNode * ginAllocEntryAccumulator(void *arg) { BuildAccumulator *accum = (BuildAccumulator *) arg; - EntryAccumulator *ea; + GinEntryAccumulator *ea; /* * Allocate memory by rather big chunks to decrease overhead. We have * no need to reclaim RBNodes individually, so this costs nothing. */ - if (accum->entryallocator == NULL || accum->length >= DEF_NENTRY) + if (accum->entryallocator == NULL || accum->eas_used >= DEF_NENTRY) { - accum->entryallocator = palloc(sizeof(EntryAccumulator) * DEF_NENTRY); + accum->entryallocator = palloc(sizeof(GinEntryAccumulator) * DEF_NENTRY); accum->allocatedMemory += GetMemoryChunkSpace(accum->entryallocator); - accum->length = 0; + accum->eas_used = 0; } /* Allocate new RBNode from current chunk */ - ea = accum->entryallocator + accum->length; - accum->length++; + ea = accum->entryallocator + accum->eas_used; + accum->eas_used++; return (RBNode *) ea; } @@ -99,10 +100,11 @@ ginAllocEntryAccumulator(void *arg) void ginInitBA(BuildAccumulator *accum) { + /* accum->ginstate is intentionally not set here */ accum->allocatedMemory = 0; - accum->length = 0; accum->entryallocator = NULL; - accum->tree = rb_create(sizeof(EntryAccumulator), + accum->eas_used = 0; + accum->tree = rb_create(sizeof(GinEntryAccumulator), cmpEntryAccumulator, ginCombineData, ginAllocEntryAccumulator, @@ -111,8 +113,8 @@ ginInitBA(BuildAccumulator *accum) } /* - * This is basically the same as datumCopy(), but modified to count - * palloc'd space in accum. + * This is basically the same as datumCopy(), but extended to count + * palloc'd space in accum->allocatedMemory. */ static Datum getDatumCopy(BuildAccumulator *accum, OffsetNumber attnum, Datum value) @@ -134,32 +136,37 @@ getDatumCopy(BuildAccumulator *accum, OffsetNumber attnum, Datum value) * Find/store one entry from indexed value. */ static void -ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum, Datum entry) +ginInsertBAEntry(BuildAccumulator *accum, + ItemPointer heapptr, OffsetNumber attnum, + Datum key, GinNullCategory category) { - EntryAccumulator key; - EntryAccumulator *ea; + GinEntryAccumulator eatmp; + GinEntryAccumulator *ea; bool isNew; /* - * For the moment, fill only the fields of key that will be looked at + * For the moment, fill only the fields of eatmp that will be looked at * by cmpEntryAccumulator or ginCombineData. */ - key.attnum = attnum; - key.value = entry; + eatmp.attnum = attnum; + eatmp.key = key; + eatmp.category = category; /* temporarily set up single-entry itempointer list */ - key.list = heapptr; + eatmp.list = heapptr; - ea = (EntryAccumulator *) rb_insert(accum->tree, (RBNode *) &key, &isNew); + ea = (GinEntryAccumulator *) rb_insert(accum->tree, (RBNode *) &eatmp, + &isNew); if (isNew) { /* * Finish initializing new tree entry, including making permanent - * copies of the datum and itempointer. + * copies of the datum (if it's not null) and itempointer. */ - ea->value = getDatumCopy(accum, attnum, entry); - ea->length = DEF_NPTR; - ea->number = 1; + if (category == GIN_CAT_NORM_KEY) + ea->key = getDatumCopy(accum, attnum, key); + ea->maxcount = DEF_NPTR; + ea->count = 1; ea->shouldSort = FALSE; ea->list = (ItemPointerData *) palloc(sizeof(ItemPointerData) * DEF_NPTR); @@ -175,7 +182,7 @@ ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum } /* - * Insert one heap pointer. + * Insert the entries for one heap pointer. * * Since the entries are being inserted into a balanced binary tree, you * might think that the order of insertion wouldn't be critical, but it turns @@ -187,22 +194,24 @@ ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum * We do this as follows. First, we imagine that we have an array whose size * is the smallest power of two greater than or equal to the actual array * size. Second, we insert the middle entry of our virtual array into the - * tree; then, we insert the middles of each half of out virtual array, then + * tree; then, we insert the middles of each half of our virtual array, then * middles of quarters, etc. */ void -ginInsertRecordBA(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum, - Datum *entries, int32 nentry) +ginInsertBAEntries(BuildAccumulator *accum, + ItemPointer heapptr, OffsetNumber attnum, + Datum *entries, GinNullCategory *categories, + int32 nentries) { - uint32 step = nentry; + uint32 step = nentries; - if (nentry <= 0) + if (nentries <= 0) return; Assert(ItemPointerIsValid(heapptr) && attnum >= FirstOffsetNumber); /* - * step will contain largest power of 2 and <= nentry + * step will contain largest power of 2 and <= nentries */ step |= (step >> 1); step |= (step >> 2); @@ -216,8 +225,9 @@ ginInsertRecordBA(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber att { int i; - for (i = step - 1; i < nentry && i >= 0; i += step << 1 /* *2 */ ) - ginInsertEntry(accum, heapptr, attnum, entries[i]); + for (i = step - 1; i < nentries && i >= 0; i += step << 1 /* *2 */ ) + ginInsertBAEntry(accum, heapptr, attnum, + entries[i], categories[i]); step >>= 1; /* /2 */ } @@ -228,37 +238,47 @@ qsortCompareItemPointers(const void *a, const void *b) { int res = ginCompareItemPointers((ItemPointer) a, (ItemPointer) b); + /* Assert that there are no equal item pointers being sorted */ Assert(res != 0); return res; } -/* Prepare to read out the rbtree contents using ginGetEntry */ +/* Prepare to read out the rbtree contents using ginGetBAEntry */ void ginBeginBAScan(BuildAccumulator *accum) { rb_begin_iterate(accum->tree, LeftRightWalk); } +/* + * Get the next entry in sequence from the BuildAccumulator's rbtree. + * This consists of a single key datum and a list (array) of one or more + * heap TIDs in which that key is found. The list is guaranteed sorted. + */ ItemPointerData * -ginGetEntry(BuildAccumulator *accum, OffsetNumber *attnum, Datum *value, uint32 *n) +ginGetBAEntry(BuildAccumulator *accum, + OffsetNumber *attnum, Datum *key, GinNullCategory *category, + uint32 *n) { - EntryAccumulator *entry; + GinEntryAccumulator *entry; ItemPointerData *list; - entry = (EntryAccumulator *) rb_iterate(accum->tree); + entry = (GinEntryAccumulator *) rb_iterate(accum->tree); if (entry == NULL) - return NULL; + return NULL; /* no more entries */ - *n = entry->number; *attnum = entry->attnum; - *value = entry->value; + *key = entry->key; + *category = entry->category; list = entry->list; + *n = entry->count; - Assert(list != NULL); + Assert(list != NULL && entry->count > 0); - if (entry->shouldSort && entry->number > 1) - qsort(list, *n, sizeof(ItemPointerData), qsortCompareItemPointers); + if (entry->shouldSort && entry->count > 1) + qsort(list, entry->count, sizeof(ItemPointerData), + qsortCompareItemPointers); return list; } diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c index f6e86da8db8..4a1e7548008 100644 --- a/src/backend/access/gin/gindatapage.c +++ b/src/backend/access/gin/gindatapage.c @@ -14,21 +14,27 @@ #include "postgres.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "storage/bufmgr.h" #include "utils/rel.h" int ginCompareItemPointers(ItemPointer a, ItemPointer b) { - if (GinItemPointerGetBlockNumber(a) == GinItemPointerGetBlockNumber(b)) + BlockNumber ba = GinItemPointerGetBlockNumber(a); + BlockNumber bb = GinItemPointerGetBlockNumber(b); + + if (ba == bb) { - if (GinItemPointerGetOffsetNumber(a) == GinItemPointerGetOffsetNumber(b)) + OffsetNumber oa = GinItemPointerGetOffsetNumber(a); + OffsetNumber ob = GinItemPointerGetOffsetNumber(b); + + if (oa == ob) return 0; - return (GinItemPointerGetOffsetNumber(a) > GinItemPointerGetOffsetNumber(b)) ? 1 : -1; + return (oa > ob) ? 1 : -1; } - return (GinItemPointerGetBlockNumber(a) > GinItemPointerGetBlockNumber(b)) ? 1 : -1; + return (ba > bb) ? 1 : -1; } /* @@ -122,12 +128,13 @@ dataLocateItem(GinBtree btree, GinBtreeStack *stack) pitem = (PostingItem *) GinDataPageGetItem(page, mid); if (mid == maxoff) - + { /* * Right infinity, page already correctly chosen with a help of * dataIsMoveRight */ result = -1; + } else { pitem = (PostingItem *) GinDataPageGetItem(page, mid); @@ -220,7 +227,7 @@ dataFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber stor Assert(!GinPageIsLeaf(page)); Assert(GinPageIsData(page)); - /* if page isn't changed, we returns storedOff */ + /* if page isn't changed, we return storedOff */ if (storedOff >= FirstOffsetNumber && storedOff <= maxoff) { pitem = (PostingItem *) GinDataPageGetItem(page, storedOff); @@ -286,9 +293,11 @@ GinDataPageAddItem(Page page, void *data, OffsetNumber offset) { ptr = GinDataPageGetItem(page, offset); if (maxoff + 1 - offset != 0) - memmove(ptr + GinSizeOfItem(page), ptr, (maxoff - offset + 1) * GinSizeOfItem(page)); + memmove(ptr + GinSizeOfDataPageItem(page), + ptr, + (maxoff - offset + 1) * GinSizeOfDataPageItem(page)); } - memcpy(ptr, data, GinSizeOfItem(page)); + memcpy(ptr, data, GinSizeOfDataPageItem(page)); GinPageGetOpaque(page)->maxoff++; } @@ -372,10 +381,11 @@ static void dataPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off, XLogRecData **prdata) { Page page = BufferGetPage(buf); + int sizeofitem = GinSizeOfDataPageItem(page); + int cnt = 0; + /* these must be static so they can be returned to caller */ static XLogRecData rdata[3]; - int sizeofitem = GinSizeOfItem(page); static ginxlogInsert data; - int cnt = 0; *prdata = rdata; Assert(GinPageIsData(page)); @@ -453,20 +463,21 @@ dataPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off, XLogRecData **prda static Page dataSplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off, XLogRecData **prdata) { - static ginxlogSplit data; - static XLogRecData rdata[4]; - static char vector[2 * BLCKSZ]; char *ptr; OffsetNumber separator; ItemPointer bound; Page lpage = PageGetTempPageCopy(BufferGetPage(lbuf)); ItemPointerData oldbound = *GinDataPageGetRightBound(lpage); - int sizeofitem = GinSizeOfItem(lpage); + int sizeofitem = GinSizeOfDataPageItem(lpage); OffsetNumber maxoff = GinPageGetOpaque(lpage)->maxoff; Page rpage = BufferGetPage(rbuf); Size pageSize = PageGetPageSize(lpage); Size freeSpace; uint32 nCopied = 1; + /* these must be static so they can be returned to caller */ + static ginxlogSplit data; + static XLogRecData rdata[4]; + static char vector[2 * BLCKSZ]; GinInitPage(rpage, GinPageGetOpaque(lpage)->flags, pageSize); freeSpace = GinDataPageGetFreeSpace(rpage); @@ -482,9 +493,11 @@ dataSplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off, XLogRe if (GinPageIsLeaf(lpage) && GinPageRightMost(lpage) && off > GinPageGetOpaque(lpage)->maxoff) { nCopied = 0; - while (btree->curitem < btree->nitem && maxoff * sizeof(ItemPointerData) < 2 * (freeSpace - sizeof(ItemPointerData))) + while (btree->curitem < btree->nitem && + maxoff * sizeof(ItemPointerData) < 2 * (freeSpace - sizeof(ItemPointerData))) { - memcpy(vector + maxoff * sizeof(ItemPointerData), btree->items + btree->curitem, + memcpy(vector + maxoff * sizeof(ItemPointerData), + btree->items + btree->curitem, sizeof(ItemPointerData)); maxoff++; nCopied++; @@ -631,9 +644,9 @@ ginPrepareScanPostingTree(Relation index, BlockNumber rootBlkno, bool searchMode * Inserts array of item pointers, may execute several tree scan (very rare) */ void -ginInsertItemPointer(GinPostingTreeScan *gdi, - ItemPointerData *items, uint32 nitem, - GinStatsData *buildStats) +ginInsertItemPointers(GinPostingTreeScan *gdi, + ItemPointerData *items, uint32 nitem, + GinStatsData *buildStats) { BlockNumber rootBlkno = gdi->stack->blkno; diff --git a/src/backend/access/gin/ginentrypage.c b/src/backend/access/gin/ginentrypage.c index e56034202ad..9749a1be786 100644 --- a/src/backend/access/gin/ginentrypage.c +++ b/src/backend/access/gin/ginentrypage.c @@ -14,7 +14,7 @@ #include "postgres.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "storage/bufmgr.h" #include "utils/rel.h" @@ -24,107 +24,116 @@ * If the tuple would be too big to be stored, function throws a suitable * error if errorTooBig is TRUE, or returns NULL if errorTooBig is FALSE. * - * On leaf pages, Index tuple has non-traditional layout. Tuple may contain - * posting list or root blocknumber of posting tree. - * Macros: GinIsPostingTree(itup) / GinSetPostingTree(itup, blkno) - * 1) Posting list - * - itup->t_info & INDEX_SIZE_MASK contains total size of tuple as usual - * - ItemPointerGetBlockNumber(&itup->t_tid) contains original - * size of tuple (without posting list). - * Macros: GinGetOrigSizePosting(itup) / GinSetOrigSizePosting(itup,n) - * - ItemPointerGetOffsetNumber(&itup->t_tid) contains number - * of elements in posting list (number of heap itempointers) - * Macros: GinGetNPosting(itup) / GinSetNPosting(itup,n) - * - After standard part of tuple there is a posting list, ie, array - * of heap itempointers - * Macros: GinGetPosting(itup) - * 2) Posting tree - * - itup->t_info & INDEX_SIZE_MASK contains size of tuple as usual - * - ItemPointerGetBlockNumber(&itup->t_tid) contains block number of - * root of posting tree - * - ItemPointerGetOffsetNumber(&itup->t_tid) contains magic number - * GIN_TREE_POSTING, which distinguishes this from posting-list case - * - * Attributes of an index tuple are different for single and multicolumn index. - * For single-column case, index tuple stores only value to be indexed. - * For multicolumn case, it stores two attributes: column number of value - * and value. + * See src/backend/access/gin/README for a description of the index tuple + * format that is being built here. We build on the assumption that we + * are making a leaf-level key entry containing a posting list of nipd items. + * If the caller is actually trying to make a posting-tree entry, non-leaf + * entry, or pending-list entry, it should pass nipd = 0 and then overwrite + * the t_tid fields as necessary. In any case, ipd can be NULL to skip + * copying any itempointers into the posting list; the caller is responsible + * for filling the posting list afterwards, if ipd = NULL and nipd > 0. */ IndexTuple -GinFormTuple(Relation index, GinState *ginstate, - OffsetNumber attnum, Datum key, - ItemPointerData *ipd, uint32 nipd, bool errorTooBig) +GinFormTuple(GinState *ginstate, + OffsetNumber attnum, Datum key, GinNullCategory category, + ItemPointerData *ipd, uint32 nipd, + bool errorTooBig) { - bool isnull[2] = {FALSE, FALSE}; + Datum datums[2]; + bool isnull[2]; IndexTuple itup; uint32 newsize; + /* Build the basic tuple: optional column number, plus key datum */ if (ginstate->oneCol) - itup = index_form_tuple(ginstate->origTupdesc, &key, isnull); + { + datums[0] = key; + isnull[0] = (category != GIN_CAT_NORM_KEY); + } else { - Datum datums[2]; - datums[0] = UInt16GetDatum(attnum); + isnull[0] = false; datums[1] = key; - itup = index_form_tuple(ginstate->tupdesc[attnum - 1], datums, isnull); + isnull[1] = (category != GIN_CAT_NORM_KEY); } - GinSetOrigSizePosting(itup, IndexTupleSize(itup)); + itup = index_form_tuple(ginstate->tupdesc[attnum - 1], datums, isnull); + + /* + * Determine and store offset to the posting list, making sure there is + * room for the category byte if needed. + * + * Note: because index_form_tuple MAXALIGNs the tuple size, there may well + * be some wasted pad space. Is it worth recomputing the data length to + * prevent that? That would also allow us to Assert that the real data + * doesn't overlap the GinNullCategory byte, which this code currently + * takes on faith. + */ + newsize = IndexTupleSize(itup); - if (nipd > 0) + if (IndexTupleHasNulls(itup)) { - newsize = MAXALIGN(SHORTALIGN(IndexTupleSize(itup)) + sizeof(ItemPointerData) * nipd); - if (newsize > Min(INDEX_SIZE_MASK, GinMaxItemSize)) - { - if (errorTooBig) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("index row size %lu exceeds maximum %lu for index \"%s\"", - (unsigned long) newsize, - (unsigned long) Min(INDEX_SIZE_MASK, - GinMaxItemSize), - RelationGetRelationName(index)))); - return NULL; - } + uint32 minsize; + + Assert(category != GIN_CAT_NORM_KEY); + minsize = GinCategoryOffset(itup, ginstate) + sizeof(GinNullCategory); + newsize = Max(newsize, minsize); + } + newsize = SHORTALIGN(newsize); + + GinSetPostingOffset(itup, newsize); + + GinSetNPosting(itup, nipd); + + /* + * Add space needed for posting list, if any. Then check that the tuple + * won't be too big to store. + */ + newsize += sizeof(ItemPointerData) * nipd; + newsize = MAXALIGN(newsize); + if (newsize > Min(INDEX_SIZE_MASK, GinMaxItemSize)) + { + if (errorTooBig) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("index row size %lu exceeds maximum %lu for index \"%s\"", + (unsigned long) newsize, + (unsigned long) Min(INDEX_SIZE_MASK, + GinMaxItemSize), + RelationGetRelationName(ginstate->index)))); + pfree(itup); + return NULL; + } + + /* + * Resize tuple if needed + */ + if (newsize != IndexTupleSize(itup)) + { itup = repalloc(itup, newsize); - /* set new size */ + /* set new size in tuple header */ itup->t_info &= ~INDEX_SIZE_MASK; itup->t_info |= newsize; - - if (ipd) - memcpy(GinGetPosting(itup), ipd, sizeof(ItemPointerData) * nipd); - GinSetNPosting(itup, nipd); } - else - { - /* - * Gin tuple without any ItemPointers should be large enough to keep - * one ItemPointer, to prevent inconsistency between - * ginHeapTupleFastCollect and ginEntryInsert called by - * ginHeapTupleInsert. ginHeapTupleFastCollect forms tuple without - * extra pointer to heap, but ginEntryInsert (called for pending list - * cleanup during vacuum) will form the same tuple with one - * ItemPointer. - */ - newsize = MAXALIGN(SHORTALIGN(IndexTupleSize(itup)) + sizeof(ItemPointerData)); - if (newsize > Min(INDEX_SIZE_MASK, GinMaxItemSize)) - { - if (errorTooBig) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("index row size %lu exceeds maximum %lu for index \"%s\"", - (unsigned long) newsize, - (unsigned long) Min(INDEX_SIZE_MASK, - GinMaxItemSize), - RelationGetRelationName(index)))); - return NULL; - } - GinSetNPosting(itup, 0); + /* + * Insert category byte, if needed + */ + if (category != GIN_CAT_NORM_KEY) + { + Assert(IndexTupleHasNulls(itup)); + GinSetNullCategory(itup, ginstate, category); } + + /* + * Copy in the posting list, if provided + */ + if (ipd) + memcpy(GinGetPosting(itup), ipd, sizeof(ItemPointerData) * nipd); + return itup; } @@ -140,7 +149,8 @@ GinShortenTuple(IndexTuple itup, uint32 nipd) Assert(nipd <= GinGetNPosting(itup)); - newsize = MAXALIGN(SHORTALIGN(GinGetOrigSizePosting(itup)) + sizeof(ItemPointerData) * nipd); + newsize = GinGetPostingOffset(itup) + sizeof(ItemPointerData) * nipd; + newsize = MAXALIGN(newsize); Assert(newsize <= (itup->t_info & INDEX_SIZE_MASK)); @@ -151,8 +161,45 @@ GinShortenTuple(IndexTuple itup, uint32 nipd) } /* + * Form a non-leaf entry tuple by copying the key data from the given tuple, + * which can be either a leaf or non-leaf entry tuple. + * + * Any posting list in the source tuple is not copied. The specified child + * block number is inserted into t_tid. + */ +static IndexTuple +GinFormInteriorTuple(IndexTuple itup, Page page, BlockNumber childblk) +{ + IndexTuple nitup; + + if (GinPageIsLeaf(page) && !GinIsPostingTree(itup)) + { + /* Tuple contains a posting list, just copy stuff before that */ + uint32 origsize = GinGetPostingOffset(itup); + + origsize = MAXALIGN(origsize); + nitup = (IndexTuple) palloc(origsize); + memcpy(nitup, itup, origsize); + /* ... be sure to fix the size header field ... */ + nitup->t_info &= ~INDEX_SIZE_MASK; + nitup->t_info |= origsize; + } + else + { + /* Copy the tuple as-is */ + nitup = (IndexTuple) palloc(IndexTupleSize(itup)); + memcpy(nitup, itup, IndexTupleSize(itup)); + } + + /* Now insert the correct downlink */ + GinSetDownlink(nitup, childblk); + + return nitup; +} + +/* * Entry tree is a "static", ie tuple never deletes from it, - * so we don't use right bound, we use rightest key instead. + * so we don't use right bound, we use rightmost key instead. */ static IndexTuple getRightMostTuple(Page page) @@ -166,16 +213,20 @@ static bool entryIsMoveRight(GinBtree btree, Page page) { IndexTuple itup; + OffsetNumber attnum; + Datum key; + GinNullCategory category; if (GinPageRightMost(page)) return FALSE; itup = getRightMostTuple(page); + attnum = gintuple_get_attrnum(btree->ginstate, itup); + key = gintuple_get_key(btree->ginstate, itup, &category); if (ginCompareAttEntries(btree->ginstate, - btree->entryAttnum, btree->entryValue, - gintuple_get_attrnum(btree->ginstate, itup), - gin_index_getattr(btree->ginstate, itup)) > 0) + btree->entryAttnum, btree->entryKey, btree->entryCategory, + attnum, key, category) > 0) return TRUE; return FALSE; @@ -183,7 +234,7 @@ entryIsMoveRight(GinBtree btree, Page page) /* * Find correct tuple in non-leaf page. It supposed that - * page correctly choosen and searching value SHOULD be on page + * page correctly chosen and searching value SHOULD be on page */ static BlockNumber entryLocateEntry(GinBtree btree, GinBtreeStack *stack) @@ -216,23 +267,31 @@ entryLocateEntry(GinBtree btree, GinBtreeStack *stack) OffsetNumber mid = low + ((high - low) / 2); if (mid == maxoff && GinPageRightMost(page)) + { /* Right infinity */ result = -1; + } else { + OffsetNumber attnum; + Datum key; + GinNullCategory category; + itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid)); + attnum = gintuple_get_attrnum(btree->ginstate, itup); + key = gintuple_get_key(btree->ginstate, itup, &category); result = ginCompareAttEntries(btree->ginstate, btree->entryAttnum, - btree->entryValue, - gintuple_get_attrnum(btree->ginstate, itup), - gin_index_getattr(btree->ginstate, itup)); + btree->entryKey, + btree->entryCategory, + attnum, key, category); } if (result == 0) { stack->off = mid; - Assert(GinItemPointerGetBlockNumber(&(itup)->t_tid) != GIN_ROOT_BLKNO); - return GinItemPointerGetBlockNumber(&(itup)->t_tid); + Assert(GinGetDownlink(itup) != GIN_ROOT_BLKNO); + return GinGetDownlink(itup); } else if (result > 0) low = mid + 1; @@ -244,13 +303,13 @@ entryLocateEntry(GinBtree btree, GinBtreeStack *stack) stack->off = high; itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, high)); - Assert(GinItemPointerGetBlockNumber(&(itup)->t_tid) != GIN_ROOT_BLKNO); - return GinItemPointerGetBlockNumber(&(itup)->t_tid); + Assert(GinGetDownlink(itup) != GIN_ROOT_BLKNO); + return GinGetDownlink(itup); } /* * Searches correct position for value on leaf page. - * Page should be corrrectly choosen. + * Page should be correctly chosen. * Returns true if value found on page. */ static bool @@ -259,7 +318,6 @@ entryLocateLeafEntry(GinBtree btree, GinBtreeStack *stack) Page page = BufferGetPage(stack->buffer); OffsetNumber low, high; - IndexTuple itup; Assert(GinPageIsLeaf(page)); Assert(!GinPageIsData(page)); @@ -284,14 +342,20 @@ entryLocateLeafEntry(GinBtree btree, GinBtreeStack *stack) while (high > low) { OffsetNumber mid = low + ((high - low) / 2); + IndexTuple itup; + OffsetNumber attnum; + Datum key; + GinNullCategory category; int result; itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid)); + attnum = gintuple_get_attrnum(btree->ginstate, itup); + key = gintuple_get_key(btree->ginstate, itup, &category); result = ginCompareAttEntries(btree->ginstate, btree->entryAttnum, - btree->entryValue, - gintuple_get_attrnum(btree->ginstate, itup), - gin_index_getattr(btree->ginstate, itup)); + btree->entryKey, + btree->entryCategory, + attnum, key, category); if (result == 0) { stack->off = mid; @@ -321,7 +385,7 @@ entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber sto if (storedOff >= FirstOffsetNumber && storedOff <= maxoff) { itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, storedOff)); - if (GinItemPointerGetBlockNumber(&(itup)->t_tid) == blkno) + if (GinGetDownlink(itup) == blkno) return storedOff; /* @@ -331,7 +395,7 @@ entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber sto for (i = storedOff + 1; i <= maxoff; i++) { itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); - if (GinItemPointerGetBlockNumber(&(itup)->t_tid) == blkno) + if (GinGetDownlink(itup) == blkno) return i; } maxoff = storedOff - 1; @@ -341,7 +405,7 @@ entryFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber sto for (i = FirstOffsetNumber; i <= maxoff; i++) { itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); - if (GinItemPointerGetBlockNumber(&(itup)->t_tid) == blkno) + if (GinGetDownlink(itup) == blkno) return i; } @@ -358,7 +422,7 @@ entryGetLeftMostPage(GinBtree btree, Page page) Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber); itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber)); - return GinItemPointerGetBlockNumber(&(itup)->t_tid); + return GinGetDownlink(itup); } static bool @@ -406,7 +470,7 @@ entryPreparePage(GinBtree btree, Page page, OffsetNumber off) { IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); - ItemPointerSet(&itup->t_tid, btree->rightblkno, InvalidOffsetNumber); + GinSetDownlink(itup, btree->rightblkno); ret = btree->rightblkno; } @@ -422,10 +486,11 @@ static void entryPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off, XLogRecData **prdata) { Page page = BufferGetPage(buf); - static XLogRecData rdata[3]; OffsetNumber placed; - static ginxlogInsert data; int cnt = 0; + /* these must be static so they can be returned to caller */ + static XLogRecData rdata[3]; + static ginxlogInsert data; *prdata = rdata; data.updateBlkno = entryPreparePage(btree, page, off); @@ -475,31 +540,6 @@ entryPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off, XLogRecData **prd } /* - * Returns new tuple with copied value from source tuple. - * New tuple will not store posting list - */ -static IndexTuple -copyIndexTuple(IndexTuple itup, Page page) -{ - IndexTuple nitup; - - if (GinPageIsLeaf(page) && !GinIsPostingTree(itup)) - { - nitup = (IndexTuple) palloc(MAXALIGN(GinGetOrigSizePosting(itup))); - memcpy(nitup, itup, GinGetOrigSizePosting(itup)); - nitup->t_info &= ~INDEX_SIZE_MASK; - nitup->t_info |= GinGetOrigSizePosting(itup); - } - else - { - nitup = (IndexTuple) palloc(MAXALIGN(IndexTupleSize(itup))); - memcpy(nitup, itup, IndexTupleSize(itup)); - } - - return nitup; -} - -/* * Place tuple and split page, original buffer(lbuf) leaves untouched, * returns shadow page of lbuf filled new data. * Tuples are distributed between pages by equal size on its, not @@ -508,26 +548,27 @@ copyIndexTuple(IndexTuple itup, Page page) static Page entrySplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off, XLogRecData **prdata) { - static XLogRecData rdata[2]; OffsetNumber i, maxoff, separator = InvalidOffsetNumber; Size totalsize = 0; Size lsize = 0, size; - static char tupstore[2 * BLCKSZ]; char *ptr; IndexTuple itup, leftrightmost = NULL; - static ginxlogSplit data; Page page; Page lpage = PageGetTempPageCopy(BufferGetPage(lbuf)); Page rpage = BufferGetPage(rbuf); Size pageSize = PageGetPageSize(lpage); + /* these must be static so they can be returned to caller */ + static XLogRecData rdata[2]; + static ginxlogSplit data; + static char tupstore[2 * BLCKSZ]; *prdata = rdata; data.leftChildBlkno = (GinPageIsLeaf(lpage)) ? - InvalidOffsetNumber : GinItemPointerGetBlockNumber(&(btree->entry->t_tid)); + InvalidOffsetNumber : GinGetDownlink(btree->entry); data.updateBlkno = entryPreparePage(btree, lpage, off); maxoff = PageGetMaxOffsetNumber(lpage); @@ -588,8 +629,8 @@ entrySplitPage(GinBtree btree, Buffer lbuf, Buffer rbuf, OffsetNumber off, XLogR ptr += MAXALIGN(IndexTupleSize(itup)); } - btree->entry = copyIndexTuple(leftrightmost, lpage); - ItemPointerSet(&(btree->entry)->t_tid, BufferGetBlockNumber(lbuf), InvalidOffsetNumber); + btree->entry = GinFormInteriorTuple(leftrightmost, lpage, + BufferGetBlockNumber(lbuf)); btree->rightblkno = BufferGetBlockNumber(rbuf); @@ -627,8 +668,7 @@ ginPageGetLinkItup(Buffer buf) Page page = BufferGetPage(buf); itup = getRightMostTuple(page); - nitup = copyIndexTuple(itup, page); - ItemPointerSet(&nitup->t_tid, BufferGetBlockNumber(buf), InvalidOffsetNumber); + nitup = GinFormInteriorTuple(itup, page, BufferGetBlockNumber(buf)); return nitup; } @@ -656,12 +696,20 @@ ginEntryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf) pfree(itup); } +/* + * Set up GinBtree for entry page access + * + * Note: during WAL recovery, there may be no valid data in ginstate + * other than a faked-up Relation pointer; the key datum is bogus too. + */ void -ginPrepareEntryScan(GinBtree btree, Relation index, OffsetNumber attnum, Datum value, GinState *ginstate) +ginPrepareEntryScan(GinBtree btree, OffsetNumber attnum, + Datum key, GinNullCategory category, + GinState *ginstate) { memset(btree, 0, sizeof(GinBtreeData)); - btree->index = index; + btree->index = ginstate->index; btree->ginstate = ginstate; btree->findChildPage = entryLocateEntry; @@ -680,6 +728,7 @@ ginPrepareEntryScan(GinBtree btree, Relation index, OffsetNumber attnum, Datum v btree->isBuild = FALSE; btree->entryAttnum = attnum; - btree->entryValue = value; + btree->entryKey = key; + btree->entryCategory = category; btree->isDelete = FALSE; } diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c index 3941f7eb007..9960c786c94 100644 --- a/src/backend/access/gin/ginfast.c +++ b/src/backend/access/gin/ginfast.c @@ -18,8 +18,7 @@ #include "postgres.h" -#include "access/genam.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "catalog/index.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -30,12 +29,13 @@ #define GIN_PAGE_FREESIZE \ ( BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(GinPageOpaqueData)) ) -typedef struct DatumArray +typedef struct KeyArray { - Datum *values; /* expansible array */ + Datum *keys; /* expansible array */ + GinNullCategory *categories; /* another expansible array */ int32 nvalues; /* current number of valid entries */ - int32 maxvalues; /* allocated size of array */ -} DatumArray; + int32 maxvalues; /* allocated size of arrays */ +} KeyArray; /* @@ -88,8 +88,9 @@ writeListPage(Relation index, Buffer buffer, GinPageGetOpaque(page)->rightlink = rightlink; /* - * tail page may contain only the whole row(s) or final part of row placed - * on previous pages + * tail page may contain only whole row(s) or final part of row placed + * on previous pages (a "row" here meaning all the index tuples generated + * for one heap tuple) */ if (rightlink == InvalidBlockNumber) { @@ -210,13 +211,16 @@ makeSublist(Relation index, IndexTuple *tuples, int32 ntuples, } /* - * Inserts collected values during normal insertion. Function guarantees - * that all values of heap will be stored sequentially, preserving order + * Write the index tuples contained in *collector into the index's + * pending list. + * + * Function guarantees that all these tuples will be inserted consecutively, + * preserving order */ void -ginHeapTupleFastInsert(Relation index, GinState *ginstate, - GinTupleCollector *collector) +ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector) { + Relation index = ginstate->index; Buffer metabuffer; Page metapage; GinMetaPageData *metadata = NULL; @@ -291,7 +295,12 @@ ginHeapTupleFastInsert(Relation index, GinState *ginstate, */ START_CRIT_SECTION(); - memcpy(metadata, &sublist, sizeof(GinMetaPageData)); + metadata->head = sublist.head; + metadata->tail = sublist.tail; + metadata->tailFreeSize = sublist.tailFreeSize; + + metadata->nPendingPages = sublist.nPendingPages; + metadata->nPendingHeapTuples = sublist.nPendingHeapTuples; } else { @@ -421,34 +430,40 @@ ginHeapTupleFastInsert(Relation index, GinState *ginstate, END_CRIT_SECTION(); if (needCleanup) - ginInsertCleanup(index, ginstate, false, NULL); + ginInsertCleanup(ginstate, false, NULL); } /* - * Collect values from one tuples to be indexed. All values for - * one tuples should be written at once - to guarantee consistent state + * Create temporary index tuples for a single indexable item (one index column + * for the heap tuple specified by ht_ctid), and append them to the array + * in *collector. They will subsequently be written out using + * ginHeapTupleFastInsert. Note that to guarantee consistent state, all + * temp tuples for a given heap tuple must be written in one call to + * ginHeapTupleFastInsert. */ -uint32 -ginHeapTupleFastCollect(Relation index, GinState *ginstate, +void +ginHeapTupleFastCollect(GinState *ginstate, GinTupleCollector *collector, - OffsetNumber attnum, Datum value, ItemPointer item) + OffsetNumber attnum, Datum value, bool isNull, + ItemPointer ht_ctid) { Datum *entries; + GinNullCategory *categories; int32 i, nentries; - entries = ginExtractEntriesSU(ginstate, attnum, value, &nentries); - - if (nentries == 0) - /* nothing to insert */ - return 0; + /* + * Extract the key values that need to be inserted in the index + */ + entries = ginExtractEntries(ginstate, attnum, value, isNull, + &nentries, &categories); /* * Allocate/reallocate memory for storing collected tuples */ if (collector->tuples == NULL) { - collector->lentuples = nentries * index->rd_att->natts; + collector->lentuples = nentries * ginstate->origTupdesc->natts; collector->tuples = (IndexTuple *) palloc(sizeof(IndexTuple) * collector->lentuples); } @@ -460,19 +475,19 @@ ginHeapTupleFastCollect(Relation index, GinState *ginstate, } /* - * Creates tuple's array + * Build an index tuple for each key value, and add to array. In + * pending tuples we just stick the heap TID into t_tid. */ for (i = 0; i < nentries; i++) { - collector->tuples[collector->ntuples + i] = - GinFormTuple(index, ginstate, attnum, entries[i], NULL, 0, true); - collector->tuples[collector->ntuples + i]->t_tid = *item; - collector->sumsize += IndexTupleSize(collector->tuples[collector->ntuples + i]); - } + IndexTuple itup; - collector->ntuples += nentries; - - return nentries; + itup = GinFormTuple(ginstate, attnum, entries[i], categories[i], + NULL, 0, true); + itup->t_tid = *ht_ctid; + collector->tuples[collector->ntuples++] = itup; + collector->sumsize += IndexTupleSize(itup); + } } /* @@ -591,38 +606,55 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead, return false; } -/* Add datum to DatumArray, resizing if needed */ +/* Initialize empty KeyArray */ static void -addDatum(DatumArray *datums, Datum datum) +initKeyArray(KeyArray *keys, int32 maxvalues) { - if (datums->nvalues >= datums->maxvalues) + keys->keys = (Datum *) palloc(sizeof(Datum) * maxvalues); + keys->categories = (GinNullCategory *) + palloc(sizeof(GinNullCategory) * maxvalues); + keys->nvalues = 0; + keys->maxvalues = maxvalues; +} + +/* Add datum to KeyArray, resizing if needed */ +static void +addDatum(KeyArray *keys, Datum datum, GinNullCategory category) +{ + if (keys->nvalues >= keys->maxvalues) { - datums->maxvalues *= 2; - datums->values = (Datum *) repalloc(datums->values, - sizeof(Datum) * datums->maxvalues); + keys->maxvalues *= 2; + keys->keys = (Datum *) + repalloc(keys->keys, sizeof(Datum) * keys->maxvalues); + keys->categories = (GinNullCategory *) + repalloc(keys->categories, sizeof(GinNullCategory) * keys->maxvalues); } - datums->values[datums->nvalues++] = datum; + keys->keys[keys->nvalues] = datum; + keys->categories[keys->nvalues] = category; + keys->nvalues++; } /* - * Go through all tuples >= startoff on page and collect values in memory + * Collect data from a pending-list page in preparation for insertion into + * the main index. + * + * Go through all tuples >= startoff on page and collect values in accum * - * Note that da is just workspace --- it does not carry any state across + * Note that ka is just workspace --- it does not carry any state across * calls. */ static void -processPendingPage(BuildAccumulator *accum, DatumArray *da, +processPendingPage(BuildAccumulator *accum, KeyArray *ka, Page page, OffsetNumber startoff) { ItemPointerData heapptr; OffsetNumber i, maxoff; - OffsetNumber attrnum, - curattnum; + OffsetNumber attrnum; - /* reset *da to empty */ - da->nvalues = 0; + /* reset *ka to empty */ + ka->nvalues = 0; maxoff = PageGetMaxOffsetNumber(page); Assert(maxoff >= FirstOffsetNumber); @@ -632,7 +664,11 @@ processPendingPage(BuildAccumulator *accum, DatumArray *da, for (i = startoff; i <= maxoff; i = OffsetNumberNext(i)) { IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); + OffsetNumber curattnum; + Datum curkey; + GinNullCategory curcategory; + /* Check for change of heap TID or attnum */ curattnum = gintuple_get_attrnum(accum->ginstate, itup); if (!ItemPointerIsValid(&heapptr)) @@ -644,18 +680,25 @@ processPendingPage(BuildAccumulator *accum, DatumArray *da, curattnum == attrnum)) { /* - * We can insert several datums per call, but only for one heap - * tuple and one column. + * ginInsertBAEntries can insert several datums per call, but only + * for one heap tuple and one column. So call it at a boundary, + * and reset ka. */ - ginInsertRecordBA(accum, &heapptr, attrnum, da->values, da->nvalues); - da->nvalues = 0; + ginInsertBAEntries(accum, &heapptr, attrnum, + ka->keys, ka->categories, ka->nvalues); + ka->nvalues = 0; heapptr = itup->t_tid; attrnum = curattnum; } - addDatum(da, gin_index_getattr(accum->ginstate, itup)); + + /* Add key to KeyArray */ + curkey = gintuple_get_key(accum->ginstate, itup, &curcategory); + addDatum(ka, curkey, curcategory); } - ginInsertRecordBA(accum, &heapptr, attrnum, da->values, da->nvalues); + /* Dump out all remaining keys */ + ginInsertBAEntries(accum, &heapptr, attrnum, + ka->keys, ka->categories, ka->nvalues); } /* @@ -679,9 +722,10 @@ processPendingPage(BuildAccumulator *accum, DatumArray *da, * If stats isn't null, we count deleted pending pages into the counts. */ void -ginInsertCleanup(Relation index, GinState *ginstate, +ginInsertCleanup(GinState *ginstate, bool vac_delay, IndexBulkDeleteResult *stats) { + Relation index = ginstate->index; Buffer metabuffer, buffer; Page metapage, @@ -690,7 +734,7 @@ ginInsertCleanup(Relation index, GinState *ginstate, MemoryContext opCtx, oldCtx; BuildAccumulator accum; - DatumArray datums; + KeyArray datums; BlockNumber blkno; metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO); @@ -726,10 +770,7 @@ ginInsertCleanup(Relation index, GinState *ginstate, oldCtx = MemoryContextSwitchTo(opCtx); - datums.maxvalues = 128; - datums.nvalues = 0; - datums.values = (Datum *) palloc(sizeof(Datum) * datums.maxvalues); - + initKeyArray(&datums, 128); ginInitBA(&accum); accum.ginstate = ginstate; @@ -748,7 +789,7 @@ ginInsertCleanup(Relation index, GinState *ginstate, } /* - * read page's datums into memory + * read page's datums into accum */ processPendingPage(&accum, &datums, page, FirstOffsetNumber); @@ -769,7 +810,8 @@ ginInsertCleanup(Relation index, GinState *ginstate, { ItemPointerData *list; uint32 nlist; - Datum entry; + Datum key; + GinNullCategory category; OffsetNumber maxoff, attnum; @@ -787,9 +829,11 @@ ginInsertCleanup(Relation index, GinState *ginstate, * list. */ ginBeginBAScan(&accum); - while ((list = ginGetEntry(&accum, &attnum, &entry, &nlist)) != NULL) + while ((list = ginGetBAEntry(&accum, + &attnum, &key, &category, &nlist)) != NULL) { - ginEntryInsert(index, ginstate, attnum, entry, list, nlist, NULL); + ginEntryInsert(ginstate, attnum, key, category, + list, nlist, NULL); if (vac_delay) vacuum_delay_point(); } @@ -822,8 +866,10 @@ ginInsertCleanup(Relation index, GinState *ginstate, processPendingPage(&accum, &datums, page, maxoff + 1); ginBeginBAScan(&accum); - while ((list = ginGetEntry(&accum, &attnum, &entry, &nlist)) != NULL) - ginEntryInsert(index, ginstate, attnum, entry, list, nlist, NULL); + while ((list = ginGetBAEntry(&accum, + &attnum, &key, &category, &nlist)) != NULL) + ginEntryInsert(ginstate, attnum, key, category, + list, nlist, NULL); } /* @@ -857,9 +903,8 @@ ginInsertCleanup(Relation index, GinState *ginstate, * release memory used so far and reinit state */ MemoryContextReset(opCtx); + initKeyArray(&datums, datums.maxvalues); ginInitBA(&accum); - datums.nvalues = 0; - datums.values = (Datum *) palloc(sizeof(Datum) * datums.maxvalues); } else { diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c index ed1e71c9d6f..aaef6efbb50 100644 --- a/src/backend/access/gin/ginget.c +++ b/src/backend/access/gin/ginget.c @@ -14,7 +14,7 @@ #include "postgres.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "access/relscan.h" #include "catalog/index.h" #include "miscadmin.h" @@ -34,10 +34,43 @@ typedef struct pendingPosition /* - * Tries to refind previously taken ItemPointer on page. + * Convenience function for invoking a key's consistentFn */ static bool -findItemInPage(Page page, ItemPointer item, OffsetNumber *off) +callConsistentFn(GinState *ginstate, GinScanKey key) +{ + /* + * If we're dealing with a dummy EVERYTHING key, we don't want to call + * the consistentFn; just claim it matches. + */ + if (key->searchMode == GIN_SEARCH_MODE_EVERYTHING) + { + key->recheckCurItem = false; + return true; + } + + /* + * Initialize recheckCurItem in case the consistentFn doesn't know it + * should set it. The safe assumption in that case is to force recheck. + */ + key->recheckCurItem = true; + + return DatumGetBool(FunctionCall8(&ginstate->consistentFn[key->attnum - 1], + PointerGetDatum(key->entryRes), + UInt16GetDatum(key->strategy), + key->query, + UInt32GetDatum(key->nuserentries), + PointerGetDatum(key->extra_data), + PointerGetDatum(&key->recheckCurItem), + PointerGetDatum(key->queryValues), + PointerGetDatum(key->queryCategories))); +} + +/* + * Tries to refind previously taken ItemPointer on a posting page. + */ +static bool +findItemInPostingPage(Page page, ItemPointer item, OffsetNumber *off) { OffsetNumber maxoff = GinPageGetOpaque(page)->maxoff; int res; @@ -79,7 +112,9 @@ moveRightIfItNeeded(GinBtreeData *btree, GinBtreeStack *stack) return false; /* no more pages */ LockBuffer(stack->buffer, GIN_UNLOCK); - stack->buffer = ReleaseAndReadBuffer(stack->buffer, btree->index, stack->blkno); + stack->buffer = ReleaseAndReadBuffer(stack->buffer, + btree->index, + stack->blkno); LockBuffer(stack->buffer, GIN_SHARE); stack->off = FirstOffsetNumber; } @@ -88,17 +123,19 @@ moveRightIfItNeeded(GinBtreeData *btree, GinBtreeStack *stack) } /* - * Does fullscan of posting tree and saves ItemPointers - * in scanEntry->partialMatch TIDBitmap + * Scan all pages of a posting tree and save all its heap ItemPointers + * in scanEntry->matchBitmap */ static void -scanForItems(Relation index, GinScanEntry scanEntry, BlockNumber rootPostingTree) +scanPostingTree(Relation index, GinScanEntry scanEntry, + BlockNumber rootPostingTree) { GinPostingTreeScan *gdi; Buffer buffer; Page page; BlockNumber blkno; + /* Descend to the leftmost leaf page */ gdi = ginPrepareScanPostingTree(index, rootPostingTree, TRUE); buffer = ginScanBeginPostingTree(gdi); @@ -108,51 +145,72 @@ scanForItems(Relation index, GinScanEntry scanEntry, BlockNumber rootPostingTree pfree(gdi); /* - * Goes through all leaves + * Loop iterates through all leaf pages of posting tree */ for (;;) { page = BufferGetPage(buffer); - if ((GinPageGetOpaque(page)->flags & GIN_DELETED) == 0 && GinPageGetOpaque(page)->maxoff >= FirstOffsetNumber) + if ((GinPageGetOpaque(page)->flags & GIN_DELETED) == 0 && + GinPageGetOpaque(page)->maxoff >= FirstOffsetNumber) { - tbm_add_tuples(scanEntry->partialMatch, + tbm_add_tuples(scanEntry->matchBitmap, (ItemPointer) GinDataPageGetItem(page, FirstOffsetNumber), GinPageGetOpaque(page)->maxoff, false); scanEntry->predictNumberResult += GinPageGetOpaque(page)->maxoff; } - blkno = GinPageGetOpaque(page)->rightlink; if (GinPageRightMost(page)) - { - UnlockReleaseBuffer(buffer); - return; /* no more pages */ - } + break; /* no more pages */ + blkno = GinPageGetOpaque(page)->rightlink; LockBuffer(buffer, GIN_UNLOCK); buffer = ReleaseAndReadBuffer(buffer, index, blkno); LockBuffer(buffer, GIN_SHARE); } + + UnlockReleaseBuffer(buffer); } /* - * Collects all ItemPointer into the TIDBitmap struct - * for entries partially matched to search entry. + * Collects TIDs into scanEntry->matchBitmap for all heap tuples that + * match the search entry. This supports three different match modes: + * + * 1. Partial-match support: scan from current point until the + * comparePartialFn says we're done. + * 2. SEARCH_MODE_ALL: scan from current point (which should be first + * key for the current attnum) until we hit null items or end of attnum + * 3. SEARCH_MODE_EVERYTHING: scan from current point (which should be first + * key for the current attnum) until we hit end of attnum * - * Returns true if done, false if it's needed to restart scan from scratch + * Returns true if done, false if it's necessary to restart scan from scratch */ static bool -computePartialMatchList(GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry scanEntry) +collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack, + GinScanEntry scanEntry) { - Page page; - IndexTuple itup; - Datum idatum; - int32 cmp; + OffsetNumber attnum; + Form_pg_attribute attr; - scanEntry->partialMatch = tbm_create(work_mem * 1024L); + /* Initialize empty bitmap result */ + scanEntry->matchBitmap = tbm_create(work_mem * 1024L); + + /* Null query cannot partial-match anything */ + if (scanEntry->isPartialMatch && + scanEntry->queryCategory != GIN_CAT_NORM_KEY) + return true; + + /* Locate tupdesc entry for key column (for attbyval/attlen data) */ + attnum = scanEntry->attnum; + attr = btree->ginstate->origTupdesc->attrs[attnum - 1]; for (;;) { + Page page; + IndexTuple itup; + Datum idatum; + GinNullCategory icategory; + /* * stack->off points to the interested entry, buffer is already locked */ @@ -165,56 +223,84 @@ computePartialMatchList(GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry /* * If tuple stores another attribute then stop scan */ - if (gintuple_get_attrnum(btree->ginstate, itup) != scanEntry->attnum) + if (gintuple_get_attrnum(btree->ginstate, itup) != attnum) return true; - idatum = gin_index_getattr(btree->ginstate, itup); + /* Safe to fetch attribute value */ + idatum = gintuple_get_key(btree->ginstate, itup, &icategory); - - /*---------- - * Check of partial match. - * case cmp == 0 => match - * case cmp > 0 => not match and finish scan - * case cmp < 0 => not match and continue scan - *---------- + /* + * Check for appropriate scan stop conditions */ - cmp = DatumGetInt32(FunctionCall4(&btree->ginstate->comparePartialFn[scanEntry->attnum - 1], - scanEntry->entry, - idatum, - UInt16GetDatum(scanEntry->strategy), - PointerGetDatum(scanEntry->extra_data))); + if (scanEntry->isPartialMatch) + { + int32 cmp; - if (cmp > 0) - return true; - else if (cmp < 0) + /* + * In partial match, stop scan at any null (including + * placeholders); partial matches never match nulls + */ + if (icategory != GIN_CAT_NORM_KEY) + return true; + + /*---------- + * Check of partial match. + * case cmp == 0 => match + * case cmp > 0 => not match and finish scan + * case cmp < 0 => not match and continue scan + *---------- + */ + cmp = DatumGetInt32(FunctionCall4(&btree->ginstate->comparePartialFn[attnum - 1], + scanEntry->queryKey, + idatum, + UInt16GetDatum(scanEntry->strategy), + PointerGetDatum(scanEntry->extra_data))); + + if (cmp > 0) + return true; + else if (cmp < 0) + { + stack->off++; + continue; + } + } + else if (scanEntry->searchMode == GIN_SEARCH_MODE_ALL) { - stack->off++; - continue; + /* + * In ALL mode, we are not interested in null items, so we can + * stop if we get to a null-item placeholder (which will be the + * last entry for a given attnum). We do want to include NULL_KEY + * and EMPTY_ITEM entries, though. + */ + if (icategory == GIN_CAT_NULL_ITEM) + return true; } + /* + * OK, we want to return the TIDs listed in this entry. + */ if (GinIsPostingTree(itup)) { BlockNumber rootPostingTree = GinGetPostingTree(itup); - Datum newDatum, - savedDatum = datumCopy( - idatum, - btree->ginstate->origTupdesc->attrs[scanEntry->attnum - 1]->attbyval, - btree->ginstate->origTupdesc->attrs[scanEntry->attnum - 1]->attlen - ); /* * We should unlock current page (but not unpin) during tree scan * to prevent deadlock with vacuum processes. * - * We save current entry value (savedDatum) to be able to refind + * We save current entry value (idatum) to be able to re-find * our tuple after re-locking */ + if (icategory == GIN_CAT_NORM_KEY) + idatum = datumCopy(idatum, attr->attbyval, attr->attlen); + LockBuffer(stack->buffer, GIN_UNLOCK); - scanForItems(btree->index, scanEntry, rootPostingTree); + + /* Collect all the TIDs in this entry's posting tree */ + scanPostingTree(btree->index, scanEntry, rootPostingTree); /* * We lock again the entry page and while it was unlocked insert - * might occured, so we need to refind our position + * might have occurred, so we need to re-find our position. */ LockBuffer(stack->buffer, GIN_SHARE); page = BufferGetPage(stack->buffer); @@ -222,45 +308,49 @@ computePartialMatchList(GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry { /* * Root page becomes non-leaf while we unlock it. We will - * start again, this situation doesn't cause often - root can - * became a non-leaf only one per life of index. + * start again, this situation doesn't occur often - root can + * became a non-leaf only once per life of index. */ - return false; } + /* Search forward to re-find idatum */ for (;;) { + Datum newDatum; + GinNullCategory newCategory; + if (moveRightIfItNeeded(btree, stack) == false) elog(ERROR, "lost saved point in index"); /* must not happen !!! */ page = BufferGetPage(stack->buffer); itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off)); - newDatum = gin_index_getattr(btree->ginstate, itup); - if (gintuple_get_attrnum(btree->ginstate, itup) != scanEntry->attnum) + if (gintuple_get_attrnum(btree->ginstate, itup) != attnum) elog(ERROR, "lost saved point in index"); /* must not happen !!! */ + newDatum = gintuple_get_key(btree->ginstate, itup, + &newCategory); - if (ginCompareEntries(btree->ginstate, scanEntry->attnum, - newDatum, savedDatum) == 0) - { - /* Found! */ - if (btree->ginstate->origTupdesc->attrs[scanEntry->attnum - 1]->attbyval == false) - pfree(DatumGetPointer(savedDatum)); - break; - } + if (ginCompareEntries(btree->ginstate, attnum, + newDatum, newCategory, + idatum, icategory) == 0) + break; /* Found! */ stack->off++; } + + if (icategory == GIN_CAT_NORM_KEY && !attr->attbyval) + pfree(DatumGetPointer(idatum)); } else { - tbm_add_tuples(scanEntry->partialMatch, GinGetPosting(itup), GinGetNPosting(itup), false); + tbm_add_tuples(scanEntry->matchBitmap, + GinGetPosting(itup), GinGetNPosting(itup), false); scanEntry->predictNumberResult += GinGetNPosting(itup); } /* - * Ok, we save ItemPointers, go to the next entry + * Done with this entry, go to the next */ stack->off++; } @@ -272,19 +362,20 @@ computePartialMatchList(GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry * Start* functions setup beginning state of searches: finds correct buffer and pins it. */ static void -startScanEntry(Relation index, GinState *ginstate, GinScanEntry entry) +startScanEntry(GinState *ginstate, GinScanEntry entry) { GinBtreeData btreeEntry; GinBtreeStack *stackEntry; Page page; - bool needUnlock = TRUE; + bool needUnlock; +restartScanEntry: entry->buffer = InvalidBuffer; entry->offset = InvalidOffsetNumber; entry->list = NULL; entry->nlist = 0; - entry->partialMatch = NULL; - entry->partialMatchResult = NULL; + entry->matchBitmap = NULL; + entry->matchResult = NULL; entry->reduceResult = FALSE; entry->predictNumberResult = 0; @@ -298,46 +389,50 @@ startScanEntry(Relation index, GinState *ginstate, GinScanEntry entry) * we should find entry, and begin scan of posting tree or just store * posting list in memory */ - - ginPrepareEntryScan(&btreeEntry, index, entry->attnum, entry->entry, ginstate); + ginPrepareEntryScan(&btreeEntry, entry->attnum, + entry->queryKey, entry->queryCategory, + ginstate); btreeEntry.searchMode = TRUE; stackEntry = ginFindLeafPage(&btreeEntry, NULL); page = BufferGetPage(stackEntry->buffer); + needUnlock = TRUE; entry->isFinished = TRUE; - if (entry->isPartialMatch) + if (entry->isPartialMatch || + entry->queryCategory == GIN_CAT_EMPTY_QUERY) { /* - * btreeEntry.findItem points to the first equal or greater value than - * needed. So we will scan further and collect all ItemPointers + * btreeEntry.findItem locates the first item >= given search key. + * (For GIN_CAT_EMPTY_QUERY, it will find the leftmost index item + * because of the way the GIN_CAT_EMPTY_QUERY category code is + * assigned.) We scan forward from there and collect all TIDs needed + * for the entry type. */ btreeEntry.findItem(&btreeEntry, stackEntry); - if (computePartialMatchList(&btreeEntry, stackEntry, entry) == false) + if (collectMatchBitmap(&btreeEntry, stackEntry, entry) == false) { /* * GIN tree was seriously restructured, so we will cleanup all * found data and rescan. See comments near 'return false' in - * computePartialMatchList() + * collectMatchBitmap() */ - if (entry->partialMatch) + if (entry->matchBitmap) { - if (entry->partialMatchIterator) - tbm_end_iterate(entry->partialMatchIterator); - entry->partialMatchIterator = NULL; - tbm_free(entry->partialMatch); - entry->partialMatch = NULL; + if (entry->matchIterator) + tbm_end_iterate(entry->matchIterator); + entry->matchIterator = NULL; + tbm_free(entry->matchBitmap); + entry->matchBitmap = NULL; } LockBuffer(stackEntry->buffer, GIN_UNLOCK); freeGinBtreeStack(stackEntry); - - startScanEntry(index, ginstate, entry); - return; + goto restartScanEntry; } - if (entry->partialMatch && !tbm_is_empty(entry->partialMatch)) + if (entry->matchBitmap && !tbm_is_empty(entry->matchBitmap)) { - entry->partialMatchIterator = tbm_begin_iterate(entry->partialMatch); + entry->matchIterator = tbm_begin_iterate(entry->matchBitmap); entry->isFinished = FALSE; } } @@ -352,7 +447,7 @@ startScanEntry(Relation index, GinState *ginstate, GinScanEntry entry) Page page; /* - * We should unlock entry page before make deal with posting tree + * We should unlock entry page before touching posting tree * to prevent deadlocks with vacuum processes. Because entry is * never deleted from page and posting tree is never reduced to * the posting list, we can unlock page after getting BlockNumber @@ -360,7 +455,7 @@ startScanEntry(Relation index, GinState *ginstate, GinScanEntry entry) */ LockBuffer(stackEntry->buffer, GIN_UNLOCK); needUnlock = FALSE; - gdi = ginPrepareScanPostingTree(index, rootPostingTree, TRUE); + gdi = ginPrepareScanPostingTree(ginstate->index, rootPostingTree, TRUE); entry->buffer = ginScanBeginPostingTree(gdi); @@ -402,7 +497,7 @@ startScanEntry(Relation index, GinState *ginstate, GinScanEntry entry) } static void -startScanKey(Relation index, GinState *ginstate, GinScanKey key) +startScanKey(GinState *ginstate, GinScanKey key) { uint32 i; @@ -410,7 +505,7 @@ startScanKey(Relation index, GinState *ginstate, GinScanKey key) return; for (i = 0; i < key->nentries; i++) - startScanEntry(index, ginstate, key->scanEntry + i); + startScanEntry(ginstate, key->scanEntry + i); key->isFinished = FALSE; key->firstCall = FALSE; @@ -444,7 +539,7 @@ startScan(IndexScanDesc scan) GinScanOpaque so = (GinScanOpaque) scan->opaque; for (i = 0; i < so->nkeys; i++) - startScanKey(scan->indexRelation, &so->ginstate, so->keys + i); + startScanKey(&so->ginstate, so->keys + i); } /* @@ -453,7 +548,7 @@ startScan(IndexScanDesc scan) * to prevent interference with vacuum */ static void -entryGetNextItem(Relation index, GinScanEntry entry) +entryGetNextItem(GinState *ginstate, GinScanEntry entry) { Page page; BlockNumber blkno; @@ -487,13 +582,15 @@ entryGetNextItem(Relation index, GinScanEntry entry) return; } - entry->buffer = ReleaseAndReadBuffer(entry->buffer, index, blkno); + entry->buffer = ReleaseAndReadBuffer(entry->buffer, + ginstate->index, + blkno); LockBuffer(entry->buffer, GIN_SHARE); page = BufferGetPage(entry->buffer); entry->offset = InvalidOffsetNumber; if (!ItemPointerIsValid(&entry->curItem) || - findItemInPage(page, &entry->curItem, &entry->offset)) + findItemInPostingPage(page, &entry->curItem, &entry->offset)) { /* * Found position equal to or greater than stored @@ -512,7 +609,6 @@ entryGetNextItem(Relation index, GinScanEntry entry) * First pages are deleted or empty, or we found exact * position, so break inner loop and continue outer one. */ - break; } @@ -527,25 +623,6 @@ entryGetNextItem(Relation index, GinScanEntry entry) } } -/* convenience function for invoking a key's consistentFn */ -static inline bool -callConsistentFn(GinState *ginstate, GinScanKey key) -{ - /* - * Initialize recheckCurItem in case the consistentFn doesn't know it - * should set it. The safe assumption in that case is to force recheck. - */ - key->recheckCurItem = true; - - return DatumGetBool(FunctionCall6(&ginstate->consistentFn[key->attnum - 1], - PointerGetDatum(key->entryRes), - UInt16GetDatum(key->strategy), - key->query, - UInt32GetDatum(key->nentries), - PointerGetDatum(key->extra_data), - PointerGetDatum(&key->recheckCurItem))); -} - #define gin_rand() (((double) random()) / ((double) MAX_RANDOM_VALUE)) #define dropItem(e) ( gin_rand() > ((double)GinFuzzySearchLimit)/((double)((e)->predictNumberResult)) ) @@ -563,7 +640,7 @@ callConsistentFn(GinState *ginstate, GinScanKey key) * current implementation this is guaranteed by the behavior of tidbitmaps. */ static void -entryGetItem(Relation index, GinScanEntry entry) +entryGetItem(GinState *ginstate, GinScanEntry entry) { Assert(!entry->isFinished); @@ -572,41 +649,40 @@ entryGetItem(Relation index, GinScanEntry entry) entry->isFinished = entry->master->isFinished; entry->curItem = entry->master->curItem; } - else if (entry->partialMatch) + else if (entry->matchBitmap) { do { - if (entry->partialMatchResult == NULL || - entry->offset >= entry->partialMatchResult->ntuples) + if (entry->matchResult == NULL || + entry->offset >= entry->matchResult->ntuples) { - entry->partialMatchResult = tbm_iterate(entry->partialMatchIterator); + entry->matchResult = tbm_iterate(entry->matchIterator); - if (entry->partialMatchResult == NULL) + if (entry->matchResult == NULL) { ItemPointerSetInvalid(&entry->curItem); - tbm_end_iterate(entry->partialMatchIterator); - entry->partialMatchIterator = NULL; + tbm_end_iterate(entry->matchIterator); + entry->matchIterator = NULL; entry->isFinished = TRUE; break; } /* - * reset counter to the beginning of - * entry->partialMatchResult. Note: entry->offset is still - * greater than partialMatchResult->ntuples if - * partialMatchResult is lossy. So, on next call we will get - * next result from TIDBitmap. + * Reset counter to the beginning of entry->matchResult. + * Note: entry->offset is still greater than + * matchResult->ntuples if matchResult is lossy. So, on next + * call we will get next result from TIDBitmap. */ entry->offset = 0; } - if (entry->partialMatchResult->ntuples < 0) + if (entry->matchResult->ntuples < 0) { /* * lossy result, so we need to check the whole page */ ItemPointerSetLossyPage(&entry->curItem, - entry->partialMatchResult->blockno); + entry->matchResult->blockno); /* * We might as well fall out of the loop; we could not @@ -617,8 +693,8 @@ entryGetItem(Relation index, GinScanEntry entry) } ItemPointerSet(&entry->curItem, - entry->partialMatchResult->blockno, - entry->partialMatchResult->offsets[entry->offset]); + entry->matchResult->blockno, + entry->matchResult->offsets[entry->offset]); entry->offset++; } while (entry->reduceResult == TRUE && dropItem(entry)); } @@ -637,7 +713,7 @@ entryGetItem(Relation index, GinScanEntry entry) { do { - entryGetNextItem(index, entry); + entryGetNextItem(ginstate, entry); } while (entry->isFinished == FALSE && entry->reduceResult == TRUE && dropItem(entry)); @@ -662,7 +738,7 @@ entryGetItem(Relation index, GinScanEntry entry) * logic in scanGetItem.) */ static void -keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx, +keyGetItem(GinState *ginstate, MemoryContext tempCtx, GinScanKey key, ItemPointer advancePast) { ItemPointerData myAdvancePast = *advancePast; @@ -701,7 +777,7 @@ keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx, while (entry->isFinished == FALSE && ginCompareItemPointers(&entry->curItem, &myAdvancePast) <= 0) - entryGetItem(index, entry); + entryGetItem(ginstate, entry); if (entry->isFinished == FALSE && ginCompareItemPointers(&entry->curItem, &key->curItem) < 0) @@ -800,12 +876,12 @@ keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx, * item pointers, possibly in combination with a lossy pointer. Our * strategy if there's a lossy pointer is to try the consistentFn both * ways and return a hit if it accepts either one (forcing the hit to - * be marked lossy so it will be rechecked). + * be marked lossy so it will be rechecked). An exception is that + * we don't need to try it both ways if the lossy pointer is in a + * "hidden" entry, because the consistentFn's result can't depend on + * that. * * Prepare entryRes array to be passed to consistentFn. - * - * (If key->nentries == 1 then the consistentFn should always succeed, - * but we must call it anyway to find out the recheck status.) */ for (i = 0; i < key->nentries; i++) { @@ -821,7 +897,7 @@ keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx, res = callConsistentFn(ginstate, key); - if (!res && haveLossyEntry) + if (!res && haveLossyEntry && lossyEntry < key->nuserentries) { /* try the other way for the lossy item */ key->entryRes[lossyEntry] = FALSE; @@ -839,13 +915,137 @@ keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx, } while (!res); } +/* + * Get next heap item pointer (after advancePast) from scan. + * Returns true if anything found. + * On success, *item and *recheck are set. + * + * Note: this is very nearly the same logic as in keyGetItem(), except + * that we know the keys are to be combined with AND logic, whereas in + * keyGetItem() the combination logic is known only to the consistentFn. + */ +static bool +scanGetItem(IndexScanDesc scan, ItemPointer advancePast, + ItemPointerData *item, bool *recheck) +{ + GinScanOpaque so = (GinScanOpaque) scan->opaque; + ItemPointerData myAdvancePast = *advancePast; + uint32 i; + bool match; + + for (;;) + { + /* + * Advance any keys that are <= myAdvancePast. In particular, + * since key->curItem was initialized with ItemPointerSetMin, this + * ensures we fetch the first item for each key on the first call. + * Then set *item to the minimum of the key curItems. + * + * Note: a lossy-page entry is encoded by a ItemPointer with max value + * for offset (0xffff), so that it will sort after any exact entries + * for the same page. So we'll prefer to return exact pointers not + * lossy pointers, which is good. Also, when we advance past an exact + * entry after processing it, we will not advance past lossy entries + * for the same page in other keys, which is NECESSARY for correct + * results (since we might have additional entries for the same page + * in the first key). + */ + ItemPointerSetMax(item); + + for (i = 0; i < so->nkeys; i++) + { + GinScanKey key = so->keys + i; + + while (key->isFinished == FALSE && + ginCompareItemPointers(&key->curItem, &myAdvancePast) <= 0) + keyGetItem(&so->ginstate, so->tempCtx, + key, &myAdvancePast); + + if (key->isFinished) + return FALSE; /* finished one of keys */ + + if (ginCompareItemPointers(&key->curItem, item) < 0) + *item = key->curItem; + } + + Assert(!ItemPointerIsMax(item)); + + /*---------- + * Now *item contains first ItemPointer after previous result. + * + * The item is a valid hit only if all the keys returned either + * that exact TID, or a lossy reference to the same page. + * + * This logic works only if a keyGetItem stream can never contain both + * exact and lossy pointers for the same page. Else we could have a + * case like + * + * stream 1 stream 2 + * ... ... + * 42/6 42/7 + * 50/1 42/0xffff + * ... ... + * + * We would conclude that 42/6 is not a match and advance stream 1, + * thus never detecting the match to the lossy pointer in stream 2. + * (keyGetItem has a similar problem versus entryGetItem.) + *---------- + */ + match = true; + for (i = 0; i < so->nkeys; i++) + { + GinScanKey key = so->keys + i; + + if (ginCompareItemPointers(item, &key->curItem) == 0) + continue; + if (ItemPointerIsLossyPage(&key->curItem) && + GinItemPointerGetBlockNumber(&key->curItem) == + GinItemPointerGetBlockNumber(item)) + continue; + match = false; + break; + } + + if (match) + break; + + /* + * No hit. Update myAdvancePast to this TID, so that on the next + * pass we'll move to the next possible entry. + */ + myAdvancePast = *item; + } + + /* + * We must return recheck = true if any of the keys are marked recheck. + */ + *recheck = false; + for (i = 0; i < so->nkeys; i++) + { + GinScanKey key = so->keys + i; + + if (key->recheckCurItem) + { + *recheck = true; + break; + } + } + + return TRUE; +} + + +/* + * Functions for scanning the pending list + */ + /* * Get ItemPointer of next heap row to be checked from pending list. - * Returns false if there are no more. On pages with several rows + * Returns false if there are no more. On pages with several heap rows * it returns each row separately, on page with part of heap row returns - * per page data. pos->firstOffset and pos->lastOffset points - * fraction of tuples for current heap row. + * per page data. pos->firstOffset and pos->lastOffset are set to identify + * the range of pending-list tuples belonging to this heap row. * * The pendingBuffer is presumed pinned and share-locked on entry, and is * pinned and share-locked on success exit. On failure exit it's released. @@ -917,10 +1117,9 @@ scanGetCandidate(IndexScanDesc scan, pendingPosition *pos) /* * Now pos->firstOffset points to the first tuple of current heap - * row, pos->lastOffset points to the first tuple of second heap + * row, pos->lastOffset points to the first tuple of next heap * row (or to the end of page) */ - break; } } @@ -929,35 +1128,47 @@ scanGetCandidate(IndexScanDesc scan, pendingPosition *pos) } /* - * Scan page from current tuple (off) up till the first of: + * Scan pending-list page from current tuple (off) up till the first of: * - match is found (then returns true) * - no later match is possible * - tuple's attribute number is not equal to entry's attrnum * - reach end of page + * + * datum[]/category[]/datumExtracted[] arrays are used to cache the results + * of gintuple_get_key() on the current page. */ static bool matchPartialInPendingList(GinState *ginstate, Page page, OffsetNumber off, OffsetNumber maxoff, - Datum value, OffsetNumber attrnum, - Datum *datum, bool *datumExtracted, - StrategyNumber strategy, - Pointer extra_data) + GinScanEntry entry, + Datum *datum, GinNullCategory *category, + bool *datumExtracted) { IndexTuple itup; int32 cmp; + /* Partial match to a null is not possible */ + if (entry->queryCategory != GIN_CAT_NORM_KEY) + return false; + while (off < maxoff) { itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); - if (attrnum != gintuple_get_attrnum(ginstate, itup)) + + if (gintuple_get_attrnum(ginstate, itup) != entry->attnum) return false; if (datumExtracted[off - 1] == false) { - datum[off - 1] = gin_index_getattr(ginstate, itup); + datum[off - 1] = gintuple_get_key(ginstate, itup, + &category[off - 1]); datumExtracted[off - 1] = true; } + /* Once we hit nulls, no further match is possible */ + if (category[off - 1] != GIN_CAT_NORM_KEY) + return false; + /*---------- * Check partial match. * case cmp == 0 => match @@ -965,11 +1176,11 @@ matchPartialInPendingList(GinState *ginstate, Page page, * case cmp < 0 => not match and continue scan *---------- */ - cmp = DatumGetInt32(FunctionCall4(&ginstate->comparePartialFn[attrnum - 1], - value, + cmp = DatumGetInt32(FunctionCall4(&ginstate->comparePartialFn[entry->attnum - 1], + entry->queryKey, datum[off - 1], - UInt16GetDatum(strategy), - PointerGetDatum(extra_data))); + UInt16GetDatum(entry->strategy), + PointerGetDatum(entry->extra_data))); if (cmp == 0) return true; else if (cmp > 0) @@ -981,27 +1192,20 @@ matchPartialInPendingList(GinState *ginstate, Page page, return false; } -static bool -hasAllMatchingKeys(GinScanOpaque so, pendingPosition *pos) -{ - int i; - - for (i = 0; i < so->nkeys; i++) - if (pos->hasMatchKey[i] == false) - return false; - - return true; -} - /* - * Sets entryRes array for each key by looking at - * every entry per indexed value (heap's row) in pending list. - * returns true if at least one of datum was matched by key's entry + * Set up the entryRes array for each key by looking at + * every entry for current heap row in pending list. + * + * Returns true if each scan key has at least one entryRes match. + * This corresponds to the situations where the normal index search will + * try to apply the key's consistentFn. (A tuple not meeting that requirement + * cannot be returned by the normal search since no entry stream will + * source its TID.) * * The pendingBuffer is presumed pinned and share-locked on entry. */ static bool -collectDatumForItem(IndexScanDesc scan, pendingPosition *pos) +collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos) { GinScanOpaque so = (GinScanOpaque) scan->opaque; OffsetNumber attrnum; @@ -1011,7 +1215,7 @@ collectDatumForItem(IndexScanDesc scan, pendingPosition *pos) j; /* - * Reset entryRes + * Reset all entryRes and hasMatchKey flags */ for (i = 0; i < so->nkeys; i++) { @@ -1021,13 +1225,19 @@ collectDatumForItem(IndexScanDesc scan, pendingPosition *pos) } memset(pos->hasMatchKey, FALSE, so->nkeys); + /* + * Outer loop iterates over multiple pending-list pages when a single + * heap row has entries spanning those pages. + */ for (;;) { Datum datum[BLCKSZ / sizeof(IndexTupleData)]; + GinNullCategory category[BLCKSZ / sizeof(IndexTupleData)]; bool datumExtracted[BLCKSZ / sizeof(IndexTupleData)]; Assert(pos->lastOffset > pos->firstOffset); - memset(datumExtracted + pos->firstOffset - 1, 0, sizeof(bool) * (pos->lastOffset - pos->firstOffset)); + memset(datumExtracted + pos->firstOffset - 1, 0, + sizeof(bool) * (pos->lastOffset - pos->firstOffset)); page = BufferGetPage(pos->pendingBuffer); @@ -1037,128 +1247,175 @@ collectDatumForItem(IndexScanDesc scan, pendingPosition *pos) for (j = 0; j < key->nentries; j++) { + GinScanEntry entry = key->scanEntry + j; OffsetNumber StopLow = pos->firstOffset, StopHigh = pos->lastOffset, StopMiddle; - GinScanEntry entry = key->scanEntry + j; - /* already true - do not extra work */ + /* If already matched on earlier page, do no extra work */ if (key->entryRes[j]) continue; /* - * Interested tuples are from pos->firstOffset to + * Interesting tuples are from pos->firstOffset to * pos->lastOffset and they are ordered by (attnum, Datum) as - * it's done in entry tree So we could use binary search to - * prevent linear scanning + * it's done in entry tree. So we can use binary search to + * avoid linear scanning. */ while (StopLow < StopHigh) { + int res; + StopMiddle = StopLow + ((StopHigh - StopLow) >> 1); itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, StopMiddle)); + attrnum = gintuple_get_attrnum(&so->ginstate, itup); if (key->attnum < attrnum) + { StopHigh = StopMiddle; - else if (key->attnum > attrnum) + continue; + } + if (key->attnum > attrnum) + { StopLow = StopMiddle + 1; - else + continue; + } + + if (datumExtracted[StopMiddle - 1] == false) { - int res; + datum[StopMiddle - 1] = + gintuple_get_key(&so->ginstate, itup, + &category[StopMiddle - 1]); + datumExtracted[StopMiddle - 1] = true; + } - if (datumExtracted[StopMiddle - 1] == false) + if (entry->queryCategory == GIN_CAT_EMPTY_QUERY) + { + /* special behavior depending on searchMode */ + if (entry->searchMode == GIN_SEARCH_MODE_ALL) { - datum[StopMiddle - 1] = gin_index_getattr(&so->ginstate, itup); - datumExtracted[StopMiddle - 1] = true; + /* match anything except NULL_ITEM */ + if (category[StopMiddle - 1] == GIN_CAT_NULL_ITEM) + res = -1; + else + res = 0; + } + else + { + /* match everything */ + res = 0; } + } + else + { res = ginCompareEntries(&so->ginstate, entry->attnum, - entry->entry, - datum[StopMiddle - 1]); + entry->queryKey, + entry->queryCategory, + datum[StopMiddle - 1], + category[StopMiddle - 1]); + } - if (res == 0) - { - /* - * The exact match causes, so we just scan from - * current position to find a partial match. See - * comment above about tuple's ordering. - */ - if (entry->isPartialMatch) - key->entryRes[j] = - matchPartialInPendingList(&so->ginstate, - page, StopMiddle, - pos->lastOffset, - entry->entry, - entry->attnum, - datum, - datumExtracted, - entry->strategy, - entry->extra_data); - else - key->entryRes[j] = true; - break; - } - else if (res < 0) - StopHigh = StopMiddle; + if (res == 0) + { + /* + * Found exact match (there can be only one, except + * in EMPTY_QUERY mode). + * + * If doing partial match, scan forward from + * here to end of page to check for matches. + * + * See comment above about tuple's ordering. + */ + if (entry->isPartialMatch) + key->entryRes[j] = + matchPartialInPendingList(&so->ginstate, + page, + StopMiddle, + pos->lastOffset, + entry, + datum, + category, + datumExtracted); else - StopLow = StopMiddle + 1; + key->entryRes[j] = true; + + /* done with binary search */ + break; } + else if (res < 0) + StopHigh = StopMiddle; + else + StopLow = StopMiddle + 1; } if (StopLow >= StopHigh && entry->isPartialMatch) { /* - * The exact match wasn't found, so we need to start scan - * from first tuple greater then current entry See comment - * above about tuple's ordering. + * No exact match on this page. If doing partial + * match, scan from the first tuple greater than + * target value to end of page. Note that since we + * don't remember whether the comparePartialFn told us + * to stop early on a previous page, we will uselessly + * apply comparePartialFn to the first tuple on each + * subsequent page. */ key->entryRes[j] = matchPartialInPendingList(&so->ginstate, - page, StopHigh, + page, + StopHigh, pos->lastOffset, - entry->entry, - entry->attnum, + entry, datum, - datumExtracted, - entry->strategy, - entry->extra_data); + category, + datumExtracted); } pos->hasMatchKey[i] |= key->entryRes[j]; } } + /* Advance firstOffset over the scanned tuples */ pos->firstOffset = pos->lastOffset; if (GinPageHasFullRow(page)) { /* - * We scan all values from one tuple, go to next one + * We have examined all pending entries for the current heap row. + * Break out of loop over pages. */ - - return hasAllMatchingKeys(so, pos); + break; } else { - ItemPointerData item = pos->item; - /* - * need to get next portion of tuples of row containing on several - * pages + * Advance to next page of pending entries for the current heap + * row. Complain if there isn't one. */ + ItemPointerData item = pos->item; - if (scanGetCandidate(scan, pos) == false || !ItemPointerEquals(&pos->item, &item)) - elog(ERROR, "Could not process tuple"); /* XXX should not be - * here ! */ + if (scanGetCandidate(scan, pos) == false || + !ItemPointerEquals(&pos->item, &item)) + elog(ERROR, "could not find additional pending pages for same heap tuple"); } } - return hasAllMatchingKeys(so, pos); + /* + * Now return "true" if all scan keys have at least one matching datum + */ + for (i = 0; i < so->nkeys; i++) + { + if (pos->hasMatchKey[i] == false) + return false; + } + + return true; } /* - * Collect all matched rows from pending list in bitmap + * Collect all matched rows from pending list into bitmap */ static void scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids) @@ -1201,11 +1458,13 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids) while (scanGetCandidate(scan, &pos)) { /* - * Check entries in tuple and setup entryRes array If tuples of heap's - * row are placed on several pages collectDatumForItem will read all - * of that pages. + * Check entries in tuple and set up entryRes array. + * + * If pending tuples belonging to the current heap row are spread + * across several pages, collectMatchesForHeapRow will read all of + * those pages. */ - if (!collectDatumForItem(scan, &pos)) + if (!collectMatchesForHeapRow(scan, &pos)) continue; /* @@ -1241,124 +1500,6 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids) pfree(pos.hasMatchKey); } -/* - * Get next heap item pointer (after advancePast) from scan. - * Returns true if anything found. - * On success, *item and *recheck are set. - * - * Note: this is very nearly the same logic as in keyGetItem(), except - * that we know the keys are to be combined with AND logic, whereas in - * keyGetItem() the combination logic is known only to the consistentFn. - */ -static bool -scanGetItem(IndexScanDesc scan, ItemPointer advancePast, - ItemPointerData *item, bool *recheck) -{ - GinScanOpaque so = (GinScanOpaque) scan->opaque; - ItemPointerData myAdvancePast = *advancePast; - uint32 i; - bool match; - - for (;;) - { - /* - * Advance any keys that are <= myAdvancePast. In particular, - * since key->curItem was initialized with ItemPointerSetMin, this - * ensures we fetch the first item for each key on the first call. - * Then set *item to the minimum of the key curItems. - * - * Note: a lossy-page entry is encoded by a ItemPointer with max value - * for offset (0xffff), so that it will sort after any exact entries - * for the same page. So we'll prefer to return exact pointers not - * lossy pointers, which is good. Also, when we advance past an exact - * entry after processing it, we will not advance past lossy entries - * for the same page in other keys, which is NECESSARY for correct - * results (since we might have additional entries for the same page - * in the first key). - */ - ItemPointerSetMax(item); - - for (i = 0; i < so->nkeys; i++) - { - GinScanKey key = so->keys + i; - - while (key->isFinished == FALSE && - ginCompareItemPointers(&key->curItem, &myAdvancePast) <= 0) - keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx, - key, &myAdvancePast); - - if (key->isFinished) - return FALSE; /* finished one of keys */ - - if (ginCompareItemPointers(&key->curItem, item) < 0) - *item = key->curItem; - } - - Assert(!ItemPointerIsMax(item)); - - /*---------- - * Now *item contains first ItemPointer after previous result. - * - * The item is a valid hit only if all the keys returned either - * that exact TID, or a lossy reference to the same page. - * - * This logic works only if a keyGetItem stream can never contain both - * exact and lossy pointers for the same page. Else we could have a - * case like - * - * stream 1 stream 2 - * ... ... - * 42/6 42/7 - * 50/1 42/0xffff - * ... ... - * - * We would conclude that 42/6 is not a match and advance stream 1, - * thus never detecting the match to the lossy pointer in stream 2. - * (keyGetItem has a similar problem versus entryGetItem.) - *---------- - */ - match = true; - for (i = 0; i < so->nkeys; i++) - { - GinScanKey key = so->keys + i; - - if (ginCompareItemPointers(item, &key->curItem) == 0) - continue; - if (ItemPointerIsLossyPage(&key->curItem) && - GinItemPointerGetBlockNumber(&key->curItem) == - GinItemPointerGetBlockNumber(item)) - continue; - match = false; - break; - } - - if (match) - break; - - /* - * No hit. Update myAdvancePast to this TID, so that on the next - * pass we'll move to the next possible entry. - */ - myAdvancePast = *item; - } - - /* - * We must return recheck = true if any of the keys are marked recheck. - */ - *recheck = false; - for (i = 0; i < so->nkeys; i++) - { - GinScanKey key = so->keys + i; - - if (key->recheckCurItem) - { - *recheck = true; - break; - } - } - - return TRUE; -} #define GinIsNewKey(s) ( ((GinScanOpaque) scan->opaque)->keys == NULL ) #define GinIsVoidRes(s) ( ((GinScanOpaque) scan->opaque)->isVoidRes ) @@ -1372,6 +1513,9 @@ gingetbitmap(PG_FUNCTION_ARGS) ItemPointerData iptr; bool recheck; + /* + * Set up the scan keys, and check for unsatisfiable query. + */ if (GinIsNewKey(scan)) ginNewScanKey(scan); diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index 5b146d6c265..af5068906fb 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -14,8 +14,7 @@ #include "postgres.h" -#include "access/genam.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "catalog/index.h" #include "miscadmin.h" #include "storage/bufmgr.h" @@ -35,8 +34,10 @@ typedef struct } GinBuildState; /* - * Creates posting tree with one page. Function - * suppose that items[] fits to page + * Creates new posting tree with one page, containing the given TIDs. + * Returns the page number (which will be the root of this posting tree). + * + * items[] must be in sorted order with no duplicates. */ static BlockNumber createPostingTree(Relation index, ItemPointerData *items, uint32 nitems) @@ -45,6 +46,9 @@ createPostingTree(Relation index, ItemPointerData *items, uint32 nitems) Buffer buffer = GinNewBuffer(index); Page page; + /* Assert that the items[] array will fit on one page */ + Assert(nitems <= GinMaxLeafDataItems); + START_CRIT_SECTION(); GinInitBuffer(buffer, GIN_DATA | GIN_LEAF); @@ -76,12 +80,9 @@ createPostingTree(Relation index, ItemPointerData *items, uint32 nitems) rdata[1].len = sizeof(ItemPointerData) * nitems; rdata[1].next = NULL; - - recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_PTREE, rdata); PageSetLSN(page, recptr); PageSetTLI(page, ThisTimeLineID); - } UnlockReleaseBuffer(buffer); @@ -93,28 +94,39 @@ createPostingTree(Relation index, ItemPointerData *items, uint32 nitems) /* - * Adds array of item pointers to tuple's posting list or - * creates posting tree and tuple pointed to tree in a case + * Adds array of item pointers to tuple's posting list, or + * creates posting tree and tuple pointing to tree in case * of not enough space. Max size of tuple is defined in - * GinFormTuple(). + * GinFormTuple(). Returns a new, modified index tuple. + * items[] must be in sorted order with no duplicates. */ static IndexTuple -addItemPointersToTuple(Relation index, GinState *ginstate, - GinBtreeStack *stack, IndexTuple old, - ItemPointerData *items, uint32 nitem, - GinStatsData *buildStats) +addItemPointersToLeafTuple(GinState *ginstate, + IndexTuple old, + ItemPointerData *items, uint32 nitem, + GinStatsData *buildStats) { - Datum key = gin_index_getattr(ginstate, old); - OffsetNumber attnum = gintuple_get_attrnum(ginstate, old); - IndexTuple res = GinFormTuple(index, ginstate, attnum, key, - NULL, nitem + GinGetNPosting(old), - false); + OffsetNumber attnum; + Datum key; + GinNullCategory category; + IndexTuple res; + + Assert(!GinIsPostingTree(old)); + + attnum = gintuple_get_attrnum(ginstate, old); + key = gintuple_get_key(ginstate, old, &category); + + /* try to build tuple with room for all the items */ + res = GinFormTuple(ginstate, attnum, key, category, + NULL, nitem + GinGetNPosting(old), + false); if (res) { /* good, small enough */ uint32 newnitem; + /* fill in the posting list with union of old and new TIDs */ newnitem = ginMergeItemPointers(GinGetPosting(res), GinGetPosting(old), GinGetNPosting(old), @@ -124,38 +136,115 @@ addItemPointersToTuple(Relation index, GinState *ginstate, } else { + /* posting list would be too big, convert to posting tree */ BlockNumber postingRoot; GinPostingTreeScan *gdi; - /* posting list becomes big, so we need to make posting's tree */ - res = GinFormTuple(index, ginstate, attnum, key, NULL, 0, true); - postingRoot = createPostingTree(index, GinGetPosting(old), GinGetNPosting(old)); - GinSetPostingTree(res, postingRoot); + /* + * Initialize posting tree with the old tuple's posting list. It's + * surely small enough to fit on one posting-tree page, and should + * already be in order with no duplicates. + */ + postingRoot = createPostingTree(ginstate->index, + GinGetPosting(old), + GinGetNPosting(old)); + + /* During index build, count the newly-added data page */ + if (buildStats) + buildStats->nDataPages++; - gdi = ginPrepareScanPostingTree(index, postingRoot, FALSE); + /* Now insert the TIDs-to-be-added into the posting tree */ + gdi = ginPrepareScanPostingTree(ginstate->index, postingRoot, FALSE); gdi->btree.isBuild = (buildStats != NULL); - ginInsertItemPointer(gdi, items, nitem, buildStats); + ginInsertItemPointers(gdi, items, nitem, buildStats); pfree(gdi); + /* And build a new posting-tree-only result tuple */ + res = GinFormTuple(ginstate, attnum, key, category, NULL, 0, true); + GinSetPostingTree(res, postingRoot); + } + + return res; +} + +/* + * Build a fresh leaf tuple, either posting-list or posting-tree format + * depending on whether the given items list will fit. + * items[] must be in sorted order with no duplicates. + * + * This is basically the same logic as in addItemPointersToLeafTuple, + * but working from slightly different input. + */ +static IndexTuple +buildFreshLeafTuple(GinState *ginstate, + OffsetNumber attnum, Datum key, GinNullCategory category, + ItemPointerData *items, uint32 nitem, + GinStatsData *buildStats) +{ + IndexTuple res; + + /* try to build tuple with room for all the items */ + res = GinFormTuple(ginstate, attnum, key, category, + items, nitem, false); + + if (!res) + { + /* posting list would be too big, build posting tree */ + BlockNumber postingRoot; + + /* + * Build posting-tree-only result tuple. We do this first so as + * to fail quickly if the key is too big. + */ + res = GinFormTuple(ginstate, attnum, key, category, NULL, 0, true); + + /* + * Initialize posting tree with as many TIDs as will fit on the + * first page. + */ + postingRoot = createPostingTree(ginstate->index, + items, + Min(nitem, GinMaxLeafDataItems)); + /* During index build, count the newly-added data page */ if (buildStats) buildStats->nDataPages++; + + /* Add any remaining TIDs to the posting tree */ + if (nitem > GinMaxLeafDataItems) + { + GinPostingTreeScan *gdi; + + gdi = ginPrepareScanPostingTree(ginstate->index, postingRoot, FALSE); + gdi->btree.isBuild = (buildStats != NULL); + + ginInsertItemPointers(gdi, + items + GinMaxLeafDataItems, + nitem - GinMaxLeafDataItems, + buildStats); + + pfree(gdi); + } + + /* And save the root link in the result tuple */ + GinSetPostingTree(res, postingRoot); } return res; } /* - * Inserts only one entry to the index, but it can add more than 1 ItemPointer. + * Insert one or more heap TIDs associated with the given key value. + * This will either add a single key entry, or enlarge a pre-existing entry. * * During an index build, buildStats is non-null and the counters * it contains should be incremented as needed. */ void -ginEntryInsert(Relation index, GinState *ginstate, - OffsetNumber attnum, Datum value, +ginEntryInsert(GinState *ginstate, + OffsetNumber attnum, Datum key, GinNullCategory category, ItemPointerData *items, uint32 nitem, GinStatsData *buildStats) { @@ -168,85 +257,82 @@ ginEntryInsert(Relation index, GinState *ginstate, if (buildStats) buildStats->nEntries++; - ginPrepareEntryScan(&btree, index, attnum, value, ginstate); + ginPrepareEntryScan(&btree, attnum, key, category, ginstate); stack = ginFindLeafPage(&btree, NULL); page = BufferGetPage(stack->buffer); if (btree.findItem(&btree, stack)) { - /* found entry */ + /* found pre-existing entry */ itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off)); if (GinIsPostingTree(itup)) { - /* lock root of posting tree */ - GinPostingTreeScan *gdi; + /* add entries to existing posting tree */ BlockNumber rootPostingTree = GinGetPostingTree(itup); + GinPostingTreeScan *gdi; /* release all stack */ LockBuffer(stack->buffer, GIN_UNLOCK); freeGinBtreeStack(stack); /* insert into posting tree */ - gdi = ginPrepareScanPostingTree(index, rootPostingTree, FALSE); + gdi = ginPrepareScanPostingTree(ginstate->index, rootPostingTree, FALSE); gdi->btree.isBuild = (buildStats != NULL); - ginInsertItemPointer(gdi, items, nitem, buildStats); + ginInsertItemPointers(gdi, items, nitem, buildStats); pfree(gdi); return; } - itup = addItemPointersToTuple(index, ginstate, stack, itup, - items, nitem, buildStats); + /* modify an existing leaf entry */ + itup = addItemPointersToLeafTuple(ginstate, itup, + items, nitem, buildStats); btree.isDelete = TRUE; } else { - /* We suppose that tuple can store at least one itempointer */ - itup = GinFormTuple(index, ginstate, attnum, value, items, 1, true); - - if (nitem > 1) - { - /* Add the rest, making a posting tree if necessary */ - IndexTuple previtup = itup; - - itup = addItemPointersToTuple(index, ginstate, stack, previtup, - items + 1, nitem - 1, buildStats); - pfree(previtup); - } + /* no match, so construct a new leaf entry */ + itup = buildFreshLeafTuple(ginstate, attnum, key, category, + items, nitem, buildStats); } + /* Insert the new or modified leaf tuple */ btree.entry = itup; ginInsertValue(&btree, stack, buildStats); pfree(itup); } /* - * Saves indexed value in memory accumulator during index creation - * Function isn't used during normal insert + * Extract index entries for a single indexable item, and add them to the + * BuildAccumulator's state. + * + * This function is used only during initial index creation. */ -static uint32 -ginHeapTupleBulkInsert(GinBuildState *buildstate, OffsetNumber attnum, Datum value, ItemPointer heapptr) +static void +ginHeapTupleBulkInsert(GinBuildState *buildstate, OffsetNumber attnum, + Datum value, bool isNull, + ItemPointer heapptr) { Datum *entries; + GinNullCategory *categories; int32 nentries; MemoryContext oldCtx; oldCtx = MemoryContextSwitchTo(buildstate->funcCtx); - entries = ginExtractEntriesSU(buildstate->accum.ginstate, attnum, value, &nentries); + entries = ginExtractEntries(buildstate->accum.ginstate, attnum, + value, isNull, + &nentries, &categories); MemoryContextSwitchTo(oldCtx); - if (nentries == 0) - /* nothing to insert */ - return 0; + ginInsertBAEntries(&buildstate->accum, heapptr, attnum, + entries, categories, nentries); - ginInsertRecordBA(&buildstate->accum, heapptr, attnum, entries, nentries); + buildstate->indtuples += nentries; MemoryContextReset(buildstate->funcCtx); - - return nentries; } static void @@ -260,25 +346,26 @@ ginBuildCallback(Relation index, HeapTuple htup, Datum *values, oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); for (i = 0; i < buildstate->ginstate.origTupdesc->natts; i++) - if (!isnull[i]) - buildstate->indtuples += ginHeapTupleBulkInsert(buildstate, - (OffsetNumber) (i + 1), values[i], - &htup->t_self); + ginHeapTupleBulkInsert(buildstate, (OffsetNumber) (i + 1), + values[i], isnull[i], + &htup->t_self); /* If we've maxed out our available memory, dump everything to the index */ if (buildstate->accum.allocatedMemory >= maintenance_work_mem * 1024L) { ItemPointerData *list; - Datum entry; + Datum key; + GinNullCategory category; uint32 nlist; OffsetNumber attnum; ginBeginBAScan(&buildstate->accum); - while ((list = ginGetEntry(&buildstate->accum, &attnum, &entry, &nlist)) != NULL) + while ((list = ginGetBAEntry(&buildstate->accum, + &attnum, &key, &category, &nlist)) != NULL) { /* there could be many entries, so be willing to abort here */ CHECK_FOR_INTERRUPTS(); - ginEntryInsert(index, &buildstate->ginstate, attnum, entry, + ginEntryInsert(&buildstate->ginstate, attnum, key, category, list, nlist, &buildstate->buildStats); } @@ -301,7 +388,8 @@ ginbuild(PG_FUNCTION_ARGS) Buffer RootBuffer, MetaBuffer; ItemPointerData *list; - Datum entry; + Datum key; + GinNullCategory category; uint32 nlist; MemoryContext oldCtx; OffsetNumber attnum; @@ -384,11 +472,12 @@ ginbuild(PG_FUNCTION_ARGS) /* dump remaining entries to the index */ oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx); ginBeginBAScan(&buildstate.accum); - while ((list = ginGetEntry(&buildstate.accum, &attnum, &entry, &nlist)) != NULL) + while ((list = ginGetBAEntry(&buildstate.accum, + &attnum, &key, &category, &nlist)) != NULL) { /* there could be many entries, so be willing to abort here */ CHECK_FOR_INTERRUPTS(); - ginEntryInsert(index, &buildstate.ginstate, attnum, entry, + ginEntryInsert(&buildstate.ginstate, attnum, key, category, list, nlist, &buildstate.buildStats); } MemoryContextSwitchTo(oldCtx); @@ -454,25 +543,25 @@ ginbuildempty(PG_FUNCTION_ARGS) } /* - * Inserts value during normal insertion + * Insert index entries for a single indexable item during "normal" + * (non-fast-update) insertion */ -static uint32 -ginHeapTupleInsert(Relation index, GinState *ginstate, OffsetNumber attnum, Datum value, ItemPointer item) +static void +ginHeapTupleInsert(GinState *ginstate, OffsetNumber attnum, + Datum value, bool isNull, + ItemPointer item) { Datum *entries; + GinNullCategory *categories; int32 i, nentries; - entries = ginExtractEntriesSU(ginstate, attnum, value, &nentries); - - if (nentries == 0) - /* nothing to insert */ - return 0; + entries = ginExtractEntries(ginstate, attnum, value, isNull, + &nentries, &categories); for (i = 0; i < nentries; i++) - ginEntryInsert(index, ginstate, attnum, entries[i], item, 1, NULL); - - return nentries; + ginEntryInsert(ginstate, attnum, entries[i], categories[i], + item, 1, NULL); } Datum @@ -507,20 +596,21 @@ gininsert(PG_FUNCTION_ARGS) GinTupleCollector collector; memset(&collector, 0, sizeof(GinTupleCollector)); + for (i = 0; i < ginstate.origTupdesc->natts; i++) - if (!isnull[i]) - ginHeapTupleFastCollect(index, &ginstate, &collector, - (OffsetNumber) (i + 1), values[i], ht_ctid); + ginHeapTupleFastCollect(&ginstate, &collector, + (OffsetNumber) (i + 1), + values[i], isnull[i], + ht_ctid); - ginHeapTupleFastInsert(index, &ginstate, &collector); + ginHeapTupleFastInsert(&ginstate, &collector); } else { for (i = 0; i < ginstate.origTupdesc->natts; i++) - if (!isnull[i]) - ginHeapTupleInsert(index, &ginstate, - (OffsetNumber) (i + 1), values[i], ht_ctid); - + ginHeapTupleInsert(&ginstate, (OffsetNumber) (i + 1), + values[i], isnull[i], + ht_ctid); } MemoryContextSwitchTo(oldCtx); diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c index 2a39c4b3834..c9cf77514a1 100644 --- a/src/backend/access/gin/ginscan.c +++ b/src/backend/access/gin/ginscan.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * ginscan.c - * routines to manage scans inverted index relations + * routines to manage scans of inverted index relations * * * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group @@ -14,7 +14,7 @@ #include "postgres.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "access/relscan.h" #include "pgstat.h" #include "storage/bufmgr.h" @@ -52,54 +52,117 @@ ginbeginscan(PG_FUNCTION_ARGS) PG_RETURN_POINTER(scan); } +/* + * Initialize a GinScanKey using the output from the extractQueryFn + */ static void -fillScanKey(GinState *ginstate, GinScanKey key, OffsetNumber attnum, Datum query, - Datum *entryValues, bool *partial_matches, uint32 nEntryValues, - StrategyNumber strategy, Pointer *extra_data) +ginFillScanKey(GinState *ginstate, GinScanKey key, + OffsetNumber attnum, Datum query, + Datum *queryValues, GinNullCategory *queryCategories, + bool *partial_matches, uint32 nQueryValues, + StrategyNumber strategy, Pointer *extra_data, + int32 searchMode) { + uint32 nUserQueryValues = nQueryValues; uint32 i, j; - key->nentries = nEntryValues; - key->entryRes = (bool *) palloc0(sizeof(bool) * nEntryValues); - key->scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData) * nEntryValues); + /* Non-default search modes add one "hidden" entry to each key */ + if (searchMode != GIN_SEARCH_MODE_DEFAULT) + nQueryValues++; + key->nentries = nQueryValues; + key->nuserentries = nUserQueryValues; + + key->scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData) * nQueryValues); + key->entryRes = (bool *) palloc0(sizeof(bool) * nQueryValues); + key->query = query; + key->queryValues = queryValues; + key->queryCategories = queryCategories; + key->extra_data = extra_data; key->strategy = strategy; + key->searchMode = searchMode; key->attnum = attnum; - key->extra_data = extra_data; - key->query = query; + key->firstCall = TRUE; ItemPointerSetMin(&key->curItem); - for (i = 0; i < nEntryValues; i++) + for (i = 0; i < nQueryValues; i++) { - key->scanEntry[i].pval = key->entryRes + i; - key->scanEntry[i].entry = entryValues[i]; - key->scanEntry[i].attnum = attnum; - key->scanEntry[i].extra_data = (extra_data) ? extra_data[i] : NULL; - ItemPointerSetMin(&key->scanEntry[i].curItem); - key->scanEntry[i].isFinished = FALSE; - key->scanEntry[i].offset = InvalidOffsetNumber; - key->scanEntry[i].buffer = InvalidBuffer; - key->scanEntry[i].partialMatch = NULL; - key->scanEntry[i].partialMatchIterator = NULL; - key->scanEntry[i].partialMatchResult = NULL; - key->scanEntry[i].strategy = strategy; - key->scanEntry[i].list = NULL; - key->scanEntry[i].nlist = 0; - key->scanEntry[i].isPartialMatch = (ginstate->canPartialMatch[attnum - 1] && partial_matches) - ? partial_matches[i] : false; - - /* link to the equals entry in current scan key */ - key->scanEntry[i].master = NULL; - for (j = 0; j < i; j++) - if (ginCompareEntries(ginstate, attnum, - entryValues[i], entryValues[j]) == 0 && - key->scanEntry[i].isPartialMatch == key->scanEntry[j].isPartialMatch && - key->scanEntry[i].strategy == key->scanEntry[j].strategy) + GinScanEntry scanEntry = key->scanEntry + i; + + scanEntry->pval = key->entryRes + i; + if (i < nUserQueryValues) + { + scanEntry->queryKey = queryValues[i]; + scanEntry->queryCategory = queryCategories[i]; + scanEntry->isPartialMatch = + (ginstate->canPartialMatch[attnum - 1] && partial_matches) + ? partial_matches[i] : false; + scanEntry->extra_data = (extra_data) ? extra_data[i] : NULL; + } + else + { + /* set up hidden entry */ + scanEntry->queryKey = (Datum) 0; + switch (searchMode) { - key->scanEntry[i].master = key->scanEntry + j; - break; + case GIN_SEARCH_MODE_INCLUDE_EMPTY: + scanEntry->queryCategory = GIN_CAT_EMPTY_ITEM; + break; + case GIN_SEARCH_MODE_ALL: + scanEntry->queryCategory = GIN_CAT_EMPTY_QUERY; + break; + case GIN_SEARCH_MODE_EVERYTHING: + scanEntry->queryCategory = GIN_CAT_EMPTY_QUERY; + break; + default: + elog(ERROR, "unexpected searchMode: %d", searchMode); + break; } + scanEntry->isPartialMatch = false; + scanEntry->extra_data = NULL; + } + scanEntry->strategy = strategy; + scanEntry->searchMode = searchMode; + scanEntry->attnum = attnum; + + ItemPointerSetMin(&scanEntry->curItem); + scanEntry->isFinished = FALSE; + scanEntry->offset = InvalidOffsetNumber; + scanEntry->buffer = InvalidBuffer; + scanEntry->list = NULL; + scanEntry->nlist = 0; + scanEntry->matchBitmap = NULL; + scanEntry->matchIterator = NULL; + scanEntry->matchResult = NULL; + + /* + * Link to any preceding identical entry in current scan key. + * + * Entries with non-null extra_data are never considered identical, + * since we can't know exactly what the opclass might be doing with + * that. + */ + scanEntry->master = NULL; + if (scanEntry->extra_data == NULL) + { + for (j = 0; j < i; j++) + { + GinScanEntry prevEntry = key->scanEntry + j; + + if (prevEntry->extra_data == NULL && + scanEntry->isPartialMatch == prevEntry->isPartialMatch && + ginCompareEntries(ginstate, attnum, + scanEntry->queryKey, + scanEntry->queryCategory, + prevEntry->queryKey, + prevEntry->queryCategory) == 0) + { + scanEntry->master = prevEntry; + break; + } + } + } } } @@ -132,9 +195,9 @@ resetScanKeys(GinScanKey keys, uint32 nkeys) key->scanEntry[j].buffer = InvalidBuffer; key->scanEntry[j].list = NULL; key->scanEntry[j].nlist = 0; - key->scanEntry[j].partialMatch = NULL; - key->scanEntry[j].partialMatchIterator = NULL; - key->scanEntry[j].partialMatchResult = NULL; + key->scanEntry[j].matchBitmap = NULL; + key->scanEntry[j].matchIterator = NULL; + key->scanEntry[j].matchResult = NULL; } } } @@ -159,10 +222,10 @@ freeScanKeys(GinScanKey keys, uint32 nkeys) ReleaseBuffer(key->scanEntry[j].buffer); if (key->scanEntry[j].list) pfree(key->scanEntry[j].list); - if (key->scanEntry[j].partialMatchIterator) - tbm_end_iterate(key->scanEntry[j].partialMatchIterator); - if (key->scanEntry[j].partialMatch) - tbm_free(key->scanEntry[j].partialMatch); + if (key->scanEntry[j].matchIterator) + tbm_end_iterate(key->scanEntry[j].matchIterator); + if (key->scanEntry[j].matchBitmap) + tbm_free(key->scanEntry[j].matchBitmap); } pfree(key->entryRes); @@ -179,27 +242,27 @@ ginNewScanKey(IndexScanDesc scan) GinScanOpaque so = (GinScanOpaque) scan->opaque; int i; uint32 nkeys = 0; + bool hasNullQuery = false; - if (scan->numberOfKeys < 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("GIN indexes do not support whole-index scans"))); - - so->keys = (GinScanKey) palloc(scan->numberOfKeys * sizeof(GinScanKeyData)); + /* if no scan keys provided, allocate extra EVERYTHING GinScanKey */ + so->keys = (GinScanKey) + palloc(Max(scan->numberOfKeys, 1) * sizeof(GinScanKeyData)); so->isVoidRes = false; for (i = 0; i < scan->numberOfKeys; i++) { ScanKey skey = &scankey[i]; - Datum *entryValues; - int32 nEntryValues = 0; + Datum *queryValues; + int32 nQueryValues = 0; bool *partial_matches = NULL; Pointer *extra_data = NULL; + bool *nullFlags = NULL; + int32 searchMode = GIN_SEARCH_MODE_DEFAULT; /* - * Assume, that GIN-indexable operators are strict, so nothing could - * be found + * We assume that GIN-indexable operators are strict, so a null + * query argument means an unsatisfiable query. */ if (skey->sk_flags & SK_ISNULL) { @@ -207,46 +270,106 @@ ginNewScanKey(IndexScanDesc scan) break; } - entryValues = (Datum *) - DatumGetPointer(FunctionCall5(&so->ginstate.extractQueryFn[skey->sk_attno - 1], + /* OK to call the extractQueryFn */ + queryValues = (Datum *) + DatumGetPointer(FunctionCall7(&so->ginstate.extractQueryFn[skey->sk_attno - 1], skey->sk_argument, - PointerGetDatum(&nEntryValues), + PointerGetDatum(&nQueryValues), UInt16GetDatum(skey->sk_strategy), PointerGetDatum(&partial_matches), - PointerGetDatum(&extra_data))); + PointerGetDatum(&extra_data), + PointerGetDatum(&nullFlags), + PointerGetDatum(&searchMode))); - if (nEntryValues < 0) + /* + * If bogus searchMode is returned, treat as GIN_SEARCH_MODE_ALL; + * note in particular we don't allow extractQueryFn to select + * GIN_SEARCH_MODE_EVERYTHING. + */ + if (searchMode < GIN_SEARCH_MODE_DEFAULT || + searchMode > GIN_SEARCH_MODE_ALL) + searchMode = GIN_SEARCH_MODE_ALL; + + /* Non-default modes require the index to have placeholders */ + if (searchMode != GIN_SEARCH_MODE_DEFAULT) + hasNullQuery = true; + + /* + * In default mode, no keys means an unsatisfiable query. + */ + if (queryValues == NULL || nQueryValues <= 0) { - /* - * extractQueryFn signals that nothing can match, so we can just - * set isVoidRes flag. No need to examine any more keys. - */ - so->isVoidRes = true; - break; + if (searchMode == GIN_SEARCH_MODE_DEFAULT) + { + so->isVoidRes = true; + break; + } + nQueryValues = 0; /* ensure sane value */ } - if (entryValues == NULL || nEntryValues == 0) + /* + * If the extractQueryFn didn't create a nullFlags array, create one, + * assuming that everything's non-null. Otherwise, run through the + * array and make sure each value is exactly 0 or 1; this ensures + * binary compatibility with the GinNullCategory representation. + * While at it, detect whether any null keys are present. + */ + if (nullFlags == NULL) + nullFlags = (bool *) palloc0(nQueryValues * sizeof(bool)); + else { - /* - * extractQueryFn signals that everything matches. This would - * require a full scan, which we can't do, but perhaps there is - * another scankey that provides a restriction to use. So we keep - * going and check only at the end. - */ - continue; + int32 j; + + for (j = 0; j < nQueryValues; j++) + { + if (nullFlags[j]) + { + nullFlags[j] = true; /* not any other nonzero value */ + hasNullQuery = true; + } + } } + /* now we can use the nullFlags as category codes */ - fillScanKey(&so->ginstate, &(so->keys[nkeys]), - skey->sk_attno, skey->sk_argument, - entryValues, partial_matches, nEntryValues, - skey->sk_strategy, extra_data); + ginFillScanKey(&so->ginstate, &(so->keys[nkeys]), + skey->sk_attno, skey->sk_argument, + queryValues, (GinNullCategory *) nullFlags, + partial_matches, nQueryValues, + skey->sk_strategy, extra_data, searchMode); nkeys++; } + /* + * If there are no regular scan keys, generate an EVERYTHING scankey to + * drive a full-index scan. + */ if (nkeys == 0 && !so->isVoidRes) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("GIN indexes do not support whole-index scans"))); + { + hasNullQuery = true; + ginFillScanKey(&so->ginstate, &(so->keys[nkeys]), + FirstOffsetNumber, (Datum) 0, + NULL, NULL, NULL, 0, + InvalidStrategy, NULL, GIN_SEARCH_MODE_EVERYTHING); + nkeys++; + } + + /* + * If the index is version 0, it may be missing null and placeholder + * entries, which would render searches for nulls and full-index scans + * unreliable. Throw an error if so. + */ + if (hasNullQuery && !so->isVoidRes) + { + GinStatsData ginStats; + + ginGetStats(scan->indexRelation, &ginStats); + if (ginStats.ginVersion < 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("old GIN indexes do not support whole-index scans nor searches for nulls"), + errhint("To fix this, do REINDEX INDEX \"%s\".", + RelationGetRelationName(scan->indexRelation)))); + } so->nkeys = nkeys; diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 4674606f798..0a7c1c521fc 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -14,8 +14,7 @@ #include "postgres.h" -#include "access/genam.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "access/reloptions.h" #include "catalog/pg_type.h" #include "miscadmin.h" @@ -24,26 +23,39 @@ #include "storage/indexfsm.h" #include "storage/lmgr.h" + +/* + * initGinState: fill in an empty GinState struct to describe the index + * + * Note: assorted subsidiary data is allocated in the CurrentMemoryContext. + */ void initGinState(GinState *state, Relation index) { + TupleDesc origTupdesc = RelationGetDescr(index); int i; - state->origTupdesc = index->rd_att; + MemSet(state, 0, sizeof(GinState)); - state->oneCol = (index->rd_att->natts == 1) ? true : false; + state->index = index; + state->oneCol = (origTupdesc->natts == 1) ? true : false; + state->origTupdesc = origTupdesc; - for (i = 0; i < index->rd_att->natts; i++) + for (i = 0; i < origTupdesc->natts; i++) { - state->tupdesc[i] = CreateTemplateTupleDesc(2, false); - - TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL, - INT2OID, -1, 0); - TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL, - index->rd_att->attrs[i]->atttypid, - index->rd_att->attrs[i]->atttypmod, - index->rd_att->attrs[i]->attndims - ); + if (state->oneCol) + state->tupdesc[i] = state->origTupdesc; + else + { + state->tupdesc[i] = CreateTemplateTupleDesc(2, false); + + TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL, + INT2OID, -1, 0); + TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL, + origTupdesc->attrs[i]->atttypid, + origTupdesc->attrs[i]->atttypmod, + origTupdesc->attrs[i]->attndims); + } fmgr_info_copy(&(state->compareFn[i]), index_getprocinfo(index, i + 1, GIN_COMPARE_PROC), @@ -82,9 +94,14 @@ initGinState(GinState *state, Relation index) OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple) { - OffsetNumber colN = FirstOffsetNumber; + OffsetNumber colN; - if (!ginstate->oneCol) + if (ginstate->oneCol) + { + /* column number is not stored explicitly */ + colN = FirstOffsetNumber; + } + else { Datum res; bool isnull; @@ -105,13 +122,14 @@ gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple) } /* - * Extract stored datum from GIN tuple + * Extract stored datum (and possible null category) from GIN tuple */ Datum -gin_index_getattr(GinState *ginstate, IndexTuple tuple) +gintuple_get_key(GinState *ginstate, IndexTuple tuple, + GinNullCategory *category) { - bool isnull; Datum res; + bool isnull; if (ginstate->oneCol) { @@ -134,7 +152,10 @@ gin_index_getattr(GinState *ginstate, IndexTuple tuple) &isnull); } - Assert(!isnull); + if (isnull) + *category = GinGetNullCategory(tuple, ginstate); + else + *category = GIN_CAT_NORM_KEY; return res; } @@ -144,7 +165,6 @@ gin_index_getattr(GinState *ginstate, IndexTuple tuple) * The returned buffer is already pinned and exclusive-locked * Caller is responsible for initializing the page by calling GinInitBuffer */ - Buffer GinNewBuffer(Relation index) { @@ -233,96 +253,218 @@ GinInitMetabuffer(Buffer b) metadata->nEntryPages = 0; metadata->nDataPages = 0; metadata->nEntries = 0; + metadata->ginVersion = GIN_CURRENT_VERSION; } +/* + * Compare two keys of the same index column + */ int -ginCompareEntries(GinState *ginstate, OffsetNumber attnum, Datum a, Datum b) +ginCompareEntries(GinState *ginstate, OffsetNumber attnum, + Datum a, GinNullCategory categorya, + Datum b, GinNullCategory categoryb) { + /* if not of same null category, sort by that first */ + if (categorya != categoryb) + return (categorya < categoryb) ? -1 : 1; + + /* all null items in same category are equal */ + if (categorya != GIN_CAT_NORM_KEY) + return 0; + + /* both not null, so safe to call the compareFn */ return DatumGetInt32(FunctionCall2(&ginstate->compareFn[attnum - 1], a, b)); } +/* + * Compare two keys of possibly different index columns + */ int -ginCompareAttEntries(GinState *ginstate, OffsetNumber attnum_a, Datum a, - OffsetNumber attnum_b, Datum b) +ginCompareAttEntries(GinState *ginstate, + OffsetNumber attnuma, Datum a, GinNullCategory categorya, + OffsetNumber attnumb, Datum b, GinNullCategory categoryb) { - if (attnum_a == attnum_b) - return ginCompareEntries(ginstate, attnum_a, a, b); + /* attribute number is the first sort key */ + if (attnuma != attnumb) + return (attnuma < attnumb) ? -1 : 1; - return (attnum_a < attnum_b) ? -1 : 1; + return ginCompareEntries(ginstate, attnuma, a, categorya, b, categoryb); } + +/* + * Support for sorting key datums in ginExtractEntries + * + * Note: we only have to worry about null and not-null keys here; + * ginExtractEntries never generates more than one placeholder null, + * so it doesn't have to sort those. + */ +typedef struct +{ + Datum datum; + bool isnull; +} keyEntryData; + typedef struct { FmgrInfo *cmpDatumFunc; - bool *needUnique; -} cmpEntriesData; + bool haveDups; +} cmpEntriesArg; static int -cmpEntries(const Datum *a, const Datum *b, cmpEntriesData *arg) +cmpEntries(const void *a, const void *b, void *arg) { - int res = DatumGetInt32(FunctionCall2(arg->cmpDatumFunc, - *a, *b)); + const keyEntryData *aa = (const keyEntryData *) a; + const keyEntryData *bb = (const keyEntryData *) b; + cmpEntriesArg *data = (cmpEntriesArg *) arg; + int res; + if (aa->isnull) + { + if (bb->isnull) + res = 0; /* NULL "=" NULL */ + else + res = 1; /* NULL ">" not-NULL */ + } + else if (bb->isnull) + res = -1; /* not-NULL "<" NULL */ + else + res = DatumGetInt32(FunctionCall2(data->cmpDatumFunc, + aa->datum, bb->datum)); + + /* + * Detect if we have any duplicates. If there are equal keys, qsort + * must compare them at some point, else it wouldn't know whether one + * should go before or after the other. + */ if (res == 0) - *(arg->needUnique) = TRUE; + data->haveDups = true; return res; } + +/* + * Extract the index key values from an indexable item + * + * The resulting key values are sorted, and any duplicates are removed. + * This avoids generating redundant index entries. + */ Datum * -ginExtractEntriesS(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries, - bool *needUnique) +ginExtractEntries(GinState *ginstate, OffsetNumber attnum, + Datum value, bool isNull, + int32 *nentries, GinNullCategory **categories) { Datum *entries; - - entries = (Datum *) DatumGetPointer(FunctionCall2( - &ginstate->extractValueFn[attnum - 1], - value, - PointerGetDatum(nentries) - )); - - if (entries == NULL) - *nentries = 0; - - *needUnique = FALSE; - if (*nentries > 1) + bool *nullFlags; + int32 i; + + /* + * We don't call the extractValueFn on a null item. Instead generate a + * placeholder. + */ + if (isNull) { - cmpEntriesData arg; - - arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1]; - arg.needUnique = needUnique; - qsort_arg(entries, *nentries, sizeof(Datum), - (qsort_arg_comparator) cmpEntries, (void *) &arg); + *nentries = 1; + entries = (Datum *) palloc(sizeof(Datum)); + entries[0] = (Datum) 0; + *categories = (GinNullCategory *) palloc(sizeof(GinNullCategory)); + (*categories)[0] = GIN_CAT_NULL_ITEM; + return entries; } - return entries; -} - - -Datum * -ginExtractEntriesSU(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries) -{ - bool needUnique; - Datum *entries = ginExtractEntriesS(ginstate, attnum, value, nentries, - &needUnique); + /* OK, call the opclass's extractValueFn */ + nullFlags = NULL; /* in case extractValue doesn't set it */ + entries = (Datum *) + DatumGetPointer(FunctionCall3(&ginstate->extractValueFn[attnum - 1], + value, + PointerGetDatum(nentries), + PointerGetDatum(&nullFlags))); + + /* + * Generate a placeholder if the item contained no keys. + */ + if (entries == NULL || *nentries <= 0) + { + *nentries = 1; + entries = (Datum *) palloc(sizeof(Datum)); + entries[0] = (Datum) 0; + *categories = (GinNullCategory *) palloc(sizeof(GinNullCategory)); + (*categories)[0] = GIN_CAT_EMPTY_ITEM; + return entries; + } - if (needUnique) + /* + * If the extractValueFn didn't create a nullFlags array, create one, + * assuming that everything's non-null. Otherwise, run through the + * array and make sure each value is exactly 0 or 1; this ensures + * binary compatibility with the GinNullCategory representation. + */ + if (nullFlags == NULL) + nullFlags = (bool *) palloc0(*nentries * sizeof(bool)); + else + { + for (i = 0; i < *nentries; i++) + nullFlags[i] = (nullFlags[i] ? true : false); + } + /* now we can use the nullFlags as category codes */ + *categories = (GinNullCategory *) nullFlags; + + /* + * If there's more than one key, sort and unique-ify. + * + * XXX Using qsort here is notationally painful, and the overhead is + * pretty bad too. For small numbers of keys it'd likely be better to + * use a simple insertion sort. + */ + if (*nentries > 1) { - Datum *ptr, - *res; + keyEntryData *keydata; + cmpEntriesArg arg; - ptr = res = entries; + keydata = (keyEntryData *) palloc(*nentries * sizeof(keyEntryData)); + for (i = 0; i < *nentries; i++) + { + keydata[i].datum = entries[i]; + keydata[i].isnull = nullFlags[i]; + } + + arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1]; + arg.haveDups = false; + qsort_arg(keydata, *nentries, sizeof(keyEntryData), + cmpEntries, (void *) &arg); - while (ptr - entries < *nentries) + if (arg.haveDups) + { + /* there are duplicates, must get rid of 'em */ + int32 j; + + entries[0] = keydata[0].datum; + nullFlags[0] = keydata[0].isnull; + j = 1; + for (i = 1; i < *nentries; i++) + { + if (cmpEntries(&keydata[i-1], &keydata[i], &arg) != 0) + { + entries[j] = keydata[i].datum; + nullFlags[j] = keydata[i].isnull; + j++; + } + } + *nentries = j; + } + else { - if (ginCompareEntries(ginstate, attnum, *ptr, *res) != 0) - *(++res) = *ptr++; - else - ptr++; + /* easy, no duplicates */ + for (i = 0; i < *nentries; i++) + { + entries[i] = keydata[i].datum; + nullFlags[i] = keydata[i].isnull; + } } - *nentries = res + 1 - entries; + pfree(keydata); } return entries; @@ -361,7 +503,7 @@ ginoptions(PG_FUNCTION_ARGS) * Fetch index's statistical data into *stats * * Note: in the result, nPendingPages can be trusted to be up-to-date, - * but the other fields are as of the last VACUUM. + * as can ginVersion; but the other fields are as of the last VACUUM. */ void ginGetStats(Relation index, GinStatsData *stats) @@ -380,6 +522,7 @@ ginGetStats(Relation index, GinStatsData *stats) stats->nEntryPages = metadata->nEntryPages; stats->nDataPages = metadata->nDataPages; stats->nEntries = metadata->nEntries; + stats->ginVersion = metadata->ginVersion; UnlockReleaseBuffer(metabuffer); } @@ -387,7 +530,7 @@ ginGetStats(Relation index, GinStatsData *stats) /* * Write the given statistics to the index's metapage * - * Note: nPendingPages is *not* copied over + * Note: nPendingPages and ginVersion are *not* copied over */ void ginUpdateStats(Relation index, const GinStatsData *stats) diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c index 3054030a2c4..41ad382df0c 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -14,8 +14,7 @@ #include "postgres.h" -#include "access/genam.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "catalog/storage.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -190,7 +189,6 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, /* saves changes about deleted tuple ... */ if (oldMaxOff != newMaxOff) { - START_CRIT_SECTION(); if (newMaxOff > 0) @@ -519,7 +517,7 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3 * store posting tree's roots for further processing, we can't * vacuum it just now due to risk of deadlocks with scans/inserts */ - roots[*nroot] = GinItemPointerGetBlockNumber(&itup->t_tid); + roots[*nroot] = GinGetDownlink(itup); (*nroot)++; } else if (GinGetNPosting(itup) > 0) @@ -533,8 +531,9 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3 if (GinGetNPosting(itup) != newN) { - Datum value; OffsetNumber attnum; + Datum key; + GinNullCategory category; /* * Some ItemPointers was deleted, so we should remake our @@ -562,9 +561,9 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3 itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i)); } - value = gin_index_getattr(&gvs->ginstate, itup); attnum = gintuple_get_attrnum(&gvs->ginstate, itup); - itup = GinFormTuple(gvs->index, &gvs->ginstate, attnum, value, + key = gintuple_get_key(&gvs->ginstate, itup, &category); + itup = GinFormTuple(&gvs->ginstate, attnum, key, category, GinGetPosting(itup), newN, true); PageIndexTupleDelete(tmppage, i); @@ -606,7 +605,7 @@ ginbulkdelete(PG_FUNCTION_ARGS) /* Yes, so initialize stats to zeroes */ stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); /* and cleanup any pending inserts */ - ginInsertCleanup(index, &gvs.ginstate, true, stats); + ginInsertCleanup(&gvs.ginstate, true, stats); } /* we'll re-count the tuples each time */ @@ -642,7 +641,7 @@ ginbulkdelete(PG_FUNCTION_ARGS) Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber); itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber)); - blkno = GinItemPointerGetBlockNumber(&(itup)->t_tid); + blkno = GinGetDownlink(itup); Assert(blkno != InvalidBlockNumber); UnlockReleaseBuffer(buffer); @@ -719,7 +718,7 @@ ginvacuumcleanup(PG_FUNCTION_ARGS) if (IsAutoVacuumWorkerProcess()) { initGinState(&ginstate, index); - ginInsertCleanup(index, &ginstate, true, stats); + ginInsertCleanup(&ginstate, true, stats); } PG_RETURN_POINTER(stats); } @@ -732,7 +731,7 @@ ginvacuumcleanup(PG_FUNCTION_ARGS) { stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); initGinState(&ginstate, index); - ginInsertCleanup(index, &ginstate, true, stats); + ginInsertCleanup(&ginstate, true, stats); } memset(&idxStat, 0, sizeof(idxStat)); diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c index 36f0233baad..e410959b851 100644 --- a/src/backend/access/gin/ginxlog.c +++ b/src/backend/access/gin/ginxlog.c @@ -13,7 +13,7 @@ */ #include "postgres.h" -#include "access/gin.h" +#include "access/gin_private.h" #include "access/xlogutils.h" #include "storage/bufmgr.h" #include "utils/memutils.h" @@ -152,7 +152,7 @@ ginRedoInsert(XLogRecPtr lsn, XLogRecord *record) itup = (IndexTuple) (XLogRecGetData(record) + sizeof(ginxlogInsert)); forgetIncompleteSplit(data->node, - GinItemPointerGetBlockNumber(&itup->t_tid), + GinGetDownlink(itup), data->updateBlkno); } } @@ -213,7 +213,7 @@ ginRedoInsert(XLogRecPtr lsn, XLogRecord *record) Assert(!GinPageIsLeaf(page)); Assert(data->offset >= FirstOffsetNumber && data->offset <= PageGetMaxOffsetNumber(page)); itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, data->offset)); - ItemPointerSet(&itup->t_tid, data->updateBlkno, InvalidOffsetNumber); + GinSetDownlink(itup, data->updateBlkno); } if (data->isDelete) @@ -270,7 +270,7 @@ ginRedoSplit(XLogRecPtr lsn, XLogRecord *record) if (data->isData) { char *ptr = XLogRecGetData(record) + sizeof(ginxlogSplit); - Size sizeofitem = GinSizeOfItem(lpage); + Size sizeofitem = GinSizeOfDataPageItem(lpage); OffsetNumber i; ItemPointer bound; @@ -380,8 +380,9 @@ ginRedoVacuumPage(XLogRecPtr lsn, XLogRecord *record) { if (GinPageIsData(page)) { - memcpy(GinDataPageGetData(page), XLogRecGetData(record) + sizeof(ginxlogVacuumPage), - GinSizeOfItem(page) *data->nitem); + memcpy(GinDataPageGetData(page), + XLogRecGetData(record) + sizeof(ginxlogVacuumPage), + data->nitem * GinSizeOfDataPageItem(page)); GinPageGetOpaque(page)->maxoff = data->nitem; } else @@ -792,6 +793,7 @@ static void ginContinueSplit(ginIncompleteSplit *split) { GinBtreeData btree; + GinState ginstate; Relation reln; Buffer buffer; GinBtreeStack stack; @@ -813,7 +815,12 @@ ginContinueSplit(ginIncompleteSplit *split) if (split->rootBlkno == GIN_ROOT_BLKNO) { - ginPrepareEntryScan(&btree, reln, InvalidOffsetNumber, (Datum) 0, NULL); + MemSet(&ginstate, 0, sizeof(ginstate)); + ginstate.index = reln; + + ginPrepareEntryScan(&btree, + InvalidOffsetNumber, (Datum) 0, GIN_CAT_NULL_KEY, + &ginstate); btree.entry = ginPageGetLinkItup(buffer); } else diff --git a/src/include/access/gin.h b/src/include/access/gin.h index d07454af685..cf9603c7c44 100644 --- a/src/include/access/gin.h +++ b/src/include/access/gin.h @@ -1,6 +1,6 @@ /*-------------------------------------------------------------------------- * gin.h - * header file for postgres inverted index access method implementation. + * Public header file for Generalized Inverted Index access method. * * Copyright (c) 2006-2011, PostgreSQL Global Development Group * @@ -10,11 +10,9 @@ #ifndef GIN_H #define GIN_H -#include "access/genam.h" -#include "access/itup.h" #include "access/xlog.h" -#include "utils/rbtree.h" -#include "fmgr.h" +#include "storage/block.h" +#include "utils/relcache.h" /* @@ -28,349 +26,12 @@ #define GINNProcs 5 /* - * Page opaque data in a inverted index page. - * - * Note: GIN does not include a page ID word as do the other index types. - * This is OK because the opaque data is only 8 bytes and so can be reliably - * distinguished by size. Revisit this if the size ever increases. - */ -typedef struct GinPageOpaqueData -{ - BlockNumber rightlink; /* next page if any */ - OffsetNumber maxoff; /* number entries on GIN_DATA page; number of - * heap ItemPointer on GIN_DATA|GIN_LEAF page - * and number of records on GIN_DATA & - * ~GIN_LEAF page. On GIN_LIST page, number of - * heap tuples. */ - uint16 flags; /* see bit definitions below */ -} GinPageOpaqueData; - -typedef GinPageOpaqueData *GinPageOpaque; - -#define GIN_DATA (1 << 0) -#define GIN_LEAF (1 << 1) -#define GIN_DELETED (1 << 2) -#define GIN_META (1 << 3) -#define GIN_LIST (1 << 4) -#define GIN_LIST_FULLROW (1 << 5) /* makes sense only on GIN_LIST page */ - -/* Page numbers of fixed-location pages */ -#define GIN_METAPAGE_BLKNO (0) -#define GIN_ROOT_BLKNO (1) - -typedef struct GinMetaPageData -{ - /* - * Pointers to head and tail of pending list, which consists of GIN_LIST - * pages. These store fast-inserted entries that haven't yet been moved - * into the regular GIN structure. - */ - BlockNumber head; - BlockNumber tail; - - /* - * Free space in bytes in the pending list's tail page. - */ - uint32 tailFreeSize; - - /* - * We store both number of pages and number of heap tuples that are in the - * pending list. - */ - BlockNumber nPendingPages; - int64 nPendingHeapTuples; - - /* - * Statistics for planner use (accurate as of last VACUUM) - */ - BlockNumber nTotalPages; - BlockNumber nEntryPages; - BlockNumber nDataPages; - int64 nEntries; -} GinMetaPageData; - -#define GinPageGetMeta(p) \ - ((GinMetaPageData *) PageGetContents(p)) - -/* - * Works on page + * searchMode settings for extractQueryFn. */ -#define GinPageGetOpaque(page) ( (GinPageOpaque) PageGetSpecialPointer(page) ) - -#define GinPageIsLeaf(page) ( GinPageGetOpaque(page)->flags & GIN_LEAF ) -#define GinPageSetLeaf(page) ( GinPageGetOpaque(page)->flags |= GIN_LEAF ) -#define GinPageSetNonLeaf(page) ( GinPageGetOpaque(page)->flags &= ~GIN_LEAF ) -#define GinPageIsData(page) ( GinPageGetOpaque(page)->flags & GIN_DATA ) -#define GinPageSetData(page) ( GinPageGetOpaque(page)->flags |= GIN_DATA ) -#define GinPageIsList(page) ( GinPageGetOpaque(page)->flags & GIN_LIST ) -#define GinPageSetList(page) ( GinPageGetOpaque(page)->flags |= GIN_LIST ) -#define GinPageHasFullRow(page) ( GinPageGetOpaque(page)->flags & GIN_LIST_FULLROW ) -#define GinPageSetFullRow(page) ( GinPageGetOpaque(page)->flags |= GIN_LIST_FULLROW ) - -#define GinPageIsDeleted(page) ( GinPageGetOpaque(page)->flags & GIN_DELETED) -#define GinPageSetDeleted(page) ( GinPageGetOpaque(page)->flags |= GIN_DELETED) -#define GinPageSetNonDeleted(page) ( GinPageGetOpaque(page)->flags &= ~GIN_DELETED) - -#define GinPageRightMost(page) ( GinPageGetOpaque(page)->rightlink == InvalidBlockNumber) - -/* - * We use our own ItemPointerGet(BlockNumber|GetOffsetNumber) - * to avoid Asserts, since sometimes the ip_posid isn't "valid" - */ -#define GinItemPointerGetBlockNumber(pointer) \ - BlockIdGetBlockNumber(&(pointer)->ip_blkid) - -#define GinItemPointerGetOffsetNumber(pointer) \ - ((pointer)->ip_posid) - -/* - * Special-case item pointer values needed by the GIN search logic. - * MIN: sorts less than any valid item pointer - * MAX: sorts greater than any valid item pointer - * LOSSY PAGE: indicates a whole heap page, sorts after normal item - * pointers for that page - * Note that these are all distinguishable from an "invalid" item pointer - * (which is InvalidBlockNumber/0) as well as from all normal item - * pointers (which have item numbers in the range 1..MaxHeapTuplesPerPage). - */ -#define ItemPointerSetMin(p) \ - ItemPointerSet((p), (BlockNumber)0, (OffsetNumber)0) -#define ItemPointerIsMin(p) \ - (GinItemPointerGetOffsetNumber(p) == (OffsetNumber)0 && \ - GinItemPointerGetBlockNumber(p) == (BlockNumber)0) -#define ItemPointerSetMax(p) \ - ItemPointerSet((p), InvalidBlockNumber, (OffsetNumber)0xffff) -#define ItemPointerIsMax(p) \ - (GinItemPointerGetOffsetNumber(p) == (OffsetNumber)0xffff && \ - GinItemPointerGetBlockNumber(p) == InvalidBlockNumber) -#define ItemPointerSetLossyPage(p, b) \ - ItemPointerSet((p), (b), (OffsetNumber)0xffff) -#define ItemPointerIsLossyPage(p) \ - (GinItemPointerGetOffsetNumber(p) == (OffsetNumber)0xffff && \ - GinItemPointerGetBlockNumber(p) != InvalidBlockNumber) - -typedef struct -{ - BlockIdData child_blkno; /* use it instead of BlockNumber to save space - * on page */ - ItemPointerData key; -} PostingItem; - -#define PostingItemGetBlockNumber(pointer) \ - BlockIdGetBlockNumber(&(pointer)->child_blkno) - -#define PostingItemSetBlockNumber(pointer, blockNumber) \ - BlockIdSet(&((pointer)->child_blkno), (blockNumber)) - -/* - * Support work on IndexTuple on leaf pages - */ -#define GinGetNPosting(itup) GinItemPointerGetOffsetNumber(&(itup)->t_tid) -#define GinSetNPosting(itup,n) ItemPointerSetOffsetNumber(&(itup)->t_tid,(n)) -#define GIN_TREE_POSTING ((OffsetNumber)0xffff) -#define GinIsPostingTree(itup) ( GinGetNPosting(itup)==GIN_TREE_POSTING ) -#define GinSetPostingTree(itup, blkno) ( GinSetNPosting((itup),GIN_TREE_POSTING ), ItemPointerSetBlockNumber(&(itup)->t_tid, blkno) ) -#define GinGetPostingTree(itup) GinItemPointerGetBlockNumber(&(itup)->t_tid) - -#define GinGetOrigSizePosting(itup) GinItemPointerGetBlockNumber(&(itup)->t_tid) -#define GinSetOrigSizePosting(itup,n) ItemPointerSetBlockNumber(&(itup)->t_tid,(n)) -#define GinGetPosting(itup) ( (ItemPointer)(( ((char*)(itup)) + SHORTALIGN(GinGetOrigSizePosting(itup)) )) ) - -#define GinMaxItemSize \ - MAXALIGN_DOWN(((BLCKSZ - SizeOfPageHeaderData - \ - MAXALIGN(sizeof(GinPageOpaqueData))) / 3 - sizeof(ItemIdData))) - - -/* - * Data (posting tree) pages - */ -#define GinDataPageGetRightBound(page) ((ItemPointer) PageGetContents(page)) -#define GinDataPageGetData(page) \ - (PageGetContents(page) + MAXALIGN(sizeof(ItemPointerData))) -#define GinSizeOfItem(page) \ - (GinPageIsLeaf(page) ? sizeof(ItemPointerData) : sizeof(PostingItem)) -#define GinDataPageGetItem(page,i) \ - (GinDataPageGetData(page) + ((i)-1) * GinSizeOfItem(page)) - -#define GinDataPageGetFreeSpace(page) \ - (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) \ - - MAXALIGN(sizeof(ItemPointerData)) \ - - GinPageGetOpaque(page)->maxoff * GinSizeOfItem(page) \ - - MAXALIGN(sizeof(GinPageOpaqueData))) - -/* - * List pages - */ -#define GinListPageSize \ - ( BLCKSZ - SizeOfPageHeaderData - MAXALIGN(sizeof(GinPageOpaqueData)) ) - -/* - * Storage type for GIN's reloptions - */ -typedef struct GinOptions -{ - int32 vl_len_; /* varlena header (do not touch directly!) */ - bool useFastUpdate; /* use fast updates? */ -} GinOptions; - -#define GIN_DEFAULT_USE_FASTUPDATE true -#define GinGetUseFastUpdate(relation) \ - ((relation)->rd_options ? \ - ((GinOptions *) (relation)->rd_options)->useFastUpdate : GIN_DEFAULT_USE_FASTUPDATE) - - -#define GIN_UNLOCK BUFFER_LOCK_UNLOCK -#define GIN_SHARE BUFFER_LOCK_SHARE -#define GIN_EXCLUSIVE BUFFER_LOCK_EXCLUSIVE - -typedef struct GinState -{ - FmgrInfo compareFn[INDEX_MAX_KEYS]; - FmgrInfo extractValueFn[INDEX_MAX_KEYS]; - FmgrInfo extractQueryFn[INDEX_MAX_KEYS]; - FmgrInfo consistentFn[INDEX_MAX_KEYS]; - FmgrInfo comparePartialFn[INDEX_MAX_KEYS]; /* optional method */ - - bool canPartialMatch[INDEX_MAX_KEYS]; /* can opclass perform - * partial match (prefix - * search)? */ - - TupleDesc tupdesc[INDEX_MAX_KEYS]; - TupleDesc origTupdesc; - bool oneCol; -} GinState; - -/* XLog stuff */ - -#define XLOG_GIN_CREATE_INDEX 0x00 - -#define XLOG_GIN_CREATE_PTREE 0x10 - -typedef struct ginxlogCreatePostingTree -{ - RelFileNode node; - BlockNumber blkno; - uint32 nitem; - /* follows list of heap's ItemPointer */ -} ginxlogCreatePostingTree; - -#define XLOG_GIN_INSERT 0x20 - -typedef struct ginxlogInsert -{ - RelFileNode node; - BlockNumber blkno; - BlockNumber updateBlkno; - OffsetNumber offset; - bool isDelete; - bool isData; - bool isLeaf; - OffsetNumber nitem; - - /* - * follows: tuples or ItemPointerData or PostingItem or list of - * ItemPointerData - */ -} ginxlogInsert; - -#define XLOG_GIN_SPLIT 0x30 - -typedef struct ginxlogSplit -{ - RelFileNode node; - BlockNumber lblkno; - BlockNumber rootBlkno; - BlockNumber rblkno; - BlockNumber rrlink; - OffsetNumber separator; - OffsetNumber nitem; - - bool isData; - bool isLeaf; - bool isRootSplit; - - BlockNumber leftChildBlkno; - BlockNumber updateBlkno; - - ItemPointerData rightbound; /* used only in posting tree */ - /* follows: list of tuple or ItemPointerData or PostingItem */ -} ginxlogSplit; - -#define XLOG_GIN_VACUUM_PAGE 0x40 - -typedef struct ginxlogVacuumPage -{ - RelFileNode node; - BlockNumber blkno; - OffsetNumber nitem; - /* follows content of page */ -} ginxlogVacuumPage; - -#define XLOG_GIN_DELETE_PAGE 0x50 - -typedef struct ginxlogDeletePage -{ - RelFileNode node; - BlockNumber blkno; - BlockNumber parentBlkno; - OffsetNumber parentOffset; - BlockNumber leftBlkno; - BlockNumber rightLink; -} ginxlogDeletePage; - -#define XLOG_GIN_UPDATE_META_PAGE 0x60 - -typedef struct ginxlogUpdateMeta -{ - RelFileNode node; - GinMetaPageData metadata; - BlockNumber prevTail; - BlockNumber newRightlink; - int32 ntuples; /* if ntuples > 0 then metadata.tail was - * updated with that many tuples; else new sub - * list was inserted */ - /* array of inserted tuples follows */ -} ginxlogUpdateMeta; - -#define XLOG_GIN_INSERT_LISTPAGE 0x70 - -typedef struct ginxlogInsertListPage -{ - RelFileNode node; - BlockNumber blkno; - BlockNumber rightlink; - int32 ntuples; - /* array of inserted tuples follows */ -} ginxlogInsertListPage; - -#define XLOG_GIN_DELETE_LISTPAGE 0x80 - -#define GIN_NDELETE_AT_ONCE 16 -typedef struct ginxlogDeleteListPages -{ - RelFileNode node; - GinMetaPageData metadata; - int32 ndeleted; - BlockNumber toDelete[GIN_NDELETE_AT_ONCE]; -} ginxlogDeleteListPages; - - -/* ginutil.c */ -extern Datum ginoptions(PG_FUNCTION_ARGS); -extern void initGinState(GinState *state, Relation index); -extern Buffer GinNewBuffer(Relation index); -extern void GinInitBuffer(Buffer b, uint32 f); -extern void GinInitPage(Page page, uint32 f, Size pageSize); -extern void GinInitMetabuffer(Buffer b); -extern int ginCompareEntries(GinState *ginstate, OffsetNumber attnum, Datum a, Datum b); -extern int ginCompareAttEntries(GinState *ginstate, OffsetNumber attnum_a, Datum a, - OffsetNumber attnum_b, Datum b); -extern Datum *ginExtractEntriesS(GinState *ginstate, OffsetNumber attnum, Datum value, - int32 *nentries, bool *needUnique); -extern Datum *ginExtractEntriesSU(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries); - -extern Datum gin_index_getattr(GinState *ginstate, IndexTuple tuple); -extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple); +#define GIN_SEARCH_MODE_DEFAULT 0 +#define GIN_SEARCH_MODE_INCLUDE_EMPTY 1 +#define GIN_SEARCH_MODE_ALL 2 +#define GIN_SEARCH_MODE_EVERYTHING 3 /* for internal use only */ /* * GinStatsData represents stats data for planner use @@ -382,20 +43,16 @@ typedef struct GinStatsData BlockNumber nEntryPages; BlockNumber nDataPages; int64 nEntries; + int32 ginVersion; } GinStatsData; +/* GUC parameter */ +extern PGDLLIMPORT int GinFuzzySearchLimit; + +/* ginutil.c */ extern void ginGetStats(Relation index, GinStatsData *stats); extern void ginUpdateStats(Relation index, const GinStatsData *stats); -/* gininsert.c */ -extern Datum ginbuild(PG_FUNCTION_ARGS); -extern Datum ginbuildempty(PG_FUNCTION_ARGS); -extern Datum gininsert(PG_FUNCTION_ARGS); -extern void ginEntryInsert(Relation index, GinState *ginstate, - OffsetNumber attnum, Datum value, - ItemPointerData *items, uint32 nitem, - GinStatsData *buildStats); - /* ginxlog.c */ extern void gin_redo(XLogRecPtr lsn, XLogRecord *record); extern void gin_desc(StringInfo buf, uint8 xl_info, char *rec); @@ -403,247 +60,4 @@ extern void gin_xlog_startup(void); extern void gin_xlog_cleanup(void); extern bool gin_safe_restartpoint(void); -/* ginbtree.c */ - -typedef struct GinBtreeStack -{ - BlockNumber blkno; - Buffer buffer; - OffsetNumber off; - /* predictNumber contains prediction number of pages on current level */ - uint32 predictNumber; - struct GinBtreeStack *parent; -} GinBtreeStack; - -typedef struct GinBtreeData *GinBtree; - -typedef struct GinBtreeData -{ - /* search methods */ - BlockNumber (*findChildPage) (GinBtree, GinBtreeStack *); - bool (*isMoveRight) (GinBtree, Page); - bool (*findItem) (GinBtree, GinBtreeStack *); - - /* insert methods */ - OffsetNumber (*findChildPtr) (GinBtree, Page, BlockNumber, OffsetNumber); - BlockNumber (*getLeftMostPage) (GinBtree, Page); - bool (*isEnoughSpace) (GinBtree, Buffer, OffsetNumber); - void (*placeToPage) (GinBtree, Buffer, OffsetNumber, XLogRecData **); - Page (*splitPage) (GinBtree, Buffer, Buffer, OffsetNumber, XLogRecData **); - void (*fillRoot) (GinBtree, Buffer, Buffer, Buffer); - - bool isData; - bool searchMode; - - Relation index; - GinState *ginstate; - bool fullScan; - bool isBuild; - - BlockNumber rightblkno; - - /* Entry options */ - OffsetNumber entryAttnum; - Datum entryValue; - IndexTuple entry; - bool isDelete; - - /* Data (posting tree) option */ - ItemPointerData *items; - uint32 nitem; - uint32 curitem; - - PostingItem pitem; -} GinBtreeData; - -extern GinBtreeStack *ginPrepareFindLeafPage(GinBtree btree, BlockNumber blkno); -extern GinBtreeStack *ginFindLeafPage(GinBtree btree, GinBtreeStack *stack); -extern void freeGinBtreeStack(GinBtreeStack *stack); -extern void ginInsertValue(GinBtree btree, GinBtreeStack *stack, - GinStatsData *buildStats); -extern void ginFindParents(GinBtree btree, GinBtreeStack *stack, BlockNumber rootBlkno); - -/* ginentrypage.c */ -extern IndexTuple GinFormTuple(Relation index, GinState *ginstate, - OffsetNumber attnum, Datum key, - ItemPointerData *ipd, uint32 nipd, bool errorTooBig); -extern void GinShortenTuple(IndexTuple itup, uint32 nipd); -extern void ginPrepareEntryScan(GinBtree btree, Relation index, OffsetNumber attnum, - Datum value, GinState *ginstate); -extern void ginEntryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf); -extern IndexTuple ginPageGetLinkItup(Buffer buf); - -/* gindatapage.c */ -extern int ginCompareItemPointers(ItemPointer a, ItemPointer b); -extern uint32 ginMergeItemPointers(ItemPointerData *dst, - ItemPointerData *a, uint32 na, - ItemPointerData *b, uint32 nb); - -extern void GinDataPageAddItem(Page page, void *data, OffsetNumber offset); -extern void GinPageDeletePostingItem(Page page, OffsetNumber offset); - -typedef struct -{ - GinBtreeData btree; - GinBtreeStack *stack; -} GinPostingTreeScan; - -extern GinPostingTreeScan *ginPrepareScanPostingTree(Relation index, - BlockNumber rootBlkno, bool searchMode); -extern void ginInsertItemPointer(GinPostingTreeScan *gdi, - ItemPointerData *items, uint32 nitem, - GinStatsData *buildStats); -extern Buffer ginScanBeginPostingTree(GinPostingTreeScan *gdi); -extern void ginDataFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf); -extern void ginPrepareDataScan(GinBtree btree, Relation index); - -/* ginscan.c */ - -typedef struct GinScanEntryData *GinScanEntry; - -typedef struct GinScanEntryData -{ - /* link to the equals entry in current scan key */ - GinScanEntry master; - - /* - * link to values reported to consistentFn, points to - * GinScanKey->entryRes[i] - */ - bool *pval; - - /* entry, got from extractQueryFn */ - Datum entry; - OffsetNumber attnum; - Pointer extra_data; - - /* Current page in posting tree */ - Buffer buffer; - - /* current ItemPointer to heap */ - ItemPointerData curItem; - - /* partial match support */ - bool isPartialMatch; - TIDBitmap *partialMatch; - TBMIterator *partialMatchIterator; - TBMIterateResult *partialMatchResult; - StrategyNumber strategy; - - /* used for Posting list and one page in Posting tree */ - ItemPointerData *list; - uint32 nlist; - OffsetNumber offset; - - bool isFinished; - bool reduceResult; - uint32 predictNumberResult; -} GinScanEntryData; - -typedef struct GinScanKeyData -{ - /* Number of entries in query (got by extractQueryFn) */ - uint32 nentries; - - /* array of ItemPointer result, reported to consistentFn */ - bool *entryRes; - - /* array of scans per entry */ - GinScanEntry scanEntry; - Pointer *extra_data; - - /* for calling consistentFn(GinScanKey->entryRes, strategy, query) */ - StrategyNumber strategy; - Datum query; - OffsetNumber attnum; - - ItemPointerData curItem; - bool recheckCurItem; - - bool firstCall; - bool isFinished; -} GinScanKeyData; - -typedef GinScanKeyData *GinScanKey; - -typedef struct GinScanOpaqueData -{ - MemoryContext tempCtx; - GinState ginstate; - - GinScanKey keys; - uint32 nkeys; - bool isVoidRes; /* true if ginstate.extractQueryFn guarantees - * that nothing will be found */ -} GinScanOpaqueData; - -typedef GinScanOpaqueData *GinScanOpaque; - -extern Datum ginbeginscan(PG_FUNCTION_ARGS); -extern Datum ginendscan(PG_FUNCTION_ARGS); -extern Datum ginrescan(PG_FUNCTION_ARGS); -extern Datum ginmarkpos(PG_FUNCTION_ARGS); -extern Datum ginrestrpos(PG_FUNCTION_ARGS); -extern void ginNewScanKey(IndexScanDesc scan); - -/* ginget.c */ -extern PGDLLIMPORT int GinFuzzySearchLimit; - -extern Datum gingetbitmap(PG_FUNCTION_ARGS); - -/* ginvacuum.c */ -extern Datum ginbulkdelete(PG_FUNCTION_ARGS); -extern Datum ginvacuumcleanup(PG_FUNCTION_ARGS); - -/* ginarrayproc.c */ -extern Datum ginarrayextract(PG_FUNCTION_ARGS); -extern Datum ginqueryarrayextract(PG_FUNCTION_ARGS); -extern Datum ginarrayconsistent(PG_FUNCTION_ARGS); - -/* ginbulk.c */ -typedef struct EntryAccumulator -{ - RBNode rbnode; - Datum value; - uint32 length; - uint32 number; - OffsetNumber attnum; - bool shouldSort; - ItemPointerData *list; -} EntryAccumulator; - -typedef struct -{ - GinState *ginstate; - long allocatedMemory; - uint32 length; - EntryAccumulator *entryallocator; - RBTree *tree; -} BuildAccumulator; - -extern void ginInitBA(BuildAccumulator *accum); -extern void ginInsertRecordBA(BuildAccumulator *accum, - ItemPointer heapptr, - OffsetNumber attnum, Datum *entries, int32 nentry); -extern void ginBeginBAScan(BuildAccumulator *accum); -extern ItemPointerData *ginGetEntry(BuildAccumulator *accum, OffsetNumber *attnum, Datum *entry, uint32 *n); - -/* ginfast.c */ - -typedef struct GinTupleCollector -{ - IndexTuple *tuples; - uint32 ntuples; - uint32 lentuples; - uint32 sumsize; -} GinTupleCollector; - -extern void ginHeapTupleFastInsert(Relation index, GinState *ginstate, - GinTupleCollector *collector); -extern uint32 ginHeapTupleFastCollect(Relation index, GinState *ginstate, - GinTupleCollector *collector, - OffsetNumber attnum, Datum value, ItemPointer item); -extern void ginInsertCleanup(Relation index, GinState *ginstate, - bool vac_delay, IndexBulkDeleteResult *stats); - #endif /* GIN_H */ diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h new file mode 100644 index 00000000000..48531845e0a --- /dev/null +++ b/src/include/access/gin_private.h @@ -0,0 +1,710 @@ +/*-------------------------------------------------------------------------- + * gin_private.h + * header file for postgres inverted index access method implementation. + * + * Copyright (c) 2006-2011, PostgreSQL Global Development Group + * + * src/include/access/gin_private.h + *-------------------------------------------------------------------------- + */ +#ifndef GIN_PRIVATE_H +#define GIN_PRIVATE_H + +#include "access/genam.h" +#include "access/gin.h" +#include "access/itup.h" +#include "fmgr.h" +#include "utils/rbtree.h" + + +/* + * Page opaque data in a inverted index page. + * + * Note: GIN does not include a page ID word as do the other index types. + * This is OK because the opaque data is only 8 bytes and so can be reliably + * distinguished by size. Revisit this if the size ever increases. + */ +typedef struct GinPageOpaqueData +{ + BlockNumber rightlink; /* next page if any */ + OffsetNumber maxoff; /* number entries on GIN_DATA page: number of + * heap ItemPointers on GIN_DATA|GIN_LEAF page + * or number of PostingItems on GIN_DATA & + * ~GIN_LEAF page. On GIN_LIST page, number of + * heap tuples. */ + uint16 flags; /* see bit definitions below */ +} GinPageOpaqueData; + +typedef GinPageOpaqueData *GinPageOpaque; + +#define GIN_DATA (1 << 0) +#define GIN_LEAF (1 << 1) +#define GIN_DELETED (1 << 2) +#define GIN_META (1 << 3) +#define GIN_LIST (1 << 4) +#define GIN_LIST_FULLROW (1 << 5) /* makes sense only on GIN_LIST page */ + +/* Page numbers of fixed-location pages */ +#define GIN_METAPAGE_BLKNO (0) +#define GIN_ROOT_BLKNO (1) + +typedef struct GinMetaPageData +{ + /* + * Pointers to head and tail of pending list, which consists of GIN_LIST + * pages. These store fast-inserted entries that haven't yet been moved + * into the regular GIN structure. + */ + BlockNumber head; + BlockNumber tail; + + /* + * Free space in bytes in the pending list's tail page. + */ + uint32 tailFreeSize; + + /* + * We store both number of pages and number of heap tuples that are in the + * pending list. + */ + BlockNumber nPendingPages; + int64 nPendingHeapTuples; + + /* + * Statistics for planner use (accurate as of last VACUUM) + */ + BlockNumber nTotalPages; + BlockNumber nEntryPages; + BlockNumber nDataPages; + int64 nEntries; + + /* + * GIN version number (ideally this should have been at the front, but + * too late now. Don't move it!) + * + * Currently 1 (for indexes initialized in 9.1 or later) + * + * Version 0 (indexes initialized in 9.0 or before) is compatible but may + * be missing null entries, including both null keys and placeholders. + * Reject full-index-scan attempts on such indexes. + */ + int32 ginVersion; +} GinMetaPageData; + +#define GIN_CURRENT_VERSION 1 + +#define GinPageGetMeta(p) \ + ((GinMetaPageData *) PageGetContents(p)) + +/* + * Macros for accessing a GIN index page's opaque data + */ +#define GinPageGetOpaque(page) ( (GinPageOpaque) PageGetSpecialPointer(page) ) + +#define GinPageIsLeaf(page) ( GinPageGetOpaque(page)->flags & GIN_LEAF ) +#define GinPageSetLeaf(page) ( GinPageGetOpaque(page)->flags |= GIN_LEAF ) +#define GinPageSetNonLeaf(page) ( GinPageGetOpaque(page)->flags &= ~GIN_LEAF ) +#define GinPageIsData(page) ( GinPageGetOpaque(page)->flags & GIN_DATA ) +#define GinPageSetData(page) ( GinPageGetOpaque(page)->flags |= GIN_DATA ) +#define GinPageIsList(page) ( GinPageGetOpaque(page)->flags & GIN_LIST ) +#define GinPageSetList(page) ( GinPageGetOpaque(page)->flags |= GIN_LIST ) +#define GinPageHasFullRow(page) ( GinPageGetOpaque(page)->flags & GIN_LIST_FULLROW ) +#define GinPageSetFullRow(page) ( GinPageGetOpaque(page)->flags |= GIN_LIST_FULLROW ) + +#define GinPageIsDeleted(page) ( GinPageGetOpaque(page)->flags & GIN_DELETED) +#define GinPageSetDeleted(page) ( GinPageGetOpaque(page)->flags |= GIN_DELETED) +#define GinPageSetNonDeleted(page) ( GinPageGetOpaque(page)->flags &= ~GIN_DELETED) + +#define GinPageRightMost(page) ( GinPageGetOpaque(page)->rightlink == InvalidBlockNumber) + +/* + * We use our own ItemPointerGet(BlockNumber|GetOffsetNumber) + * to avoid Asserts, since sometimes the ip_posid isn't "valid" + */ +#define GinItemPointerGetBlockNumber(pointer) \ + BlockIdGetBlockNumber(&(pointer)->ip_blkid) + +#define GinItemPointerGetOffsetNumber(pointer) \ + ((pointer)->ip_posid) + +/* + * Special-case item pointer values needed by the GIN search logic. + * MIN: sorts less than any valid item pointer + * MAX: sorts greater than any valid item pointer + * LOSSY PAGE: indicates a whole heap page, sorts after normal item + * pointers for that page + * Note that these are all distinguishable from an "invalid" item pointer + * (which is InvalidBlockNumber/0) as well as from all normal item + * pointers (which have item numbers in the range 1..MaxHeapTuplesPerPage). + */ +#define ItemPointerSetMin(p) \ + ItemPointerSet((p), (BlockNumber)0, (OffsetNumber)0) +#define ItemPointerIsMin(p) \ + (GinItemPointerGetOffsetNumber(p) == (OffsetNumber)0 && \ + GinItemPointerGetBlockNumber(p) == (BlockNumber)0) +#define ItemPointerSetMax(p) \ + ItemPointerSet((p), InvalidBlockNumber, (OffsetNumber)0xffff) +#define ItemPointerIsMax(p) \ + (GinItemPointerGetOffsetNumber(p) == (OffsetNumber)0xffff && \ + GinItemPointerGetBlockNumber(p) == InvalidBlockNumber) +#define ItemPointerSetLossyPage(p, b) \ + ItemPointerSet((p), (b), (OffsetNumber)0xffff) +#define ItemPointerIsLossyPage(p) \ + (GinItemPointerGetOffsetNumber(p) == (OffsetNumber)0xffff && \ + GinItemPointerGetBlockNumber(p) != InvalidBlockNumber) + +/* + * Posting item in a non-leaf posting-tree page + */ +typedef struct +{ + /* We use BlockIdData not BlockNumber to avoid padding space wastage */ + BlockIdData child_blkno; + ItemPointerData key; +} PostingItem; + +#define PostingItemGetBlockNumber(pointer) \ + BlockIdGetBlockNumber(&(pointer)->child_blkno) + +#define PostingItemSetBlockNumber(pointer, blockNumber) \ + BlockIdSet(&((pointer)->child_blkno), (blockNumber)) + +/* + * Category codes to distinguish placeholder nulls from ordinary NULL keys. + * Note that the datatype size and the first two code values are chosen to be + * compatible with the usual usage of bool isNull flags. + * + * GIN_CAT_EMPTY_QUERY is never stored in the index; and notice that it is + * chosen to sort before not after regular key values. + */ +typedef signed char GinNullCategory; + +#define GIN_CAT_NORM_KEY 0 /* normal, non-null key value */ +#define GIN_CAT_NULL_KEY 1 /* null key value */ +#define GIN_CAT_EMPTY_ITEM 2 /* placeholder for zero-key item */ +#define GIN_CAT_NULL_ITEM 3 /* placeholder for null item */ +#define GIN_CAT_EMPTY_QUERY (-1) /* placeholder for full-scan query */ + +/* + * Access macros for null category byte in entry tuples + */ +#define GinCategoryOffset(itup,ginstate) \ + (IndexInfoFindDataOffset((itup)->t_info) + \ + ((ginstate)->oneCol ? 0 : sizeof(int2))) +#define GinGetNullCategory(itup,ginstate) \ + (*((GinNullCategory *) ((char*)(itup) + GinCategoryOffset(itup,ginstate)))) +#define GinSetNullCategory(itup,ginstate,c) \ + (*((GinNullCategory *) ((char*)(itup) + GinCategoryOffset(itup,ginstate))) = (c)) + +/* + * Access macros for leaf-page entry tuples (see discussion in README) + */ +#define GinGetNPosting(itup) GinItemPointerGetOffsetNumber(&(itup)->t_tid) +#define GinSetNPosting(itup,n) ItemPointerSetOffsetNumber(&(itup)->t_tid,n) +#define GIN_TREE_POSTING ((OffsetNumber)0xffff) +#define GinIsPostingTree(itup) (GinGetNPosting(itup) == GIN_TREE_POSTING) +#define GinSetPostingTree(itup, blkno) ( GinSetNPosting((itup),GIN_TREE_POSTING), ItemPointerSetBlockNumber(&(itup)->t_tid, blkno) ) +#define GinGetPostingTree(itup) GinItemPointerGetBlockNumber(&(itup)->t_tid) + +#define GinGetPostingOffset(itup) GinItemPointerGetBlockNumber(&(itup)->t_tid) +#define GinSetPostingOffset(itup,n) ItemPointerSetBlockNumber(&(itup)->t_tid,n) +#define GinGetPosting(itup) ((ItemPointer) ((char*)(itup) + GinGetPostingOffset(itup))) + +#define GinMaxItemSize \ + MAXALIGN_DOWN(((BLCKSZ - SizeOfPageHeaderData - \ + MAXALIGN(sizeof(GinPageOpaqueData))) / 3 - sizeof(ItemIdData))) + +/* + * Access macros for non-leaf entry tuples + */ +#define GinGetDownlink(itup) GinItemPointerGetBlockNumber(&(itup)->t_tid) +#define GinSetDownlink(itup,blkno) ItemPointerSet(&(itup)->t_tid, blkno, InvalidOffsetNumber) + + +/* + * Data (posting tree) pages + */ +#define GinDataPageGetRightBound(page) ((ItemPointer) PageGetContents(page)) +#define GinDataPageGetData(page) \ + (PageGetContents(page) + MAXALIGN(sizeof(ItemPointerData))) +#define GinSizeOfDataPageItem(page) \ + (GinPageIsLeaf(page) ? sizeof(ItemPointerData) : sizeof(PostingItem)) +#define GinDataPageGetItem(page,i) \ + (GinDataPageGetData(page) + ((i)-1) * GinSizeOfDataPageItem(page)) + +#define GinDataPageGetFreeSpace(page) \ + (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) \ + - MAXALIGN(sizeof(ItemPointerData)) \ + - GinPageGetOpaque(page)->maxoff * GinSizeOfDataPageItem(page) \ + - MAXALIGN(sizeof(GinPageOpaqueData))) + +#define GinMaxLeafDataItems \ + ((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - \ + MAXALIGN(sizeof(ItemPointerData)) - \ + MAXALIGN(sizeof(GinPageOpaqueData))) \ + / sizeof(ItemPointerData)) + +/* + * List pages + */ +#define GinListPageSize \ + ( BLCKSZ - SizeOfPageHeaderData - MAXALIGN(sizeof(GinPageOpaqueData)) ) + +/* + * Storage type for GIN's reloptions + */ +typedef struct GinOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + bool useFastUpdate; /* use fast updates? */ +} GinOptions; + +#define GIN_DEFAULT_USE_FASTUPDATE true +#define GinGetUseFastUpdate(relation) \ + ((relation)->rd_options ? \ + ((GinOptions *) (relation)->rd_options)->useFastUpdate : GIN_DEFAULT_USE_FASTUPDATE) + + +/* Macros for buffer lock/unlock operations */ +#define GIN_UNLOCK BUFFER_LOCK_UNLOCK +#define GIN_SHARE BUFFER_LOCK_SHARE +#define GIN_EXCLUSIVE BUFFER_LOCK_EXCLUSIVE + + +/* + * GinState: working data structure describing the index being worked on + */ +typedef struct GinState +{ + Relation index; + bool oneCol; /* true if single-column index */ + + /* + * origTupDesc is the nominal tuple descriptor of the index, ie, the i'th + * attribute shows the key type (not the input data type!) of the i'th + * index column. In a single-column index this describes the actual leaf + * index tuples. In a multi-column index, the actual leaf tuples contain + * a smallint column number followed by a key datum of the appropriate + * type for that column. We set up tupdesc[i] to describe the actual + * rowtype of the index tuples for the i'th column, ie, (int2, keytype). + * Note that in any case, leaf tuples contain more data than is known to + * the TupleDesc; see access/gin/README for details. + */ + TupleDesc origTupdesc; + TupleDesc tupdesc[INDEX_MAX_KEYS]; + + /* + * Per-index-column opclass support functions + */ + FmgrInfo compareFn[INDEX_MAX_KEYS]; + FmgrInfo extractValueFn[INDEX_MAX_KEYS]; + FmgrInfo extractQueryFn[INDEX_MAX_KEYS]; + FmgrInfo consistentFn[INDEX_MAX_KEYS]; + FmgrInfo comparePartialFn[INDEX_MAX_KEYS]; /* optional method */ + /* canPartialMatch[i] is true if comparePartialFn[i] is valid */ + bool canPartialMatch[INDEX_MAX_KEYS]; +} GinState; + +/* XLog stuff */ + +#define XLOG_GIN_CREATE_INDEX 0x00 + +#define XLOG_GIN_CREATE_PTREE 0x10 + +typedef struct ginxlogCreatePostingTree +{ + RelFileNode node; + BlockNumber blkno; + uint32 nitem; + /* follows list of heap's ItemPointer */ +} ginxlogCreatePostingTree; + +#define XLOG_GIN_INSERT 0x20 + +typedef struct ginxlogInsert +{ + RelFileNode node; + BlockNumber blkno; + BlockNumber updateBlkno; + OffsetNumber offset; + bool isDelete; + bool isData; + bool isLeaf; + OffsetNumber nitem; + + /* + * follows: tuples or ItemPointerData or PostingItem or list of + * ItemPointerData + */ +} ginxlogInsert; + +#define XLOG_GIN_SPLIT 0x30 + +typedef struct ginxlogSplit +{ + RelFileNode node; + BlockNumber lblkno; + BlockNumber rootBlkno; + BlockNumber rblkno; + BlockNumber rrlink; + OffsetNumber separator; + OffsetNumber nitem; + + bool isData; + bool isLeaf; + bool isRootSplit; + + BlockNumber leftChildBlkno; + BlockNumber updateBlkno; + + ItemPointerData rightbound; /* used only in posting tree */ + /* follows: list of tuple or ItemPointerData or PostingItem */ +} ginxlogSplit; + +#define XLOG_GIN_VACUUM_PAGE 0x40 + +typedef struct ginxlogVacuumPage +{ + RelFileNode node; + BlockNumber blkno; + OffsetNumber nitem; + /* follows content of page */ +} ginxlogVacuumPage; + +#define XLOG_GIN_DELETE_PAGE 0x50 + +typedef struct ginxlogDeletePage +{ + RelFileNode node; + BlockNumber blkno; + BlockNumber parentBlkno; + OffsetNumber parentOffset; + BlockNumber leftBlkno; + BlockNumber rightLink; +} ginxlogDeletePage; + +#define XLOG_GIN_UPDATE_META_PAGE 0x60 + +typedef struct ginxlogUpdateMeta +{ + RelFileNode node; + GinMetaPageData metadata; + BlockNumber prevTail; + BlockNumber newRightlink; + int32 ntuples; /* if ntuples > 0 then metadata.tail was + * updated with that many tuples; else new sub + * list was inserted */ + /* array of inserted tuples follows */ +} ginxlogUpdateMeta; + +#define XLOG_GIN_INSERT_LISTPAGE 0x70 + +typedef struct ginxlogInsertListPage +{ + RelFileNode node; + BlockNumber blkno; + BlockNumber rightlink; + int32 ntuples; + /* array of inserted tuples follows */ +} ginxlogInsertListPage; + +#define XLOG_GIN_DELETE_LISTPAGE 0x80 + +#define GIN_NDELETE_AT_ONCE 16 +typedef struct ginxlogDeleteListPages +{ + RelFileNode node; + GinMetaPageData metadata; + int32 ndeleted; + BlockNumber toDelete[GIN_NDELETE_AT_ONCE]; +} ginxlogDeleteListPages; + + +/* ginutil.c */ +extern Datum ginoptions(PG_FUNCTION_ARGS); +extern void initGinState(GinState *state, Relation index); +extern Buffer GinNewBuffer(Relation index); +extern void GinInitBuffer(Buffer b, uint32 f); +extern void GinInitPage(Page page, uint32 f, Size pageSize); +extern void GinInitMetabuffer(Buffer b); +extern int ginCompareEntries(GinState *ginstate, OffsetNumber attnum, + Datum a, GinNullCategory categorya, + Datum b, GinNullCategory categoryb); +extern int ginCompareAttEntries(GinState *ginstate, + OffsetNumber attnuma, Datum a, GinNullCategory categorya, + OffsetNumber attnumb, Datum b, GinNullCategory categoryb); +extern Datum *ginExtractEntries(GinState *ginstate, OffsetNumber attnum, + Datum value, bool isNull, + int32 *nentries, GinNullCategory **categories); + +extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple); +extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple, + GinNullCategory *category); + +/* gininsert.c */ +extern Datum ginbuild(PG_FUNCTION_ARGS); +extern Datum ginbuildempty(PG_FUNCTION_ARGS); +extern Datum gininsert(PG_FUNCTION_ARGS); +extern void ginEntryInsert(GinState *ginstate, + OffsetNumber attnum, Datum key, GinNullCategory category, + ItemPointerData *items, uint32 nitem, + GinStatsData *buildStats); + +/* ginbtree.c */ + +typedef struct GinBtreeStack +{ + BlockNumber blkno; + Buffer buffer; + OffsetNumber off; + /* predictNumber contains predicted number of pages on current level */ + uint32 predictNumber; + struct GinBtreeStack *parent; +} GinBtreeStack; + +typedef struct GinBtreeData *GinBtree; + +typedef struct GinBtreeData +{ + /* search methods */ + BlockNumber (*findChildPage) (GinBtree, GinBtreeStack *); + bool (*isMoveRight) (GinBtree, Page); + bool (*findItem) (GinBtree, GinBtreeStack *); + + /* insert methods */ + OffsetNumber (*findChildPtr) (GinBtree, Page, BlockNumber, OffsetNumber); + BlockNumber (*getLeftMostPage) (GinBtree, Page); + bool (*isEnoughSpace) (GinBtree, Buffer, OffsetNumber); + void (*placeToPage) (GinBtree, Buffer, OffsetNumber, XLogRecData **); + Page (*splitPage) (GinBtree, Buffer, Buffer, OffsetNumber, XLogRecData **); + void (*fillRoot) (GinBtree, Buffer, Buffer, Buffer); + + bool isData; + bool searchMode; + + Relation index; + GinState *ginstate; /* not valid in a data scan */ + bool fullScan; + bool isBuild; + + BlockNumber rightblkno; + + /* Entry options */ + OffsetNumber entryAttnum; + Datum entryKey; + GinNullCategory entryCategory; + IndexTuple entry; + bool isDelete; + + /* Data (posting tree) options */ + ItemPointerData *items; + uint32 nitem; + uint32 curitem; + + PostingItem pitem; +} GinBtreeData; + +extern GinBtreeStack *ginPrepareFindLeafPage(GinBtree btree, BlockNumber blkno); +extern GinBtreeStack *ginFindLeafPage(GinBtree btree, GinBtreeStack *stack); +extern void freeGinBtreeStack(GinBtreeStack *stack); +extern void ginInsertValue(GinBtree btree, GinBtreeStack *stack, + GinStatsData *buildStats); +extern void ginFindParents(GinBtree btree, GinBtreeStack *stack, BlockNumber rootBlkno); + +/* ginentrypage.c */ +extern IndexTuple GinFormTuple(GinState *ginstate, + OffsetNumber attnum, Datum key, GinNullCategory category, + ItemPointerData *ipd, uint32 nipd, bool errorTooBig); +extern void GinShortenTuple(IndexTuple itup, uint32 nipd); +extern void ginPrepareEntryScan(GinBtree btree, OffsetNumber attnum, + Datum key, GinNullCategory category, + GinState *ginstate); +extern void ginEntryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf); +extern IndexTuple ginPageGetLinkItup(Buffer buf); + +/* gindatapage.c */ +extern int ginCompareItemPointers(ItemPointer a, ItemPointer b); +extern uint32 ginMergeItemPointers(ItemPointerData *dst, + ItemPointerData *a, uint32 na, + ItemPointerData *b, uint32 nb); + +extern void GinDataPageAddItem(Page page, void *data, OffsetNumber offset); +extern void GinPageDeletePostingItem(Page page, OffsetNumber offset); + +typedef struct +{ + GinBtreeData btree; + GinBtreeStack *stack; +} GinPostingTreeScan; + +extern GinPostingTreeScan *ginPrepareScanPostingTree(Relation index, + BlockNumber rootBlkno, bool searchMode); +extern void ginInsertItemPointers(GinPostingTreeScan *gdi, + ItemPointerData *items, uint32 nitem, + GinStatsData *buildStats); +extern Buffer ginScanBeginPostingTree(GinPostingTreeScan *gdi); +extern void ginDataFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf); +extern void ginPrepareDataScan(GinBtree btree, Relation index); + +/* ginscan.c */ + +/* + * GinScanKeyData describes a single GIN index qualification condition. + * It contains one GinScanEntryData for each key datum extracted from + * the qual by the extractQueryFn or added implicitly by ginFillScanKey. + * nentries is the true number of entries, nuserentries is the number + * that extractQueryFn returned (which is what we report to consistentFn). + * The "user" entries must come first. + */ +typedef struct GinScanKeyData *GinScanKey; + +typedef struct GinScanEntryData *GinScanEntry; + +typedef struct GinScanKeyData +{ + /* Real number of entries in scanEntry[] (always > 0) */ + uint32 nentries; + /* Number of entries that extractQueryFn and consistentFn know about */ + uint32 nuserentries; + + /* array of GinScanEntryData, one per key datum */ + GinScanEntry scanEntry; + + /* array of ItemPointer result, reported to consistentFn */ + bool *entryRes; + + /* other data needed for calling consistentFn */ + Datum query; + /* NB: these three arrays have only nuserentries elements! */ + Datum *queryValues; + GinNullCategory *queryCategories; + Pointer *extra_data; + StrategyNumber strategy; + int32 searchMode; + OffsetNumber attnum; + + /* scan status data */ + ItemPointerData curItem; + bool recheckCurItem; + + bool firstCall; + bool isFinished; +} GinScanKeyData; + +typedef struct GinScanEntryData +{ + /* link to any preceding identical entry in current scan key */ + GinScanEntry master; + + /* ptr to value reported to consistentFn, points to parent->entryRes[i] */ + bool *pval; + + /* query key and other information from extractQueryFn */ + Datum queryKey; + GinNullCategory queryCategory; + bool isPartialMatch; + Pointer extra_data; + StrategyNumber strategy; + int32 searchMode; + OffsetNumber attnum; + + /* Current page in posting tree */ + Buffer buffer; + + /* current ItemPointer to heap */ + ItemPointerData curItem; + + /* for a partial-match or full-scan query, we accumulate all TIDs here */ + TIDBitmap *matchBitmap; + TBMIterator *matchIterator; + TBMIterateResult *matchResult; + + /* used for Posting list and one page in Posting tree */ + ItemPointerData *list; + uint32 nlist; + OffsetNumber offset; + + bool isFinished; + bool reduceResult; + uint32 predictNumberResult; +} GinScanEntryData; + +typedef struct GinScanOpaqueData +{ + MemoryContext tempCtx; + GinState ginstate; + + GinScanKey keys; + uint32 nkeys; + bool isVoidRes; /* true if ginstate.extractQueryFn guarantees + * that nothing will be found */ +} GinScanOpaqueData; + +typedef GinScanOpaqueData *GinScanOpaque; + +extern Datum ginbeginscan(PG_FUNCTION_ARGS); +extern Datum ginendscan(PG_FUNCTION_ARGS); +extern Datum ginrescan(PG_FUNCTION_ARGS); +extern Datum ginmarkpos(PG_FUNCTION_ARGS); +extern Datum ginrestrpos(PG_FUNCTION_ARGS); +extern void ginNewScanKey(IndexScanDesc scan); + +/* ginget.c */ +extern Datum gingetbitmap(PG_FUNCTION_ARGS); + +/* ginvacuum.c */ +extern Datum ginbulkdelete(PG_FUNCTION_ARGS); +extern Datum ginvacuumcleanup(PG_FUNCTION_ARGS); + +/* ginbulk.c */ +typedef struct GinEntryAccumulator +{ + RBNode rbnode; + Datum key; + GinNullCategory category; + OffsetNumber attnum; + bool shouldSort; + ItemPointerData *list; + uint32 maxcount; /* allocated size of list[] */ + uint32 count; /* current number of list[] entries */ +} GinEntryAccumulator; + +typedef struct +{ + GinState *ginstate; + long allocatedMemory; + GinEntryAccumulator *entryallocator; + uint32 eas_used; + RBTree *tree; +} BuildAccumulator; + +extern void ginInitBA(BuildAccumulator *accum); +extern void ginInsertBAEntries(BuildAccumulator *accum, + ItemPointer heapptr, OffsetNumber attnum, + Datum *entries, GinNullCategory *categories, + int32 nentries); +extern void ginBeginBAScan(BuildAccumulator *accum); +extern ItemPointerData *ginGetBAEntry(BuildAccumulator *accum, + OffsetNumber *attnum, Datum *key, GinNullCategory *category, + uint32 *n); + +/* ginfast.c */ + +typedef struct GinTupleCollector +{ + IndexTuple *tuples; + uint32 ntuples; + uint32 lentuples; + uint32 sumsize; +} GinTupleCollector; + +extern void ginHeapTupleFastInsert(GinState *ginstate, + GinTupleCollector *collector); +extern void ginHeapTupleFastCollect(GinState *ginstate, + GinTupleCollector *collector, + OffsetNumber attnum, Datum value, bool isNull, + ItemPointer ht_ctid); +extern void ginInsertCleanup(GinState *ginstate, + bool vac_delay, IndexBulkDeleteResult *stats); + +#endif /* GIN_PRIVATE_H */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 07a72b208ee..4cb6c5c17a5 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1047,6 +1047,11 @@ extern Datum window_first_value(PG_FUNCTION_ARGS); extern Datum window_last_value(PG_FUNCTION_ARGS); extern Datum window_nth_value(PG_FUNCTION_ARGS); +/* access/gin/ginarrayproc.c */ +extern Datum ginarrayextract(PG_FUNCTION_ARGS); +extern Datum ginqueryarrayextract(PG_FUNCTION_ARGS); +extern Datum ginarrayconsistent(PG_FUNCTION_ARGS); + /* access/transam/twophase.c */ extern Datum pg_prepared_xact(PG_FUNCTION_ARGS); diff --git a/src/test/regress/data/array.data b/src/test/regress/data/array.data index 12a420fe222..394d851fc35 100644 --- a/src/test/regress/data/array.data +++ b/src/test/regress/data/array.data @@ -98,3 +98,6 @@ 98 {38,34,32,89} {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} 99 {37,86} {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} 100 {85,32,57,39,49,84,32,3,30} {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +101 {} {} +102 {NULL} {NULL} +103 \N \N diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 4d86f454f96..7b05ce33548 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -499,7 +499,154 @@ SELECT * FROM array_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} -(3 rows) + 101 | {} | {} +(4 rows) + +SELECT * FROM array_op_test WHERE i = '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) + +SELECT * FROM array_op_test WHERE i @> '{}' ORDER BY seqno; + seqno | i | t +-------+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038} + 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793} + 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246} + 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557} + 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104} + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946} + 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407} + 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000} + 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249} + 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658} + 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 16 | {14,63,85,11} | {AAAAAA66777} + 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356} + 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494} + 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420} + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562} + 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219} + 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449} + 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009} + 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254} + 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601} + 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194} + 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} + 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938} + 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533} + 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796} + 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242} + 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084} + 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598} + 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611} + 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387} + 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620} + 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} + 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666} + 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587} + 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946} + 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621} + 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466} + 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037} + 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587} + 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955} + 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452} + 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862} + 53 | {38,17} | {AAAAAAAAAAA21658} + 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322} + 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737} + 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406} + 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415} + 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119} + 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955} + 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875} + 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804} + 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617} + 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938} + 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836} + 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946} + 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643} + 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955} + 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242} + 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052} + 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007} + 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121} + 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} + 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119} + 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183} + 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154} + 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176} + 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505} + 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526} + 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043} + 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089} + 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383} + 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587} + 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} + 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} + 101 | {} | {} + 102 | {NULL} | {NULL} +(102 rows) + +SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) + +SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; + seqno | i | t +-------+--------+-------- + 102 | {NULL} | {NULL} +(1 row) + +SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t @@ -557,7 +704,132 @@ SELECT * FROM array_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075, -------+--------------------+----------------------------------------------------------------------------------------------------------- 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} -(2 rows) + 101 | {} | {} +(3 rows) + +SELECT * FROM array_op_test WHERE t = '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) + +SELECT * FROM array_op_test WHERE t @> '{}' ORDER BY seqno; + seqno | i | t +-------+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038} + 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793} + 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246} + 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557} + 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104} + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946} + 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407} + 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000} + 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249} + 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658} + 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 16 | {14,63,85,11} | {AAAAAA66777} + 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356} + 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494} + 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420} + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562} + 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219} + 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449} + 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009} + 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254} + 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601} + 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194} + 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} + 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938} + 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533} + 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796} + 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242} + 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084} + 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598} + 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611} + 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387} + 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620} + 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} + 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666} + 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587} + 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946} + 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621} + 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466} + 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037} + 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587} + 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955} + 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452} + 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862} + 53 | {38,17} | {AAAAAAAAAAA21658} + 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322} + 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737} + 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406} + 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415} + 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119} + 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955} + 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875} + 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804} + 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617} + 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938} + 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836} + 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946} + 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643} + 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955} + 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242} + 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052} + 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007} + 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121} + 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} + 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119} + 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183} + 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154} + 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176} + 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505} + 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526} + 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043} + 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089} + 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383} + 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587} + 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} + 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} + 101 | {} | {} + 102 | {NULL} | {NULL} +(102 rows) + +SELECT * FROM array_op_test WHERE t && '{}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE t <@ '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) -- array casts SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}"; diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 1d1470a25d4..d586f69ebbb 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -577,10 +577,24 @@ RESET enable_bitmapscan; -- -- GIN over int[] and text[] -- +-- Note: GIN currently supports only bitmap scans, not plain indexscans +-- SET enable_seqscan = OFF; -SET enable_indexscan = ON; -SET enable_bitmapscan = OFF; +SET enable_indexscan = OFF; +SET enable_bitmapscan = ON; CREATE INDEX intarrayidx ON array_index_op_test USING gin (i); +explain (costs off) +SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; + QUERY PLAN +---------------------------------------------------- + Sort + Sort Key: seqno + -> Bitmap Heap Scan on array_index_op_test + Recheck Cond: (i @> '{32}'::integer[]) + -> Bitmap Index Scan on intarrayidx + Index Cond: (i @> '{32}'::integer[]) +(6 rows) + SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -659,7 +673,8 @@ SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} -(3 rows) + 101 | {} | {} +(4 rows) SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno; seqno | i | t @@ -667,7 +682,165 @@ SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno; 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} (1 row) +SELECT * FROM array_index_op_test WHERE i = '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) + +SELECT * FROM array_index_op_test WHERE i @> '{}' ORDER BY seqno; + seqno | i | t +-------+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038} + 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793} + 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246} + 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557} + 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104} + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946} + 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407} + 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000} + 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249} + 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658} + 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 16 | {14,63,85,11} | {AAAAAA66777} + 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356} + 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494} + 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420} + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562} + 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219} + 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449} + 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009} + 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254} + 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601} + 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194} + 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} + 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938} + 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533} + 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796} + 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242} + 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084} + 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598} + 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611} + 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387} + 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620} + 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} + 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666} + 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587} + 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946} + 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621} + 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466} + 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037} + 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587} + 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955} + 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452} + 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862} + 53 | {38,17} | {AAAAAAAAAAA21658} + 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322} + 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737} + 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406} + 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415} + 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119} + 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955} + 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875} + 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804} + 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617} + 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938} + 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836} + 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946} + 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643} + 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955} + 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242} + 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052} + 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007} + 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121} + 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} + 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119} + 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183} + 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154} + 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176} + 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505} + 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526} + 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043} + 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089} + 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383} + 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587} + 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} + 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} + 101 | {} | {} + 102 | {NULL} | {NULL} +(102 rows) + +SELECT * FROM array_index_op_test WHERE i && '{}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_index_op_test WHERE i <@ '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) + +SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; + seqno | i | t +-------+--------+-------- + 102 | {NULL} | {NULL} +(1 row) + +SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) + CREATE INDEX textarrayidx ON array_index_op_test USING gin (t); +explain (costs off) +SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: seqno + -> Bitmap Heap Scan on array_index_op_test + Recheck Cond: (t @> '{AAAAAAAA72908}'::text[]) + -> Bitmap Index Scan on textarrayidx + Index Cond: (t @> '{AAAAAAAA72908}'::text[]) +(6 rows) + SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -724,7 +897,8 @@ SELECT * FROM array_index_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA -------+--------------------+----------------------------------------------------------------------------------------------------------- 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} -(2 rows) + 101 | {} | {} +(3 rows) SELECT * FROM array_index_op_test WHERE t = '{AAAAAAAAAA646,A87088}' ORDER BY seqno; seqno | i | t @@ -732,101 +906,133 @@ SELECT * FROM array_index_op_test WHERE t = '{AAAAAAAAAA646,A87088}' ORDER BY se 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (1 row) --- Repeat some of the above tests but exercising bitmapscans instead -SET enable_indexscan = OFF; -SET enable_bitmapscan = ON; -SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_index_op_test WHERE i @> '{17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 53 | {38,17} | {AAAAAAAAAAA21658} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(8 rows) - -SELECT * FROM array_index_op_test WHERE i && '{17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 53 | {38,17} | {AAAAAAAAAAA21658} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(8 rows) - -SELECT * FROM array_index_op_test WHERE i @> '{32,17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(3 rows) +SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) -SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ +SELECT * FROM array_index_op_test WHERE t @> '{}' ORDER BY seqno; + seqno | i | t +-------+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038} + 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793} + 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246} + 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557} + 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104} 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946} + 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407} + 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000} + 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249} + 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557} 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658} + 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909} 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 16 | {14,63,85,11} | {AAAAAA66777} + 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356} + 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374} 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494} + 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420} + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562} + 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219} + 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449} + 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009} + 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254} + 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601} + 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194} + 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} + 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938} + 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533} + 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796} + 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242} + 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084} + 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598} + 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611} + 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387} + 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620} + 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} + 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666} + 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587} + 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946} + 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621} + 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466} + 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037} + 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587} + 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955} + 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452} + 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862} 53 | {38,17} | {AAAAAAAAAAA21658} + 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322} + 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737} + 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406} + 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415} + 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119} + 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955} + 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875} + 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804} + 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617} + 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938} + 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836} + 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946} + 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643} + 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955} + 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242} + 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598} 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052} + 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062} 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007} + 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121} + 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} + 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119} + 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183} + 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154} + 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176} + 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505} + 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526} + 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043} + 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089} + 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383} + 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587} + 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} + 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(11 rows) - -SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; - seqno | i | t --------+---------------+---------------------------------------------------------------------------------------------------------------------------- - 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} -(3 rows) - -SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno; - seqno | i | t --------+---------+----------------------------------------------------------------------------------------------------------------- - 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} + 101 | {} | {} + 102 | {NULL} | {NULL} +(102 rows) + +SELECT * FROM array_index_op_test WHERE t && '{}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_index_op_test WHERE t <@ '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} (1 row) -- And try it with a multicolumn GIN index DROP INDEX intarrayidx, textarrayidx; CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t); -SET enable_seqscan = OFF; -SET enable_indexscan = ON; -SET enable_bitmapscan = OFF; SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -885,64 +1091,22 @@ SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' OR 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (1 row) -SET enable_indexscan = OFF; -SET enable_bitmapscan = ON; -SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno; - seqno | i | t --------+--------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------- - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} - 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} - 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} - 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} - 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(7 rows) - -SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno; - seqno | i | t --------+--------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------- - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} - 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} - 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} - 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} - 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(7 rows) +SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) -SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno; - seqno | i | t --------+-----------------------------+------------------------------------------------------------------------------ - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; + seqno | i | t +-------+--------+-------- + 102 | {NULL} | {NULL} (1 row) -SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno; - seqno | i | t --------+-----------------------------+------------------------------------------------------------------------------ - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} (1 row) RESET enable_seqscan; diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index b0c096d9e5b..9ea53b1544b 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -203,6 +203,14 @@ SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i = '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @> '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; @@ -211,6 +219,10 @@ SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t = '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @> '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t && '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t <@ '{}' ORDER BY seqno; -- array casts SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}"; diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index 043f433eb00..97c1beb358a 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -249,13 +249,18 @@ RESET enable_bitmapscan; -- -- GIN over int[] and text[] -- +-- Note: GIN currently supports only bitmap scans, not plain indexscans +-- SET enable_seqscan = OFF; -SET enable_indexscan = ON; -SET enable_bitmapscan = OFF; +SET enable_indexscan = OFF; +SET enable_bitmapscan = ON; CREATE INDEX intarrayidx ON array_index_op_test USING gin (i); +explain (costs off) +SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; + SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE i @> '{17}' ORDER BY seqno; @@ -264,9 +269,20 @@ SELECT * FROM array_index_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE i = '{}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE i @> '{}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE i && '{}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE i <@ '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; CREATE INDEX textarrayidx ON array_index_op_test USING gin (t); +explain (costs off) +SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; + SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; @@ -275,19 +291,10 @@ SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORD SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE t = '{AAAAAAAAAA646,A87088}' ORDER BY seqno; - --- Repeat some of the above tests but exercising bitmapscans instead -SET enable_indexscan = OFF; -SET enable_bitmapscan = ON; - -SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i @> '{17}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i && '{17}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i @> '{32,17}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE t @> '{}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE t && '{}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE t <@ '{}' ORDER BY seqno; -- And try it with a multicolumn GIN index @@ -295,26 +302,15 @@ DROP INDEX intarrayidx, textarrayidx; CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t); -SET enable_seqscan = OFF; -SET enable_indexscan = ON; -SET enable_bitmapscan = OFF; - -SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno; -SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno; - -SET enable_indexscan = OFF; -SET enable_bitmapscan = ON; - SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno; SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno; +SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; RESET enable_seqscan; RESET enable_indexscan; |