Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit cf25498

Browse files
committed
Fix a crash in logical replication
The bug was that determining which columns are part of the replica identity index using RelationGetIndexAttrBitmap() would run eval_const_expressions() on index expressions and predicates across all indexes of the table, which in turn might require a snapshot, but there wasn't one set, so it crashes. There were actually two separate bugs, one on the publisher and one on the subscriber. To trigger the bug, a table that is part of a publication or subscription needs to have an index with a predicate or expression that lends itself to constant expressions simplification. The fix is to avoid the constant expressions simplification in RelationGetIndexAttrBitmap(), so that it becomes safe to call in these contexts. The constant expressions simplification comes from the calls to RelationGetIndexExpressions()/RelationGetIndexPredicate() via BuildIndexInfo(). But RelationGetIndexAttrBitmap() calling BuildIndexInfo() is overkill. The latter just takes pg_index catalog information, packs it into the IndexInfo structure, which former then just unpacks again and throws away. We can just do this directly with less overhead and skip the troublesome calls to eval_const_expressions(). This also removes the awkward cross-dependency between relcache.c and index.c. Bug: #15114 Reported-by: Петър Славов <pet.slavov@gmail.com> Reviewed-by: Noah Misch <noah@leadboat.com> Reviewed-by: Michael Paquier <michael@paquier.xyz> Discussion: https://www.postgresql.org/message-id/flat/152110589574.1223.17983600132321618383@wrigleys.postgresql.org/
1 parent ff9e63c commit cf25498

File tree

2 files changed

+106
-19
lines changed

2 files changed

+106
-19
lines changed

src/backend/utils/cache/relcache.c

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
#include "access/xact.h"
4141
#include "access/xlog.h"
4242
#include "catalog/catalog.h"
43-
#include "catalog/index.h"
4443
#include "catalog/indexing.h"
4544
#include "catalog/namespace.h"
4645
#include "catalog/partition.h"
@@ -4700,12 +4699,12 @@ RelationGetIndexPredicate(Relation relation)
47004699
* 2. "recheck_on_update" index option explicitly set by user, which overrides 1)
47014700
*/
47024701
static bool
4703-
IsProjectionFunctionalIndex(Relation index, IndexInfo *ii)
4702+
IsProjectionFunctionalIndex(Relation index)
47044703
{
47054704
bool is_projection = false;
47064705

47074706
#ifdef NOT_USED
4708-
if (ii->ii_Expressions)
4707+
if (RelationGetIndexExpressions(index))
47094708
{
47104709
HeapTuple tuple;
47114710
Datum reloptions;
@@ -4715,7 +4714,7 @@ IsProjectionFunctionalIndex(Relation index, IndexInfo *ii)
47154714
/* by default functional index is considered as non-injective */
47164715
is_projection = true;
47174716

4718-
cost_qual_eval(&index_expr_cost, ii->ii_Expressions, NULL);
4717+
cost_qual_eval(&index_expr_cost, RelationGetIndexExpressions(index), NULL);
47194718

47204719
/*
47214720
* If index expression is too expensive, then disable projection
@@ -4861,21 +4860,44 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
48614860
{
48624861
Oid indexOid = lfirst_oid(l);
48634862
Relation indexDesc;
4864-
IndexInfo *indexInfo;
4863+
Datum datum;
4864+
bool isnull;
4865+
Node *indexExpressions;
4866+
Node *indexPredicate;
48654867
int i;
48664868
bool isKey; /* candidate key */
48674869
bool isPK; /* primary key */
48684870
bool isIDKey; /* replica identity index */
48694871

48704872
indexDesc = index_open(indexOid, AccessShareLock);
48714873

4872-
/* Extract index key information from the index's pg_index row */
4873-
indexInfo = BuildIndexInfo(indexDesc);
4874+
/*
4875+
* Extract index expressions and index predicate. Note: Don't use
4876+
* RelationGetIndexExpressions()/RelationGetIndexPredicate(), because
4877+
* those might run constant expressions evaluation, which needs a
4878+
* snapshot, which we might not have here. (Also, it's probably more
4879+
* sound to collect the bitmaps before any transformations that might
4880+
* eliminate columns, but the practical impact of this is limited.)
4881+
*/
4882+
4883+
datum = heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indexprs,
4884+
GetPgIndexDescriptor(), &isnull);
4885+
if (!isnull)
4886+
indexExpressions = stringToNode(TextDatumGetCString(datum));
4887+
else
4888+
indexExpressions = NULL;
4889+
4890+
datum = heap_getattr(indexDesc->rd_indextuple, Anum_pg_index_indpred,
4891+
GetPgIndexDescriptor(), &isnull);
4892+
if (!isnull)
4893+
indexPredicate = stringToNode(TextDatumGetCString(datum));
4894+
else
4895+
indexPredicate = NULL;
48744896

48754897
/* Can this index be referenced by a foreign key? */
4876-
isKey = indexInfo->ii_Unique &&
4877-
indexInfo->ii_Expressions == NIL &&
4878-
indexInfo->ii_Predicate == NIL;
4898+
isKey = indexDesc->rd_index->indisunique &&
4899+
indexExpressions == NULL &&
4900+
indexPredicate == NULL;
48794901

48804902
/* Is this a primary key? */
48814903
isPK = (indexOid == relpkindex);
@@ -4884,9 +4906,9 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
48844906
isIDKey = (indexOid == relreplindex);
48854907

48864908
/* Collect simple attribute references */
4887-
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
4909+
for (i = 0; i < indexDesc->rd_index->indnatts; i++)
48884910
{
4889-
int attrnum = indexInfo->ii_IndexAttrNumbers[i];
4911+
int attrnum = indexDesc->rd_index->indkey.values[i];
48904912

48914913
/*
48924914
* Since we have covering indexes with non-key columns, we must
@@ -4901,33 +4923,33 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
49014923
indexattrs = bms_add_member(indexattrs,
49024924
attrnum - FirstLowInvalidHeapAttributeNumber);
49034925

4904-
if (isKey && i < indexInfo->ii_NumIndexKeyAttrs)
4926+
if (isKey && i < indexDesc->rd_index->indnkeyatts)
49054927
uindexattrs = bms_add_member(uindexattrs,
49064928
attrnum - FirstLowInvalidHeapAttributeNumber);
49074929

4908-
if (isPK && i < indexInfo->ii_NumIndexKeyAttrs)
4930+
if (isPK && i < indexDesc->rd_index->indnkeyatts)
49094931
pkindexattrs = bms_add_member(pkindexattrs,
49104932
attrnum - FirstLowInvalidHeapAttributeNumber);
49114933

4912-
if (isIDKey && i < indexInfo->ii_NumIndexKeyAttrs)
4934+
if (isIDKey && i < indexDesc->rd_index->indnkeyatts)
49134935
idindexattrs = bms_add_member(idindexattrs,
49144936
attrnum - FirstLowInvalidHeapAttributeNumber);
49154937
}
49164938
}
49174939

49184940
/* Collect attributes used in expressions, too */
4919-
if (IsProjectionFunctionalIndex(indexDesc, indexInfo))
4941+
if (IsProjectionFunctionalIndex(indexDesc))
49204942
{
49214943
projindexes = bms_add_member(projindexes, indexno);
4922-
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &projindexattrs);
4944+
pull_varattnos(indexExpressions, 1, &projindexattrs);
49234945
}
49244946
else
49254947
{
49264948
/* Collect all attributes used in expressions, too */
4927-
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
4949+
pull_varattnos(indexExpressions, 1, &indexattrs);
49284950
}
49294951
/* Collect all attributes in the index predicate, too */
4930-
pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
4952+
pull_varattnos(indexPredicate, 1, &indexattrs);
49314953

49324954
index_close(indexDesc, AccessShareLock);
49334955
indexno += 1;

src/test/subscription/t/100_bugs.pl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Tests for various bugs found over time
2+
use strict;
3+
use warnings;
4+
use PostgresNode;
5+
use TestLib;
6+
use Test::More tests => 1;
7+
8+
# Bug #15114
9+
10+
# The bug was that determining which columns are part of the replica
11+
# identity index using RelationGetIndexAttrBitmap() would run
12+
# eval_const_expressions() on index expressions and predicates across
13+
# all indexes of the table, which in turn might require a snapshot,
14+
# but there wasn't one set, so it crashes. There were actually two
15+
# separate bugs, one on the publisher and one on the subscriber. The
16+
# fix was to avoid the constant expressions simplification in
17+
# RelationGetIndexAttrBitmap(), so it's safe to call in more contexts.
18+
19+
my $node_publisher = get_new_node('publisher');
20+
$node_publisher->init(allows_streaming => 'logical');
21+
$node_publisher->start;
22+
23+
my $node_subscriber = get_new_node('subscriber');
24+
$node_subscriber->init(allows_streaming => 'logical');
25+
$node_subscriber->start;
26+
27+
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
28+
29+
$node_publisher->safe_psql('postgres',
30+
"CREATE TABLE tab1 (a int PRIMARY KEY, b int)");
31+
32+
$node_publisher->safe_psql('postgres',
33+
"CREATE FUNCTION double(x int) RETURNS int IMMUTABLE LANGUAGE SQL AS 'select x * 2'");
34+
35+
# an index with a predicate that lends itself to constant expressions
36+
# evaluation
37+
$node_publisher->safe_psql('postgres',
38+
"CREATE INDEX ON tab1 (b) WHERE a > double(1)");
39+
40+
# and the same setup on the subscriber
41+
$node_subscriber->safe_psql('postgres',
42+
"CREATE TABLE tab1 (a int PRIMARY KEY, b int)");
43+
44+
$node_subscriber->safe_psql('postgres',
45+
"CREATE FUNCTION double(x int) RETURNS int IMMUTABLE LANGUAGE SQL AS 'select x * 2'");
46+
47+
$node_subscriber->safe_psql('postgres',
48+
"CREATE INDEX ON tab1 (b) WHERE a > double(1)");
49+
50+
$node_publisher->safe_psql('postgres',
51+
"CREATE PUBLICATION pub1 FOR ALL TABLES");
52+
53+
$node_subscriber->safe_psql('postgres',
54+
"CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr application_name=sub1' PUBLICATION pub1");
55+
56+
$node_publisher->wait_for_catchup('sub1');
57+
58+
# This would crash, first on the publisher, and then (if the publisher
59+
# is fixed) on the subscriber.
60+
$node_publisher->safe_psql('postgres',
61+
"INSERT INTO tab1 VALUES (1, 2)");
62+
63+
$node_publisher->wait_for_catchup('sub1');
64+
65+
pass('index predicates do not cause crash');

0 commit comments

Comments
 (0)