OSDN Git Service

Fix GIN to support null keys, empty and null items, and full index scans.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 8 Jan 2011 00:16:24 +0000 (19:16 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 8 Jan 2011 00:16:24 +0000 (19:16 -0500)
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.

23 files changed:
contrib/hstore/hstore_gin.c
doc/src/sgml/gin.sgml
src/backend/access/gin/README
src/backend/access/gin/ginarrayproc.c
src/backend/access/gin/ginbtree.c
src/backend/access/gin/ginbulk.c
src/backend/access/gin/gindatapage.c
src/backend/access/gin/ginentrypage.c
src/backend/access/gin/ginfast.c
src/backend/access/gin/ginget.c
src/backend/access/gin/gininsert.c
src/backend/access/gin/ginscan.c
src/backend/access/gin/ginutil.c
src/backend/access/gin/ginvacuum.c
src/backend/access/gin/ginxlog.c
src/include/access/gin.h
src/include/access/gin_private.h [new file with mode: 0644]
src/include/utils/builtins.h
src/test/regress/data/array.data
src/test/regress/expected/arrays.out
src/test/regress/expected/create_index.out
src/test/regress/sql/arrays.sql
src/test/regress/sql/create_index.sql

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