Allow HOT updates for some expression indexes
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
This commit is contained in:
parent
1944cdc982
commit
c203d6cf81
@ -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->>'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>
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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();
|
||||
{
|
||||
|
112
src/backend/utils/cache/relcache.c
vendored
112
src/backend/utils/cache/relcache.c
vendored
@ -69,8 +69,10 @@
|
||||
#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;
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
61
src/test/regress/expected/func_index.out
Normal file
61
src/test/regress/expected/func_index.out
Normal file
@ -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;
|
@ -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
|
||||
|
@ -101,6 +101,7 @@ test: portals
|
||||
test: arrays
|
||||
test: btree_index
|
||||
test: hash_index
|
||||
test: func_index
|
||||
test: update
|
||||
test: delete
|
||||
test: namespace
|
||||
|
30
src/test/regress/sql/func_index.sql
Normal file
30
src/test/regress/sql/func_index.sql
Normal file
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user