]> granicus.if.org Git - postgresql/commitdiff
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;
+
+