Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 08859bb

Browse files
committed
Fix replication with replica identity full
The comparison with the target rows on the subscriber side was done with datumIsEqual(), which can have false negatives. For instance, it didn't work reliably for text columns. So use the equality operator provided by the type cache instead. Also add more user documentation about replica identity requirements. Reported-by: Tatsuo Ishii <[email protected]>
1 parent 0b13b2a commit 08859bb

File tree

3 files changed

+59
-11
lines changed

3 files changed

+59
-11
lines changed

doc/src/sgml/logical-replication.sgml

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,29 @@
110110
Publications can choose to limit the changes they produce to
111111
any combination of <command>INSERT</command>, <command>UPDATE</command>, and
112112
<command>DELETE</command>, similar to how triggers are fired by
113-
particular event types. If a table without a <literal>REPLICA
114-
IDENTITY</literal> is added to a publication that
115-
replicates <command>UPDATE</command> or <command>DELETE</command>
116-
operations then subsequent <command>UPDATE</command>
117-
or <command>DELETE</command> operations will fail on the publisher.
113+
particular event types. By default, all operation types are replicated.
114+
</para>
115+
116+
<para>
117+
A published table must have a <quote>replica identity</quote> configured in
118+
order to be able to replicate <command>UPDATE</command>
119+
and <command>DELETE</command> operations, so that appropriate rows to
120+
update or delete can be identified on the subscriber side. By default,
121+
this is the primary key, if there is one. Another unique index (with
122+
certain additional requirements) can also be set to be the replica
123+
identity. If the table does not have any suitable key, then it can be set
124+
to replica identity <quote>full</quote>, which means the entire row becomes
125+
the key. This, however, is very inefficient and should only be used as a
126+
fallback if no other solution is possible. If a replica identity other
127+
than <quote>full</quote> is set on the publisher side, a replica identity
128+
comprising the same or fewer columns must also be set on the subscriber
129+
side. See <xref linkend="SQL-CREATETABLE-REPLICA-IDENTITY"> for details on
130+
how to set the replica identity. If a table without a replica identity is
131+
added to a publication that replicates <command>UPDATE</command>
132+
or <command>DELETE</command> operations then
133+
subsequent <command>UPDATE</command> or <command>DELETE</command>
134+
operations will cause an error on the publisher. <command>INSERT</command>
135+
operations can proceed regardless of any replica identity.
118136
</para>
119137

120138
<para>

src/backend/executor/execReplication.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
#include "parser/parsetree.h"
2525
#include "storage/bufmgr.h"
2626
#include "storage/lmgr.h"
27+
#include "utils/builtins.h"
2728
#include "utils/datum.h"
2829
#include "utils/lsyscache.h"
2930
#include "utils/memutils.h"
3031
#include "utils/rel.h"
3132
#include "utils/snapmgr.h"
3233
#include "utils/syscache.h"
34+
#include "utils/typcache.h"
3335
#include "utils/tqual.h"
3436

3537

@@ -224,13 +226,15 @@ tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
224226
Datum values[MaxTupleAttributeNumber];
225227
bool isnull[MaxTupleAttributeNumber];
226228
int attrnum;
227-
Form_pg_attribute att;
228229

229230
heap_deform_tuple(tup, desc, values, isnull);
230231

231232
/* Check equality of the attributes. */
232233
for (attrnum = 0; attrnum < desc->natts; attrnum++)
233234
{
235+
Form_pg_attribute att;
236+
TypeCacheEntry *typentry;
237+
234238
/*
235239
* If one value is NULL and other is not, then they are certainly not
236240
* equal
@@ -245,8 +249,17 @@ tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
245249
continue;
246250

247251
att = desc->attrs[attrnum];
248-
if (!datumIsEqual(values[attrnum], slot->tts_values[attrnum],
249-
att->attbyval, att->attlen))
252+
253+
typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
254+
if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
255+
ereport(ERROR,
256+
(errcode(ERRCODE_UNDEFINED_FUNCTION),
257+
errmsg("could not identify an equality operator for type %s",
258+
format_type_be(att->atttypid))));
259+
260+
if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
261+
values[attrnum],
262+
slot->tts_values[attrnum])))
250263
return false;
251264
}
252265

src/test/subscription/t/001_rep_changes.pl

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use warnings;
44
use PostgresNode;
55
use TestLib;
6-
use Test::More tests => 15;
6+
use Test::More tests => 16;
77

88
# Initialize publisher node
99
my $node_publisher = get_new_node('publisher');
@@ -22,6 +22,10 @@
2222
"CREATE TABLE tab_ins AS SELECT generate_series(1,1002) AS a");
2323
$node_publisher->safe_psql('postgres',
2424
"CREATE TABLE tab_full AS SELECT generate_series(1,10) AS a");
25+
$node_publisher->safe_psql('postgres',
26+
"CREATE TABLE tab_full2 (x text)");
27+
$node_publisher->safe_psql('postgres',
28+
"INSERT INTO tab_full2 VALUES ('a'), ('b'), ('b')");
2529
$node_publisher->safe_psql('postgres',
2630
"CREATE TABLE tab_rep (a int primary key)");
2731
$node_publisher->safe_psql('postgres',
@@ -33,6 +37,7 @@
3337
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_notrep (a int)");
3438
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_ins (a int)");
3539
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_full (a int)");
40+
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_full2 (x text)");
3641
$node_subscriber->safe_psql('postgres',
3742
"CREATE TABLE tab_rep (a int primary key)");
3843
# different column count and order than on publisher
@@ -45,7 +50,7 @@
4550
$node_publisher->safe_psql('postgres',
4651
"CREATE PUBLICATION tap_pub_ins_only WITH (publish = insert)");
4752
$node_publisher->safe_psql('postgres',
48-
"ALTER PUBLICATION tap_pub ADD TABLE tab_rep, tab_full, tab_mixed");
53+
"ALTER PUBLICATION tap_pub ADD TABLE tab_rep, tab_full, tab_full2, tab_mixed");
4954
$node_publisher->safe_psql('postgres',
5055
"ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_ins");
5156

@@ -108,13 +113,18 @@
108113
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
109114
$node_subscriber->safe_psql('postgres',
110115
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
116+
$node_publisher->safe_psql('postgres',
117+
"ALTER TABLE tab_full2 REPLICA IDENTITY FULL");
118+
$node_subscriber->safe_psql('postgres',
119+
"ALTER TABLE tab_full2 REPLICA IDENTITY FULL");
111120
$node_publisher->safe_psql('postgres',
112121
"ALTER TABLE tab_ins REPLICA IDENTITY FULL");
113122
$node_subscriber->safe_psql('postgres',
114123
"ALTER TABLE tab_ins REPLICA IDENTITY FULL");
115124

116-
# and do the update
125+
# and do the updates
117126
$node_publisher->safe_psql('postgres', "UPDATE tab_full SET a = a * a");
127+
$node_publisher->safe_psql('postgres', "UPDATE tab_full2 SET x = 'bb' WHERE x = 'b'");
118128

119129
# Wait for subscription to catch up
120130
$node_publisher->poll_query_until('postgres', $caughtup_query)
@@ -125,6 +135,13 @@
125135
is($result, qq(20|1|100),
126136
'update works with REPLICA IDENTITY FULL and duplicate tuples');
127137

138+
$result = $node_subscriber->safe_psql('postgres',
139+
"SELECT x FROM tab_full2 ORDER BY 1");
140+
is($result, qq(a
141+
bb
142+
bb),
143+
'update works with REPLICA IDENTITY FULL and text datums');
144+
128145
# check that change of connection string and/or publication list causes
129146
# restart of subscription workers. Not all of these are registered as tests
130147
# as we need to poll for a change but the test suite will fail none the less

0 commit comments

Comments
 (0)