Allow HOT updates for some expression indexes
authorSimon Riggs <simon@2ndQuadrant.com>
Tue, 27 Mar 2018 18:57:02 +0000 (19:57 +0100)
committerSimon Riggs <simon@2ndQuadrant.com>
Tue, 27 Mar 2018 18:57:02 +0000 (19:57 +0100)
If the value of an index expression is unchanged after UPDATE,
allow HOT updates where previously we disallowed them, giving
a significant performance boost in those cases.

Particularly useful for indexes such as JSON->>field where the
JSON value changes but the indexed value does not.

Submitted as "surjective indexes" patch, now enabled by use
of new "recheck_on_update" parameter.

Author: Konstantin Knizhnik
Reviewer: Simon Riggs, with much wordsmithing and some cleanup

13 files changed:
doc/src/sgml/ref/create_index.sgml
src/backend/access/common/reloptions.c
src/backend/access/heap/heapam.c
src/backend/catalog/index.c
src/backend/utils/cache/relcache.c
src/bin/psql/tab-complete.c
src/include/access/reloptions.h
src/include/utils/rel.h
src/include/utils/relcache.h
src/test/regress/expected/func_index.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/func_index.sql [new file with mode: 0644]

index 1fd21e12bd70d8f2d93253ce48b5cdd4193c0edf..ba1c5d639253bc50fc416408dbc443ff6c5433ac 100644 (file)
@@ -309,8 +309,41 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
    <para>
     The optional <literal>WITH</literal> clause specifies <firstterm>storage
     parameters</firstterm> for the index.  Each index method has its own set of allowed
-    storage parameters.  The B-tree, hash, GiST and SP-GiST index methods all
-    accept this parameter:
+    storage parameters.  All indexes accept the following parameter:
+   </para>
+
+   <variablelist>
+   <varlistentry>
+    <term><literal>recheck_on_update</literal></term>
+    <listitem>
+     <para>
+       Specifies whether to recheck a functional index value to see whether
+       we can use a HOT update or not. The default value is on for functional
+       indexes with an total expression cost less than 1000, otherwise off.
+       You might decide to turn this off if you knew that a function used in
+       an index is unlikely to return the same value when one of the input
+       columns is updated and so the recheck is not worth the additional cost
+       of executing the function.
+     </para>
+       
+     <para>
+       Functional indexes are used frequently for the case where the function
+       returns a subset of the argument. Examples of this would be accessing
+       part of a string with <literal>SUBSTR()</literal> or accessing a single
+       field in a JSON document using an expression such as
+       <literal>(bookinfo-&gt;&gt;'isbn')</literal>. In this example, the JSON
+       document might be updated frequently, yet it is uncommon for the ISBN
+       field for a book to change so we would keep the parameter set to on
+       for that index. A more frequently changing field might have an index
+       with this parameter turned off, while very frequently changing fields
+       might be better to avoid indexing at all under high load.
+     </para>
+    </listitem>
+   </varlistentry>
+   </variablelist>
+
+   <para>
+     The B-tree, hash, GiST and SP-GiST index methods all accept this parameter:
    </para>
 
    <variablelist>
index 46276ceff1cdbc373ca4df245451e15f3c64bd9a..35c09987adb783541b07dd98f7cbe4754cdd72a2 100644 (file)
@@ -129,6 +129,15 @@ static relopt_bool boolRelOpts[] =
        },
        true
    },
+   {
+       {
+           "recheck_on_update",
+           "Recheck functional index expression for changed value after update",
+           RELOPT_KIND_INDEX,
+           ShareUpdateExclusiveLock    /* since only applies to later UPDATEs */
+       },
+       true
+   },
    {
        {
            "security_barrier",
@@ -1310,7 +1319,7 @@ fillRelOptions(void *rdopts, Size basesize,
                break;
            }
        }
-       if (validate && !found)
+       if (validate && !found && options[i].gen->kinds != RELOPT_KIND_INDEX)
            elog(ERROR, "reloption \"%s\" not found in parse table",
                 options[i].gen->name);
    }
@@ -1466,6 +1475,40 @@ index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate)
    return amoptions(reloptions, validate);
 }
 
+/*
+ * Parse generic options for all indexes.
+ *
+ * reloptions  options as text[] datum
+ * validate    error flag
+ */
+bytea *
+index_generic_reloptions(Datum reloptions, bool validate)
+{
+   int              numoptions;
+   GenericIndexOpts *idxopts;
+   relopt_value     *options;
+   static const relopt_parse_elt tab[] = {
+       {"recheck_on_update", RELOPT_TYPE_BOOL, offsetof(GenericIndexOpts, recheck_on_update)}
+   };
+
+   options = parseRelOptions(reloptions, validate,
+                             RELOPT_KIND_INDEX,
+                             &numoptions);
+
+   /* if none set, we're done */
+   if (numoptions == 0)
+       return NULL;
+
+   idxopts = allocateReloptStruct(sizeof(GenericIndexOpts), options, numoptions);
+
+   fillRelOptions((void *)idxopts, sizeof(GenericIndexOpts), options, numoptions,
+                  validate, tab, lengthof(tab));
+
+   pfree(options);
+
+   return (bytea*) idxopts;
+}
+
 /*
  * Option parser for attribute reloptions
  */
index c08ab14c02b51e5ba903ef2ce0450f91887b9fa4..d7279248e70027472764923a7da7fc7f0e5c5c7d 100644 (file)
@@ -56,6 +56,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/namespace.h"
+#include "catalog/index.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -74,7 +75,9 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
-
+#include "utils/memutils.h"
+#include "nodes/execnodes.h"
+#include "executor/executor.h"
 
 /* GUC variable */
 bool       synchronize_seqscans = true;
@@ -126,6 +129,7 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
                       bool *copy);
+static bool ProjIndexIsUnchanged(Relation relation, HeapTuple oldtup, HeapTuple newtup);
 
 
 /*
@@ -3508,6 +3512,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
    HTSU_Result result;
    TransactionId xid = GetCurrentTransactionId();
    Bitmapset  *hot_attrs;
+   Bitmapset  *proj_idx_attrs;
    Bitmapset  *key_attrs;
    Bitmapset  *id_attrs;
    Bitmapset  *interesting_attrs;
@@ -3571,12 +3576,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
     * Note that we get copies of each bitmap, so we need not worry about
     * relcache flush happening midway through.
     */
-   hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
+   hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_HOT);
+   proj_idx_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_PROJ);
    key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
    id_attrs = RelationGetIndexAttrBitmap(relation,
                                          INDEX_ATTR_BITMAP_IDENTITY_KEY);
-
-
    block = ItemPointerGetBlockNumber(otid);
    buffer = ReadBuffer(relation, block);
    page = BufferGetPage(buffer);
@@ -3596,6 +3600,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
    if (!PageIsFull(page))
    {
        interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
+       interesting_attrs = bms_add_members(interesting_attrs, proj_idx_attrs);
        hot_attrs_checked = true;
    }
    interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
@@ -3894,6 +3899,7 @@ l2:
        if (vmbuffer != InvalidBuffer)
            ReleaseBuffer(vmbuffer);
        bms_free(hot_attrs);
+       bms_free(proj_idx_attrs);
        bms_free(key_attrs);
        bms_free(id_attrs);
        bms_free(modified_attrs);
@@ -4201,11 +4207,18 @@ l2:
        /*
         * Since the new tuple is going into the same page, we might be able
         * to do a HOT update.  Check if any of the index columns have been
-        * changed. If the page was already full, we may have skipped checking
-        * for index columns. If so, HOT update is possible.
+        * changed, or if we have projection functional indexes, check whether
+        * the old and the new values are the same.   If the page was already
+        * full, we may have skipped checking for index columns. If so, HOT
+        * update is possible.
         */
-       if (hot_attrs_checked && !bms_overlap(modified_attrs, hot_attrs))
+       if (hot_attrs_checked
+           && !bms_overlap(modified_attrs, hot_attrs)
+           && (!bms_overlap(modified_attrs, proj_idx_attrs)
+               || ProjIndexIsUnchanged(relation, &oldtup, newtup)))
+       {
            use_hot_update = true;
+       }
    }
    else
    {
@@ -4367,6 +4380,7 @@ l2:
        heap_freetuple(old_key_tuple);
 
    bms_free(hot_attrs);
+   bms_free(proj_idx_attrs);
    bms_free(key_attrs);
    bms_free(id_attrs);
    bms_free(modified_attrs);
@@ -4453,6 +4467,83 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
    }
 }
 
+/*
+ * Check whether the value is unchanged after update of a projection
+ * functional index. Compare the new and old values of the indexed
+ * expression to see if we are able to use a HOT update or not.
+ */
+static bool ProjIndexIsUnchanged(Relation relation, HeapTuple oldtup, HeapTuple newtup)
+{
+   ListCell       *l;
+   List           *indexoidlist = RelationGetIndexList(relation);
+   EState         *estate = CreateExecutorState();
+   ExprContext    *econtext = GetPerTupleExprContext(estate);
+   TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(relation));
+   bool            equals = true;
+   Datum           old_values[INDEX_MAX_KEYS];
+   bool            old_isnull[INDEX_MAX_KEYS];
+   Datum           new_values[INDEX_MAX_KEYS];
+   bool            new_isnull[INDEX_MAX_KEYS];
+   int             indexno = 0;
+   econtext->ecxt_scantuple = slot;
+
+   foreach(l, indexoidlist)
+   {
+       if (bms_is_member(indexno, relation->rd_projidx))
+       {
+           Oid         indexOid = lfirst_oid(l);
+           Relation    indexDesc = index_open(indexOid, AccessShareLock);
+           IndexInfo  *indexInfo = BuildIndexInfo(indexDesc);
+           int         i;
+
+           ResetExprContext(econtext);
+           ExecStoreTuple(oldtup, slot, InvalidBuffer, false);
+           FormIndexDatum(indexInfo,
+                          slot,
+                          estate,
+                          old_values,
+                          old_isnull);
+
+           ExecStoreTuple(newtup, slot, InvalidBuffer, false);
+           FormIndexDatum(indexInfo,
+                          slot,
+                          estate,
+                          new_values,
+                          new_isnull);
+
+           for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+           {
+               if (old_isnull[i] != new_isnull[i])
+               {
+                   equals = false;
+                   break;
+               }
+               else if (!old_isnull[i])
+               {
+                   Form_pg_attribute att = TupleDescAttr(RelationGetDescr(indexDesc), i);
+                   if (!datumIsEqual(old_values[i], new_values[i], att->attbyval, att->attlen))
+                   {
+                       equals = false;
+                       break;
+                   }
+               }
+           }
+           index_close(indexDesc, AccessShareLock);
+
+           if (!equals)
+           {
+               break;
+           }
+       }
+       indexno += 1;
+   }
+   ExecDropSingleTupleTableSlot(slot);
+   FreeExecutorState(estate);
+
+   return equals;
+}
+
+
 /*
  * Check which columns are being updated.
  *
index bfac37f9d17e29968c255e2c23232aa51574a2db..dee1a8ac7832e109125cc108b467d099f27b87eb 100644 (file)
@@ -26,6 +26,7 @@
 #include "access/amapi.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
+#include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
@@ -3863,7 +3864,7 @@ reindex_relation(Oid relid, int flags, int options)
 
    /* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
    if (is_pg_class)
-       (void) RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_ALL);
+       (void) RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_HOT);
 
    PG_TRY();
    {
index 6ab4db26bdd6b0ff4b64c525a0f2bdde36c0c2db..465130244059431ddea751b05469bcb725521a3a 100644 (file)
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/cost.h"
 #include "optimizer/prep.h"
 #include "optimizer/var.h"
+#include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
@@ -2314,9 +2316,11 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
    list_free_deep(relation->rd_fkeylist);
    list_free(relation->rd_indexlist);
    bms_free(relation->rd_indexattr);
+   bms_free(relation->rd_projindexattr);
    bms_free(relation->rd_keyattr);
    bms_free(relation->rd_pkattr);
    bms_free(relation->rd_idattr);
+   bms_free(relation->rd_projidx);
    if (relation->rd_pubactions)
        pfree(relation->rd_pubactions);
    if (relation->rd_options)
@@ -4799,6 +4803,73 @@ RelationGetIndexPredicate(Relation relation)
    return result;
 }
 
+#define HEURISTIC_MAX_HOT_RECHECK_EXPR_COST 1000
+
+/*
+ * Check if functional index is projection: index expression returns some subset
+ * of its argument values. During HOT update check we handle projection indexes
+ * differently: instead of checking if any of attributes used in indexed
+ * expression were updated, we calculate and compare values of index expression
+ * for old and new tuple values.
+ *
+ * Decision made by this function is based on two sources:
+ * 1. Calculated cost of index expression: if greater than some heuristic limit
+     then extra comparison of index expression values is expected to be too
+     expensive, so we don't attempt it by default.
+ * 2. "recheck_on_update" index option explicitly set by user, which overrides 1)
+ */
+static bool IsProjectionFunctionalIndex(Relation index, IndexInfo* ii)
+{
+   bool is_projection = false;
+
+   if (ii->ii_Expressions)
+   {
+       HeapTuple       tuple;
+       Datum           reloptions;
+       bool            isnull;
+       QualCost index_expr_cost;
+
+       /* by default functional index is considered as non-injective */
+       is_projection = true;
+
+       cost_qual_eval(&index_expr_cost, ii->ii_Expressions, NULL);
+
+       /*
+        * If index expression is too expensive, then disable projection
+        * optimization, because extra evaluation of index expression is
+        * expected to be more expensive than index update.  Currently the
+        * projection optimization has to calculate index expression twice
+        * when the value of index expression has not changed and three times
+        * when values differ because the expression is recalculated when
+        * inserting a new index entry for the changed value.
+        */
+       if ((index_expr_cost.startup + index_expr_cost.per_tuple) >
+                               HEURISTIC_MAX_HOT_RECHECK_EXPR_COST)
+           is_projection = false;
+
+       tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(RelationGetRelid(index)));
+       if (!HeapTupleIsValid(tuple))
+           elog(ERROR, "cache lookup failed for relation %u", RelationGetRelid(index));
+
+       reloptions = SysCacheGetAttr(RELOID, tuple,
+                                    Anum_pg_class_reloptions, &isnull);
+       if (!isnull)
+       {
+           GenericIndexOpts *idxopts;
+
+           idxopts = (GenericIndexOpts *) index_generic_reloptions(reloptions, false);
+
+           if (idxopts != NULL)
+           {
+               is_projection = idxopts->recheck_on_update;
+               pfree(idxopts);
+           }
+       }
+       ReleaseSysCache(tuple);
+   }
+   return is_projection;
+}
+
 /*
  * RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
  *
@@ -4826,24 +4897,29 @@ RelationGetIndexPredicate(Relation relation)
 Bitmapset *
 RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 {
-   Bitmapset  *indexattrs;     /* indexed columns */
+   Bitmapset  *indexattrs;     /* columns used in non-projection indexes */
+   Bitmapset  *projindexattrs; /* columns used in projection indexes */
    Bitmapset  *uindexattrs;    /* columns in unique indexes */
    Bitmapset  *pkindexattrs;   /* columns in the primary index */
    Bitmapset  *idindexattrs;   /* columns in the replica identity */
+   Bitmapset  *projindexes;    /* projection indexes */
    List       *indexoidlist;
    List       *newindexoidlist;
    Oid         relpkindex;
    Oid         relreplindex;
    ListCell   *l;
    MemoryContext oldcxt;
+   int         indexno;
 
    /* Quick exit if we already computed the result. */
    if (relation->rd_indexattr != NULL)
    {
        switch (attrKind)
        {
-           case INDEX_ATTR_BITMAP_ALL:
+           case INDEX_ATTR_BITMAP_HOT:
                return bms_copy(relation->rd_indexattr);
+           case INDEX_ATTR_BITMAP_PROJ:
+               return bms_copy(relation->rd_projindexattr);
            case INDEX_ATTR_BITMAP_KEY:
                return bms_copy(relation->rd_keyattr);
            case INDEX_ATTR_BITMAP_PRIMARY_KEY:
@@ -4890,9 +4966,12 @@ restart:
     * won't be returned at all by RelationGetIndexList.
     */
    indexattrs = NULL;
+   projindexattrs = NULL;
    uindexattrs = NULL;
    pkindexattrs = NULL;
    idindexattrs = NULL;
+   projindexes = NULL;
+   indexno = 0;
    foreach(l, indexoidlist)
    {
        Oid         indexOid = lfirst_oid(l);
@@ -4943,13 +5022,22 @@ restart:
            }
        }
 
-       /* Collect all attributes used in expressions, too */
-       pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
-
+       /* Collect attributes used in expressions, too */
+       if (IsProjectionFunctionalIndex(indexDesc, indexInfo))
+       {
+           projindexes = bms_add_member(projindexes, indexno);
+           pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &projindexattrs);
+       }
+       else
+       {
+           /* Collect all attributes used in expressions, too */
+           pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+       }
        /* Collect all attributes in the index predicate, too */
        pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
 
        index_close(indexDesc, AccessShareLock);
+       indexno += 1;
    }
 
    /*
@@ -4976,6 +5064,8 @@ restart:
        bms_free(pkindexattrs);
        bms_free(idindexattrs);
        bms_free(indexattrs);
+       bms_free(projindexattrs);
+       bms_free(projindexes);
 
        goto restart;
    }
@@ -4983,12 +5073,16 @@ restart:
    /* Don't leak the old values of these bitmaps, if any */
    bms_free(relation->rd_indexattr);
    relation->rd_indexattr = NULL;
+   bms_free(relation->rd_projindexattr);
+   relation->rd_projindexattr = NULL;
    bms_free(relation->rd_keyattr);
    relation->rd_keyattr = NULL;
    bms_free(relation->rd_pkattr);
    relation->rd_pkattr = NULL;
    bms_free(relation->rd_idattr);
    relation->rd_idattr = NULL;
+   bms_free(relation->rd_projidx);
+   relation->rd_projidx = NULL;
 
    /*
     * Now save copies of the bitmaps in the relcache entry.  We intentionally
@@ -5002,13 +5096,17 @@ restart:
    relation->rd_pkattr = bms_copy(pkindexattrs);
    relation->rd_idattr = bms_copy(idindexattrs);
    relation->rd_indexattr = bms_copy(indexattrs);
+   relation->rd_projindexattr = bms_copy(projindexattrs);
+   relation->rd_projidx = bms_copy(projindexes);
    MemoryContextSwitchTo(oldcxt);
 
    /* We return our original working copy for caller to play with */
    switch (attrKind)
    {
-       case INDEX_ATTR_BITMAP_ALL:
+       case INDEX_ATTR_BITMAP_HOT:
            return indexattrs;
+       case INDEX_ATTR_BITMAP_PROJ:
+           return projindexattrs;
        case INDEX_ATTR_BITMAP_KEY:
            return uindexattrs;
        case INDEX_ATTR_BITMAP_PRIMARY_KEY:
@@ -5632,9 +5730,11 @@ load_relcache_init_file(bool shared)
        rel->rd_pkindex = InvalidOid;
        rel->rd_replidindex = InvalidOid;
        rel->rd_indexattr = NULL;
+       rel->rd_projindexattr = NULL;
        rel->rd_keyattr = NULL;
        rel->rd_pkattr = NULL;
        rel->rd_idattr = NULL;
+       rel->rd_projidx = NULL;
        rel->rd_pubactions = NULL;
        rel->rd_statvalid = false;
        rel->rd_statlist = NIL;
index 08d8ef09a4c7c85bcc46eee74297ea10bddc82d9..6926ca132e7b16b1537ef8291c1b8e2d31c911ed 100644 (file)
@@ -1855,13 +1855,13 @@ psql_completion(const char *text, int start, int end)
        COMPLETE_WITH_CONST("(");
    /* ALTER INDEX <foo> SET|RESET ( */
    else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
-       COMPLETE_WITH_LIST6("fillfactor",
+       COMPLETE_WITH_LIST7("fillfactor", "recheck_on_update",
                            "fastupdate", "gin_pending_list_limit", /* GIN */
                            "buffering",    /* GiST */
                            "pages_per_range", "autosummarize"  /* BRIN */
            );
    else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
-       COMPLETE_WITH_LIST6("fillfactor =",
+       COMPLETE_WITH_LIST7("fillfactor =", "recheck_on_update =",
                            "fastupdate =", "gin_pending_list_limit =", /* GIN */
                            "buffering =",  /* GiST */
                            "pages_per_range =", "autosummarize ="  /* BRIN */
index b32c1e9efe57e41bc9e3b08d28ca3602021aba07..ef09611e0d624caeba6b0f932602d2d438f32ea7 100644 (file)
@@ -51,6 +51,7 @@ typedef enum relopt_kind
    RELOPT_KIND_PARTITIONED = (1 << 11),
    /* if you add a new kind, make sure you update "last_default" too */
    RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_PARTITIONED,
+   RELOPT_KIND_INDEX = RELOPT_KIND_BTREE|RELOPT_KIND_HASH|RELOPT_KIND_GIN|RELOPT_KIND_SPGIST,
    /* some compilers treat enums as signed ints, so we can't use 1 << 31 */
    RELOPT_KIND_MAX = (1 << 30)
 } relopt_kind;
@@ -276,6 +277,7 @@ extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);
 extern bytea *view_reloptions(Datum reloptions, bool validate);
 extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions,
                 bool validate);
+extern bytea *index_generic_reloptions(Datum reloptions, bool validate);
 extern bytea *attribute_reloptions(Datum reloptions, bool validate);
 extern bytea *tablespace_reloptions(Datum reloptions, bool validate);
 extern LOCKMODE AlterTableGetRelOptionsLockLevel(List *defList);
index aa8add544a1d3798231c75d3eb6e088d200e7038..c26c395b0bd47baeb224ee1ebbdbbe5852377350 100644 (file)
@@ -141,10 +141,12 @@ typedef struct RelationData
    List       *rd_statlist;    /* list of OIDs of extended stats */
 
    /* data managed by RelationGetIndexAttrBitmap: */
-   Bitmapset  *rd_indexattr;   /* identifies columns used in indexes */
+   Bitmapset  *rd_indexattr;   /* columns used in non-projection indexes */
+   Bitmapset  *rd_projindexattr;   /* columns used in projection indexes */
    Bitmapset  *rd_keyattr;     /* cols that can be ref'd by foreign keys */
    Bitmapset  *rd_pkattr;      /* cols included in primary key */
    Bitmapset  *rd_idattr;      /* included in replica identity index */
+   Bitmapset  *rd_projidx;     /* Oids of projection indexes */
 
    PublicationActions *rd_pubactions;  /* publication actions */
 
@@ -245,6 +247,14 @@ typedef struct ForeignKeyCacheInfo
    Oid         conpfeqop[INDEX_MAX_KEYS];  /* PK = FK operator OIDs */
 } ForeignKeyCacheInfo;
 
+/*
+ * Options common for all all indexes
+ */
+typedef struct GenericIndexOpts
+{
+   int32       vl_len_;
+   bool        recheck_on_update;
+} GenericIndexOpts;
 
 /*
  * StdRdOptions
index 8a546aba283de37539317bb480fe958c9975af51..dbbf41b0c16d8008f42909a294bf6933d3321215 100644 (file)
@@ -53,7 +53,8 @@ extern List *RelationGetIndexPredicate(Relation relation);
 
 typedef enum IndexAttrBitmapKind
 {
-   INDEX_ATTR_BITMAP_ALL,
+   INDEX_ATTR_BITMAP_HOT,
+   INDEX_ATTR_BITMAP_PROJ,
    INDEX_ATTR_BITMAP_KEY,
    INDEX_ATTR_BITMAP_PRIMARY_KEY,
    INDEX_ATTR_BITMAP_IDENTITY_KEY
diff --git a/src/test/regress/expected/func_index.out b/src/test/regress/expected/func_index.out
new file mode 100644 (file)
index 0000000..e616ea2
--- /dev/null
@@ -0,0 +1,61 @@
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name')) with (recheck_on_update=false);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   0
+(1 row)
+
+drop table keyvalue;
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name')) with (recheck_on_update=true);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   1
+(1 row)
+
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+ pg_stat_get_xact_tuples_hot_updated 
+-------------------------------------
+                                   2
+(1 row)
+
+drop table keyvalue;
index d308a05117d3b38206c4f6d43d00fe2e88f79a7c..fda54d4b67d63f99c0286f10af2318d346890007 100644 (file)
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index
 
 # ----------
 # Another group of parallel tests
index 45147e9328aaab6ff207a0a72fcd8438038ffead..f79e31d02864da2124b315fff75f1e829b5cd079 100644 (file)
@@ -101,6 +101,7 @@ test: portals
 test: arrays
 test: btree_index
 test: hash_index
+test: func_index
 test: update
 test: delete
 test: namespace
diff --git a/src/test/regress/sql/func_index.sql b/src/test/regress/sql/func_index.sql
new file mode 100644 (file)
index 0000000..b08f754
--- /dev/null
@@ -0,0 +1,30 @@
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name')) with (recheck_on_update=false);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+drop table keyvalue;
+
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name')) with (recheck_on_update=true);
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+drop table keyvalue;
+
+create table keyvalue(id integer primary key, info jsonb);
+create index nameindex on keyvalue((info->>'name'));
+insert into keyvalue values (1, '{"name": "john", "data": "some data"}');
+update keyvalue set info='{"name": "john", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+update keyvalue set info='{"name": "smith", "data": "some other data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+update keyvalue set info='{"name": "smith", "data": "some more data"}' where id=1;
+select pg_stat_get_xact_tuples_hot_updated('keyvalue'::regclass);
+drop table keyvalue;
+
+