From 73912e7fbd1b52c51d914214abbec1cda64595f2 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 7 Jan 2011 19:16:24 -0500 Subject: [PATCH] Fix GIN to support null keys, empty and null items, and full index scans. Per my recent proposal(s). Null key datums can now be returned by extractValue and extractQuery functions, and will be stored in the index. Also, placeholder entries are made for indexable items that are NULL or contain no keys according to extractValue. This means that the index is now always complete, having at least one entry for every indexed heap TID, and so we can get rid of the prohibition on full-index scans. A full-index scan is implemented much the same way as partial-match scans were already: we build a bitmap representing all the TIDs found in the index, and then drive the results off that. Also, introduce a concept of a "search mode" that can be requested by extractQuery when the operator requires matching to empty items (this is just as cheap as matching to a single key) or requires a full index scan (which is not so cheap, but it sure beats failing or giving wrong answers). The behavior remains backward compatible for opclasses that don't return any null keys or request a non-default search mode. Using these features, we can now make the GIN index opclass for anyarray behave in a way that matches the actual anyarray operators for &&, <@, @>, and = ... which it failed to do before in assorted corner cases. This commit fixes the core GIN code and ginarrayprocs.c, updates the documentation, and adds some simple regression test cases for the new behaviors using the array operators. The tsearch and contrib GIN opclass support functions still need to be looked over and probably fixed. Another thing I intend to fix separately is that this is pretty inefficient for cases where more than one scan condition needs a full-index search: we'll run duplicate GinScanEntrys, each one of which builds a large bitmap. There is some existing logic to merge duplicate GinScanEntrys but it needs refactoring to make it work for entries belonging to different scan keys. Note that most of gin.h has been split out into a new file gin_private.h, so that gin.h doesn't export anything that's not supposed to be used by GIN opclasses or the rest of the backend. I did quite a bit of other code beautification work as well, mostly fixing comments and choosing more appropriate names for things. --- contrib/hstore/hstore_gin.c | 1 + doc/src/sgml/gin.sgml | 262 +++-- src/backend/access/gin/README | 180 ++- src/backend/access/gin/ginarrayproc.c | 168 ++- src/backend/access/gin/ginbtree.c | 6 +- src/backend/access/gin/ginbulk.c | 134 ++- src/backend/access/gin/gindatapage.c | 55 +- src/backend/access/gin/ginentrypage.c | 329 +++--- src/backend/access/gin/ginfast.c | 181 +-- src/backend/access/gin/ginget.c | 1236 +++++++++++--------- src/backend/access/gin/gininsert.c | 278 +++-- src/backend/access/gin/ginscan.c | 285 +++-- src/backend/access/gin/ginutil.c | 289 +++-- src/backend/access/gin/ginvacuum.c | 21 +- src/backend/access/gin/ginxlog.c | 21 +- src/include/access/gin.h | 612 +--------- src/include/access/gin_private.h | 710 +++++++++++ src/include/utils/builtins.h | 5 + src/test/regress/data/array.data | 3 + src/test/regress/expected/arrays.out | 276 ++++- src/test/regress/expected/create_index.out | 436 ++++--- src/test/regress/sql/arrays.sql | 12 + src/test/regress/sql/create_index.sql | 54 +- 23 files changed, 3505 insertions(+), 2049 deletions(-) create mode 100644 src/include/access/gin_private.h diff --git a/contrib/hstore/hstore_gin.c b/contrib/hstore/hstore_gin.c index da9b79d8de..8fd3d27873 100644 --- a/contrib/hstore/hstore_gin.c +++ b/contrib/hstore/hstore_gin.c @@ -4,6 +4,7 @@ #include "postgres.h" #include "access/gin.h" +#include "access/skey.h" #include "catalog/pg_type.h" #include "hstore.h" diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index a1838e6266..f7bab75f70 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -12,17 +12,38 @@ Introduction - GIN stands for Generalized Inverted Index. It is - an index structure storing a set of (key, posting list) pairs, where - a posting list is a set of rows in which the key occurs. Each - indexed value can contain many keys, so the same row ID can appear in - multiple posting lists. + GIN stands for Generalized Inverted Index. + GIN is designed for handling cases where the items + to be indexed are composite values, and the queries to be handled by + the index need to search for element values that appear within + the composite items. For example, the items could be documents, + and the queries could be searches for documents containing specific words. - It is generalized in the sense that a GIN index - does not need to be aware of the operation that it accelerates. - Instead, it uses custom strategies defined for particular data types. + We use the word item to refer to a composite value that + is to be indexed, and the word key to refer to an element + value. GIN always stores and searches for keys, + not item values per se. + + + + A GIN index stores a set of (key, posting list) pairs, + where a posting list is a set of row IDs in which the key + occurs. The same row ID can appear in multiple posting lists, since + an item can contain more than one key. Each key value is stored only + once, so a GIN index is very compact for cases + where the same key appears many times. + + + + GIN is generalized in the sense that the + GIN access method code does not need to know the + specific operations that it accelerates. + Instead, it uses custom strategies defined for particular data types. + The strategy defines how keys are extracted from indexed items and + query conditions, and how to determine whether a row that contains + some of the key values in a query actually satisfies the query. @@ -54,7 +75,7 @@ All it takes to get a GIN access method working is to implement four (or five) user-defined methods, which define the behavior of - keys in the tree and the relationships between keys, indexed values, + keys in the tree and the relationships between keys, indexed items, and indexable queries. In short, GIN combines extensibility with generality, code reuse, and a clean interface. @@ -68,53 +89,92 @@ int compare(Datum a, Datum b) - Compares keys (not indexed values!) and returns an integer less than + Compares two keys (not indexed items!) and returns an integer less than zero, zero, or greater than zero, indicating whether the first key is - less than, equal to, or greater than the second. + less than, equal to, or greater than the second. Null keys are never + passed to this function. - Datum *extractValue(Datum inputValue, int32 *nkeys) + Datum *extractValue(Datum itemValue, int32 *nkeys, + bool **nullFlags) - Returns an array of keys given a value to be indexed. The + Returns a palloc'd array of keys given an item to be indexed. The number of returned keys must be stored into *nkeys. + If any of the keys can be null, also palloc an array of + *nkeys booleans, store its address at + *nullFlags, and set these null flags as needed. + *nullFlags can be left NULL (its initial value) + if all keys are non-null. + The return value can be NULL if the item contains no keys. Datum *extractQuery(Datum query, int32 *nkeys, - StrategyNumber n, bool **pmatch, Pointer **extra_data) + StrategyNumber n, bool **pmatch, Pointer **extra_data, + bool **nullFlags, int32 *searchMode) - Returns an array of keys given a value to be queried; that is, + Returns a palloc'd array of keys given a value to be queried; that is, query is the value on the right-hand side of an indexable operator whose left-hand side is the indexed column. n is the strategy number of the operator within the operator class (see ). Often, extractQuery will need to consult n to determine the data type of - query and the key values that need to be extracted. + query and the method it should use to extract key values. The number of returned keys must be stored into *nkeys. - If the query contains no keys then extractQuery - should store 0 or -1 into *nkeys, depending on the - semantics of the operator. 0 means that every - value matches the query and a full-index scan should be - performed (but see ). - -1 means that nothing can match the query, and - so the index scan can be skipped entirely. + If any of the keys can be null, also palloc an array of + *nkeys booleans, store its address at + *nullFlags, and set these null flags as needed. + *nullFlags can be left NULL (its initial value) + if all keys are non-null. + The return value can be NULL if the query contains no keys. + + + + searchMode is an output argument that allows + extractQuery to specify details about how the search + will be done. + If *searchMode is set to + GIN_SEARCH_MODE_DEFAULT (which is the value it is + initialized to before call), only items that match at least one of + the returned keys are considered candidate matches. + If *searchMode is set to + GIN_SEARCH_MODE_INCLUDE_EMPTY, then in addition to items + containing at least one matching key, items that contain no keys at + all are considered candidate matches. (This mode is useful for + implementing is-subset-of operators, for example.) + If *searchMode is set to GIN_SEARCH_MODE_ALL, + then all non-null items in the index are considered candidate + matches, whether they match any of the returned keys or not. (This + mode is much slower than the other two choices, since it requires + scanning essentially the entire index, but it may be necessary to + implement corner cases correctly. An operator that needs this mode + in most cases is probably not a good candidate for a GIN operator + class.) + The symbols to use for setting this mode are defined in + access/gin.h. + + + pmatch is an output argument for use when partial match is supported. To use it, extractQuery must allocate - an array of *nkeys Booleans and store its address at + an array of *nkeys booleans and store its address at *pmatch. Each element of the array should be set to TRUE if the corresponding key requires partial match, FALSE if not. If *pmatch is set to NULL then GIN assumes partial match is not required. The variable is initialized to NULL before call, so this argument can simply be ignored by operator classes that do not support partial match. + + + extra_data is an output argument that allows extractQuery to pass additional data to the consistent and comparePartial methods. @@ -133,25 +193,51 @@ bool consistent(bool check[], StrategyNumber n, Datum query, - int32 nkeys, Pointer extra_data[], bool *recheck) + int32 nkeys, Pointer extra_data[], bool *recheck, + Datum queryKeys[], bool nullFlags[]) - Returns TRUE if the indexed value satisfies the query operator with - strategy number n (or might satisfy, if the recheck - indication is returned). The check array has length + Returns TRUE if an indexed item satisfies the query operator with + strategy number n (or might satisfy it, if the recheck + indication is returned). This function does not have direct access + to the indexed item's value, since GIN does not + store items explicitly. Rather, what is available is knowledge + about which key values extracted from the query appear in a given + indexed item. The check array has length nkeys, which is the same as the number of keys previously returned by extractQuery for this query datum. Each element of the - check array is TRUE if the indexed value contains the + check array is TRUE if the indexed item contains the corresponding query key, ie, if (check[i] == TRUE) the i-th key of the - extractQuery result array is present in the indexed value. - The original query datum (not the extracted key array!) is - passed in case the consistent method needs to consult it. + extractQuery result array is present in the indexed item. + The original query datum is + passed in case the consistent method needs to consult it, + and so are the queryKeys[] and nullFlags[] + arrays previously returned by extractQuery. extra_data is the extra-data array returned by extractQuery, or NULL if none. + + + + When extractQuery returns a null key in + queryKeys[], the corresponding check[] element + is TRUE if the indexed item contains a null key; that is, the + semantics of check[] are like IS NOT DISTINCT + FROM. The consistent function can examine the + corresponding nullFlags[] element if it needs to tell + the difference between a regular value match and a null match. + + + On success, *recheck should be set to TRUE if the heap tuple needs to be rechecked against the query operator, or FALSE if - the index test is exact. + the index test is exact. That is, a FALSE return value guarantees + that the heap tuple does not match the query; a TRUE return value with + *recheck set to FALSE guarantees that the heap tuple does + match the query; and a TRUE return value with + *recheck set to TRUE means that the heap tuple might match + the query, so it needs to be fetched and rechecked by evaluating the + query operator directly against the originally indexed item. @@ -166,7 +252,7 @@ Pointer extra_data) - Compare a partial-match query to an index key. Returns an integer + Compare a partial-match query key to an index key. Returns an integer whose sign indicates the result: less than zero means the index key does not match the query, but the index scan should continue; zero means that the index key does match the query; greater than zero @@ -176,6 +262,7 @@ semantics are needed to determine when to end the scan. Also, extra_data is the corresponding element of the extra-data array made by extractQuery, or NULL if none. + Null keys are never passed to this function. @@ -190,6 +277,18 @@ for details. + + The actual data types of the various Datum values mentioned + above vary depending on the operator class. The item values passed to + extractValue are always of the operator class's input type, and + all key values must be of the class's STORAGE type. The type of + the query argument passed to extractQuery and + consistent is whatever is specified as the right-hand input + type of the class member operator identified by the strategy number. + This need not be the same as the item type, so long as key values of the + correct type can be extracted from it. + + @@ -197,10 +296,26 @@ Internally, a GIN index contains a B-tree index - constructed over keys, where each key is an element of the indexed value - (a member of an array, for example) and where each tuple in a leaf page is - either a pointer to a B-tree over heap pointers (PT, posting tree), or a - list of heap pointers (PL, posting list) if the list is small enough. + constructed over keys, where each key is an element of one or more indexed + items (a member of an array, for example) and where each tuple in a leaf + page contains either a pointer to a B-tree of heap pointers (a + posting tree), or a simple list of heap pointers (a posting + list) when the list is small enough to fit into a single index tuple along + with the key value. + + + + As of PostgreSQL 9.1, NULL key values can be + included in the index. Also, placeholder NULLs are included in the index + for indexed items that are NULL or contain no keys according to + extractValue. This allows searches that should find empty + items to do so. + + + + Multi-column GIN indexes are implemented by building + a single B-tree over composite values (column number, key value). The + key values for different columns can be of different types. @@ -210,7 +325,7 @@ Updating a GIN index tends to be slow because of the intrinsic nature of inverted indexes: inserting or updating one heap row can cause many inserts into the index (one for each key extracted - from the indexed value). As of PostgreSQL 8.4, + from the indexed item). As of PostgreSQL 8.4, GIN is capable of postponing much of this work by inserting new tuples into a temporary, unsorted list of pending entries. When the table is vacuumed, or if the pending list becomes too large @@ -218,7 +333,7 @@ main GIN data structure using the same bulk insert techniques used during initial index creation. This greatly improves GIN index update speed, even counting the additional - vacuum overhead. Moreover the overhead can be done by a background + vacuum overhead. Moreover the overhead work can be done by a background process instead of in foreground query processing. @@ -252,9 +367,9 @@ The extractQuery method, instead of returning a key value to be matched exactly, returns a key value that is the lower bound of the range to be searched, and sets the pmatch flag true. - The key range is then searched using the comparePartial - method. comparePartial must return zero for an actual - match, less than zero for a non-match that is still within the range + The key range is then scanned using the comparePartial + method. comparePartial must return zero for a matching + index key, less than zero for a non-match that is still within the range to be searched, or greater than zero if the index key is past the range that could match. @@ -271,7 +386,7 @@ Insertion into a GIN index can be slow - due to the likelihood of many keys being inserted for each value. + due to the likelihood of many keys being inserted for each item. So, for bulk insertions into a table it is advisable to drop the GIN index and recreate it after finishing bulk insertion. @@ -302,7 +417,7 @@ During a series of insertions into an existing GIN index that has FASTUPDATE enabled, the system will clean up - the pending-entry list whenever it grows larger than + the pending-entry list whenever the list grows larger than work_mem. To avoid fluctuations in observed response time, it's desirable to have pending-list cleanup occur in the background (i.e., via autovacuum). Foreground cleanup operations can be avoided by @@ -318,7 +433,7 @@ The primary goal of developing GIN indexes was - to create support for highly scalable, full-text search in + to create support for highly scalable full-text search in PostgreSQL, and there are often situations when a full-text search returns a very large set of results. Moreover, this often happens when the query contains very frequent words, so that the @@ -328,9 +443,9 @@ fast.) - To facilitate controlled execution of such queries + To facilitate controlled execution of such queries, GIN has a configurable soft upper limit on the - number of rows returned, the + number of rows returned: the gin_fuzzy_search_limit configuration parameter. It is set to 0 (meaning no limit) by default. If a non-zero limit is set, then the returned set is a subset of @@ -338,9 +453,13 @@ Soft means that the actual number of returned results - could differ slightly from the specified limit, depending on the query + could differ somewhat from the specified limit, depending on the query and the quality of the system's random number generator. + + From experience, values in the thousands (e.g., 5000 — 20000) + work well. + @@ -351,44 +470,13 @@ Limitations - GIN doesn't support full index scans. The reason for - this is that extractValue is allowed to return zero keys, - as for example might happen with an empty string or empty array. In such - a case the indexed value will be unrepresented in the index. It is - therefore impossible for GIN to guarantee that a - scan of the index can find every row in the table. - - - - Because of this limitation, when extractQuery returns - nkeys = 0 to indicate that all values match the query, - GIN will emit an error. (If there are multiple ANDed - indexable operators in the query, this happens only if they all return zero - for nkeys.) - - - - It is possible for an operator class to circumvent the restriction against - full index scan. To do that, extractValue must return at least - one (possibly dummy) key for every indexed value, and - extractQuery must convert an unrestricted search into - a partial-match query that will scan the whole index. This is inefficient - but might be necessary to avoid corner-case failures with operators such - as LIKE or subset inclusion. - - - - GIN assumes that indexable operators are strict. - This means that extractValue will not be called at all on - a NULL value (so the value will go unindexed), and - extractQuery will not be called on a NULL comparison - value either (instead, the query is presumed to be unmatchable). - - - - A possibly more serious limitation is that GIN cannot - handle NULL keys — for example, an array containing a NULL cannot - be handled except by ignoring the NULL. + GIN assumes that indexable operators are strict. This + means that extractValue will not be called at all on a NULL + item value (instead, a placeholder index entry is created automatically), + and extractQuery will not be called on a NULL query + value either (instead, the query is presumed to be unsatisfiable). Note + however that NULL key values contained within a non-null composite item + or query value are supported. diff --git a/src/backend/access/gin/README b/src/backend/access/gin/README index 0f634f83d1..67159d8529 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. +Index structure +--------------- + +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.) + 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. - -Open Items ----------- - -We appreciate any comments, help and suggestions. - - * 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 1837a0d5a1..2100c5fd0e 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 ec038aac4b..739fa8afff 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 a4ee3364e1..f0c8c8e37f 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 f6e86da8db..4a1e754800 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); - static XLogRecData rdata[3]; - int sizeofitem = GinSizeOfItem(page); - static ginxlogInsert data; + int sizeofitem = GinSizeOfDataPageItem(page); int cnt = 0; + /* these must be static so they can be returned to caller */ + static XLogRecData rdata[3]; + static ginxlogInsert data; *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 e56034202a..9749a1be78 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); - if (nipd > 0) + /* + * 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 (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 + + /* + * Insert category byte, if needed + */ + if (category != GIN_CAT_NORM_KEY) { - /* - * 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); + 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)); @@ -150,9 +160,46 @@ GinShortenTuple(IndexTuple itup, uint32 nipd) GinSetNPosting(itup, 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); @@ -474,31 +539,6 @@ entryPlaceToPage(GinBtree btree, Buffer buf, OffsetNumber off, XLogRecData **prd btree->entry = NULL; } -/* - * 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. @@ -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 3941f7eb00..9960c786c9 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; + + 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); } - - collector->ntuples += nentries; - - return nentries; } /* @@ -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. * - * Note that da is just workspace --- it does not carry any state across + * Go through all tuples >= startoff on page and collect values in accum + * + * 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 ed1e71c9d6..aaef6efbb5 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: * - * Returns true if done, false if it's needed to restart scan from scratch + * 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 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 (cmp > 0) - return true; - else if (cmp < 0) + if (scanEntry->isPartialMatch) { - stack->off++; - continue; + int32 cmp; + + /* + * 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) + { + /* + * 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,408 +915,6 @@ keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx, } while (!res); } - -/* - * Get ItemPointer of next heap row to be checked from pending list. - * Returns false if there are no more. On pages with several 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. - * - * 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. - */ -static bool -scanGetCandidate(IndexScanDesc scan, pendingPosition *pos) -{ - OffsetNumber maxoff; - Page page; - IndexTuple itup; - - ItemPointerSetInvalid(&pos->item); - for (;;) - { - page = BufferGetPage(pos->pendingBuffer); - - maxoff = PageGetMaxOffsetNumber(page); - if (pos->firstOffset > maxoff) - { - BlockNumber blkno = GinPageGetOpaque(page)->rightlink; - - if (blkno == InvalidBlockNumber) - { - UnlockReleaseBuffer(pos->pendingBuffer); - pos->pendingBuffer = InvalidBuffer; - - return false; - } - else - { - /* - * Here we must prevent deletion of next page by insertcleanup - * process, which may be trying to obtain exclusive lock on - * current page. So, we lock next page before releasing the - * current one - */ - Buffer tmpbuf = ReadBuffer(scan->indexRelation, blkno); - - LockBuffer(tmpbuf, GIN_SHARE); - UnlockReleaseBuffer(pos->pendingBuffer); - - pos->pendingBuffer = tmpbuf; - pos->firstOffset = FirstOffsetNumber; - } - } - else - { - itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->firstOffset)); - pos->item = itup->t_tid; - if (GinPageHasFullRow(page)) - { - /* - * find itempointer to the next row - */ - for (pos->lastOffset = pos->firstOffset + 1; pos->lastOffset <= maxoff; pos->lastOffset++) - { - itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->lastOffset)); - if (!ItemPointerEquals(&pos->item, &itup->t_tid)) - break; - } - } - else - { - /* - * All itempointers are the same on this page - */ - pos->lastOffset = maxoff + 1; - } - - /* - * Now pos->firstOffset points to the first tuple of current heap - * row, pos->lastOffset points to the first tuple of second heap - * row (or to the end of page) - */ - - break; - } - } - - return true; -} - -/* - * Scan 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 - */ -static bool -matchPartialInPendingList(GinState *ginstate, Page page, - OffsetNumber off, OffsetNumber maxoff, - Datum value, OffsetNumber attrnum, - Datum *datum, bool *datumExtracted, - StrategyNumber strategy, - Pointer extra_data) -{ - IndexTuple itup; - int32 cmp; - - while (off < maxoff) - { - itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); - if (attrnum != gintuple_get_attrnum(ginstate, itup)) - return false; - - if (datumExtracted[off - 1] == false) - { - datum[off - 1] = gin_index_getattr(ginstate, itup); - datumExtracted[off - 1] = true; - } - - /*---------- - * Check partial match. - * case cmp == 0 => match - * case cmp > 0 => not match and end scan (no later match possible) - * case cmp < 0 => not match and continue scan - *---------- - */ - cmp = DatumGetInt32(FunctionCall4(&ginstate->comparePartialFn[attrnum - 1], - value, - datum[off - 1], - UInt16GetDatum(strategy), - PointerGetDatum(extra_data))); - if (cmp == 0) - return true; - else if (cmp > 0) - return false; - - off++; - } - - 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 - * - * The pendingBuffer is presumed pinned and share-locked on entry. - */ -static bool -collectDatumForItem(IndexScanDesc scan, pendingPosition *pos) -{ - GinScanOpaque so = (GinScanOpaque) scan->opaque; - OffsetNumber attrnum; - Page page; - IndexTuple itup; - int i, - j; - - /* - * Reset entryRes - */ - for (i = 0; i < so->nkeys; i++) - { - GinScanKey key = so->keys + i; - - memset(key->entryRes, FALSE, key->nentries); - } - memset(pos->hasMatchKey, FALSE, so->nkeys); - - for (;;) - { - Datum datum[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)); - - page = BufferGetPage(pos->pendingBuffer); - - for (i = 0; i < so->nkeys; i++) - { - GinScanKey key = so->keys + i; - - for (j = 0; j < key->nentries; j++) - { - OffsetNumber StopLow = pos->firstOffset, - StopHigh = pos->lastOffset, - StopMiddle; - GinScanEntry entry = key->scanEntry + j; - - /* already true - do not extra work */ - if (key->entryRes[j]) - continue; - - /* - * Interested 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 - */ - while (StopLow < StopHigh) - { - 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) - StopLow = StopMiddle + 1; - else - { - int res; - - if (datumExtracted[StopMiddle - 1] == false) - { - datum[StopMiddle - 1] = gin_index_getattr(&so->ginstate, itup); - datumExtracted[StopMiddle - 1] = true; - } - res = ginCompareEntries(&so->ginstate, - entry->attnum, - entry->entry, - datum[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; - 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. - */ - key->entryRes[j] = - matchPartialInPendingList(&so->ginstate, - page, StopHigh, - pos->lastOffset, - entry->entry, - entry->attnum, - datum, - datumExtracted, - entry->strategy, - entry->extra_data); - } - - pos->hasMatchKey[i] |= key->entryRes[j]; - } - } - - pos->firstOffset = pos->lastOffset; - - if (GinPageHasFullRow(page)) - { - /* - * We scan all values from one tuple, go to next one - */ - - return hasAllMatchingKeys(so, pos); - } - else - { - ItemPointerData item = pos->item; - - /* - * need to get next portion of tuples of row containing on several - * pages - */ - - if (scanGetCandidate(scan, pos) == false || !ItemPointerEquals(&pos->item, &item)) - elog(ERROR, "Could not process tuple"); /* XXX should not be - * here ! */ - } - } - - return hasAllMatchingKeys(so, pos); -} - -/* - * Collect all matched rows from pending list in bitmap - */ -static void -scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids) -{ - GinScanOpaque so = (GinScanOpaque) scan->opaque; - MemoryContext oldCtx; - bool recheck, - match; - int i; - pendingPosition pos; - Buffer metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO); - BlockNumber blkno; - - *ntids = 0; - - LockBuffer(metabuffer, GIN_SHARE); - blkno = GinPageGetMeta(BufferGetPage(metabuffer))->head; - - /* - * fetch head of list before unlocking metapage. head page must be pinned - * to prevent deletion by vacuum process - */ - if (blkno == InvalidBlockNumber) - { - /* No pending list, so proceed with normal scan */ - UnlockReleaseBuffer(metabuffer); - return; - } - - pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno); - LockBuffer(pos.pendingBuffer, GIN_SHARE); - pos.firstOffset = FirstOffsetNumber; - UnlockReleaseBuffer(metabuffer); - pos.hasMatchKey = palloc(sizeof(bool) * so->nkeys); - - /* - * loop for each heap row. scanGetCandidate returns full row or row's - * tuples from first page. - */ - 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. - */ - if (!collectDatumForItem(scan, &pos)) - continue; - - /* - * Matching of entries of one row is finished, so check row using - * consistent functions. - */ - oldCtx = MemoryContextSwitchTo(so->tempCtx); - recheck = false; - match = true; - - for (i = 0; i < so->nkeys; i++) - { - GinScanKey key = so->keys + i; - - if (!callConsistentFn(&so->ginstate, key)) - { - match = false; - break; - } - recheck |= key->recheckCurItem; - } - - MemoryContextSwitchTo(oldCtx); - MemoryContextReset(so->tempCtx); - - if (match) - { - tbm_add_tuples(tbm, &pos.item, 1, recheck); - (*ntids)++; - } - } - - pfree(pos.hasMatchKey); -} - /* * Get next heap item pointer (after advancePast) from scan. * Returns true if anything found. @@ -1284,7 +958,7 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, while (key->isFinished == FALSE && ginCompareItemPointers(&key->curItem, &myAdvancePast) <= 0) - keyGetItem(scan->indexRelation, &so->ginstate, so->tempCtx, + keyGetItem(&so->ginstate, so->tempCtx, key, &myAdvancePast); if (key->isFinished) @@ -1360,6 +1034,473 @@ scanGetItem(IndexScanDesc scan, ItemPointer advancePast, 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 heap rows + * it returns each row separately, on page with part of heap row returns + * 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. + */ +static bool +scanGetCandidate(IndexScanDesc scan, pendingPosition *pos) +{ + OffsetNumber maxoff; + Page page; + IndexTuple itup; + + ItemPointerSetInvalid(&pos->item); + for (;;) + { + page = BufferGetPage(pos->pendingBuffer); + + maxoff = PageGetMaxOffsetNumber(page); + if (pos->firstOffset > maxoff) + { + BlockNumber blkno = GinPageGetOpaque(page)->rightlink; + + if (blkno == InvalidBlockNumber) + { + UnlockReleaseBuffer(pos->pendingBuffer); + pos->pendingBuffer = InvalidBuffer; + + return false; + } + else + { + /* + * Here we must prevent deletion of next page by insertcleanup + * process, which may be trying to obtain exclusive lock on + * current page. So, we lock next page before releasing the + * current one + */ + Buffer tmpbuf = ReadBuffer(scan->indexRelation, blkno); + + LockBuffer(tmpbuf, GIN_SHARE); + UnlockReleaseBuffer(pos->pendingBuffer); + + pos->pendingBuffer = tmpbuf; + pos->firstOffset = FirstOffsetNumber; + } + } + else + { + itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->firstOffset)); + pos->item = itup->t_tid; + if (GinPageHasFullRow(page)) + { + /* + * find itempointer to the next row + */ + for (pos->lastOffset = pos->firstOffset + 1; pos->lastOffset <= maxoff; pos->lastOffset++) + { + itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->lastOffset)); + if (!ItemPointerEquals(&pos->item, &itup->t_tid)) + break; + } + } + else + { + /* + * All itempointers are the same on this page + */ + pos->lastOffset = maxoff + 1; + } + + /* + * Now pos->firstOffset points to the first tuple of current heap + * row, pos->lastOffset points to the first tuple of next heap + * row (or to the end of page) + */ + break; + } + } + + return true; +} + +/* + * 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, + 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 (gintuple_get_attrnum(ginstate, itup) != entry->attnum) + return false; + + if (datumExtracted[off - 1] == false) + { + 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 + * case cmp > 0 => not match and end scan (no later match possible) + * case cmp < 0 => not match and continue scan + *---------- + */ + cmp = DatumGetInt32(FunctionCall4(&ginstate->comparePartialFn[entry->attnum - 1], + entry->queryKey, + datum[off - 1], + UInt16GetDatum(entry->strategy), + PointerGetDatum(entry->extra_data))); + if (cmp == 0) + return true; + else if (cmp > 0) + return false; + + off++; + } + + return false; +} + +/* + * 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 +collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos) +{ + GinScanOpaque so = (GinScanOpaque) scan->opaque; + OffsetNumber attrnum; + Page page; + IndexTuple itup; + int i, + j; + + /* + * Reset all entryRes and hasMatchKey flags + */ + for (i = 0; i < so->nkeys; i++) + { + GinScanKey key = so->keys + i; + + memset(key->entryRes, FALSE, key->nentries); + } + 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)); + + page = BufferGetPage(pos->pendingBuffer); + + for (i = 0; i < so->nkeys; i++) + { + GinScanKey key = so->keys + i; + + for (j = 0; j < key->nentries; j++) + { + GinScanEntry entry = key->scanEntry + j; + OffsetNumber StopLow = pos->firstOffset, + StopHigh = pos->lastOffset, + StopMiddle; + + /* If already matched on earlier page, do no extra work */ + if (key->entryRes[j]) + continue; + + /* + * 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 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; + continue; + } + if (key->attnum > attrnum) + { + StopLow = StopMiddle + 1; + continue; + } + + if (datumExtracted[StopMiddle - 1] == false) + { + datum[StopMiddle - 1] = + gintuple_get_key(&so->ginstate, itup, + &category[StopMiddle - 1]); + datumExtracted[StopMiddle - 1] = true; + } + + if (entry->queryCategory == GIN_CAT_EMPTY_QUERY) + { + /* special behavior depending on searchMode */ + if (entry->searchMode == GIN_SEARCH_MODE_ALL) + { + /* 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->queryKey, + entry->queryCategory, + datum[StopMiddle - 1], + category[StopMiddle - 1]); + } + + 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 + key->entryRes[j] = true; + + /* done with binary search */ + break; + } + else if (res < 0) + StopHigh = StopMiddle; + else + StopLow = StopMiddle + 1; + } + + if (StopLow >= StopHigh && entry->isPartialMatch) + { + /* + * 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, + pos->lastOffset, + entry, + datum, + category, + datumExtracted); + } + + pos->hasMatchKey[i] |= key->entryRes[j]; + } + } + + /* Advance firstOffset over the scanned tuples */ + pos->firstOffset = pos->lastOffset; + + if (GinPageHasFullRow(page)) + { + /* + * We have examined all pending entries for the current heap row. + * Break out of loop over pages. + */ + break; + } + else + { + /* + * 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 find additional pending pages for same heap tuple"); + } + } + + /* + * 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 into bitmap + */ +static void +scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids) +{ + GinScanOpaque so = (GinScanOpaque) scan->opaque; + MemoryContext oldCtx; + bool recheck, + match; + int i; + pendingPosition pos; + Buffer metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO); + BlockNumber blkno; + + *ntids = 0; + + LockBuffer(metabuffer, GIN_SHARE); + blkno = GinPageGetMeta(BufferGetPage(metabuffer))->head; + + /* + * fetch head of list before unlocking metapage. head page must be pinned + * to prevent deletion by vacuum process + */ + if (blkno == InvalidBlockNumber) + { + /* No pending list, so proceed with normal scan */ + UnlockReleaseBuffer(metabuffer); + return; + } + + pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno); + LockBuffer(pos.pendingBuffer, GIN_SHARE); + pos.firstOffset = FirstOffsetNumber; + UnlockReleaseBuffer(metabuffer); + pos.hasMatchKey = palloc(sizeof(bool) * so->nkeys); + + /* + * loop for each heap row. scanGetCandidate returns full row or row's + * tuples from first page. + */ + while (scanGetCandidate(scan, &pos)) + { + /* + * 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 (!collectMatchesForHeapRow(scan, &pos)) + continue; + + /* + * Matching of entries of one row is finished, so check row using + * consistent functions. + */ + oldCtx = MemoryContextSwitchTo(so->tempCtx); + recheck = false; + match = true; + + for (i = 0; i < so->nkeys; i++) + { + GinScanKey key = so->keys + i; + + if (!callConsistentFn(&so->ginstate, key)) + { + match = false; + break; + } + recheck |= key->recheckCurItem; + } + + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(so->tempCtx); + + if (match) + { + tbm_add_tuples(tbm, &pos.item, 1, recheck); + (*ntids)++; + } + } + + pfree(pos.hasMatchKey); +} + + #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 5b146d6c26..af5068906f 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); - - gdi = ginPrepareScanPostingTree(index, postingRoot, FALSE); - gdi->btree.isBuild = (buildStats != NULL); - - ginInsertItemPointer(gdi, items, nitem, buildStats); - - pfree(gdi); + /* + * 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++; + + /* Now insert the TIDs-to-be-added into the posting tree */ + gdi = ginPrepareScanPostingTree(ginstate->index, postingRoot, FALSE); + gdi->btree.isBuild = (buildStats != NULL); + + 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; } /* - * Inserts only one entry to the index, but it can add more than 1 ItemPointer. + * 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; +} + +/* + * 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); - ginHeapTupleFastInsert(index, &ginstate, &collector); + for (i = 0; i < ginstate.origTupdesc->natts; i++) + ginHeapTupleFastCollect(&ginstate, &collector, + (OffsetNumber) (i + 1), + values[i], isnull[i], + ht_ctid); + + 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 2a39c4b383..c9cf77514a 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); - key->strategy = strategy; - key->attnum = attnum; - key->extra_data = extra_data; + /* 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->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; + GinScanEntry scanEntry = key->scanEntry + i; - /* 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) + 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; - fillScanKey(&so->ginstate, &(so->keys[nkeys]), - skey->sk_attno, skey->sk_argument, - entryValues, partial_matches, nEntryValues, - skey->sk_strategy, extra_data); + 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 */ + + 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 4674606f79..0a7c1c521f 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); + 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, - index->rd_att->attrs[i]->atttypid, - index->rd_att->attrs[i]->atttypmod, - index->rd_att->attrs[i]->attndims - ); + 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; + bool *nullFlags; + int32 i; - entries = (Datum *) DatumGetPointer(FunctionCall2( - &ginstate->extractValueFn[attnum - 1], - value, - PointerGetDatum(nentries) - )); - - if (entries == NULL) - *nentries = 0; - - *needUnique = FALSE; - if (*nentries > 1) + /* + * 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; -} + /* 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))); - -Datum * -ginExtractEntriesSU(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries) -{ - bool needUnique; - Datum *entries = ginExtractEntriesS(ginstate, attnum, value, nentries, - &needUnique); - - if (needUnique) + /* + * Generate a placeholder if the item contained no keys. + */ + if (entries == NULL || *nentries <= 0) { - Datum *ptr, - *res; + *nentries = 1; + entries = (Datum *) palloc(sizeof(Datum)); + entries[0] = (Datum) 0; + *categories = (GinNullCategory *) palloc(sizeof(GinNullCategory)); + (*categories)[0] = GIN_CAT_EMPTY_ITEM; + return entries; + } - ptr = res = entries; + /* + * 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; - while (ptr - entries < *nentries) + /* + * 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) + { + keyEntryData *keydata; + cmpEntriesArg arg; + + keydata = (keyEntryData *) palloc(*nentries * sizeof(keyEntryData)); + for (i = 0; i < *nentries; i++) { - if (ginCompareEntries(ginstate, attnum, *ptr, *res) != 0) - *(++res) = *ptr++; - else - ptr++; + keydata[i].datum = entries[i]; + keydata[i].isnull = nullFlags[i]; } - *nentries = res + 1 - entries; + arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1]; + arg.haveDups = false; + qsort_arg(keydata, *nentries, sizeof(keyEntryData), + cmpEntries, (void *) &arg); + + 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 + { + /* easy, no duplicates */ + for (i = 0; i < *nentries; i++) + { + entries[i] = keydata[i].datum; + nullFlags[i] = keydata[i].isnull; + } + } + + 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 3054030a2c..41ad382df0 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 36f0233baa..e410959b85 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 d07454af68..cf9603c7c4 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. + * searchMode settings for extractQueryFn. */ -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 - */ -#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 0000000000..48531845e0 --- /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 07a72b208e..4cb6c5c17a 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 12a420fe22..394d851fc3 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 4d86f454f9..7b05ce3354 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 1d1470a25d..d586f69ebb 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 t = '{}' ORDER BY seqno; + seqno | i | t +-------+----+---- + 101 | {} | {} +(1 row) -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 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) + 101 | {} | {} + 102 | {NULL} | {NULL} +(102 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 t && '{}' ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 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} +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 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_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_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 b0c096d9e5..9ea53b1544 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 043f433eb0..97c1beb358 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;