aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/gin/README180
-rw-r--r--src/backend/access/gin/ginarrayproc.c168
-rw-r--r--src/backend/access/gin/ginbtree.c6
-rw-r--r--src/backend/access/gin/ginbulk.c134
-rw-r--r--src/backend/access/gin/gindatapage.c53
-rw-r--r--src/backend/access/gin/ginentrypage.c329
-rw-r--r--src/backend/access/gin/ginfast.c181
-rw-r--r--src/backend/access/gin/ginget.c858
-rw-r--r--src/backend/access/gin/gininsert.c266
-rw-r--r--src/backend/access/gin/ginscan.c283
-rw-r--r--src/backend/access/gin/ginutil.c293
-rw-r--r--src/backend/access/gin/ginvacuum.c21
-rw-r--r--src/backend/access/gin/ginxlog.c21
-rw-r--r--src/include/access/gin.h612
-rw-r--r--src/include/access/gin_private.h710
-rw-r--r--src/include/utils/builtins.h5
-rw-r--r--src/test/regress/data/array.data3
-rw-r--r--src/test/regress/expected/arrays.out276
-rw-r--r--src/test/regress/expected/create_index.out438
-rw-r--r--src/test/regress/sql/arrays.sql12
-rw-r--r--src/test/regress/sql/create_index.sql54
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;