diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 92ec175af1..fa8ae536d9 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -110,11 +110,29 @@
Publications can choose to limit the changes they produce to
any combination of INSERT, UPDATE, and
DELETE, similar to how triggers are fired by
- particular event types. If a table without a REPLICA
- IDENTITY is added to a publication that
- replicates UPDATE or DELETE
- operations then subsequent UPDATE
- or DELETE operations will fail on the publisher.
+ particular event types. By default, all operation types are replicated.
+
+
+
+ A published table must have a replica identity
configured in
+ order to be able to replicate UPDATE
+ and DELETE operations, so that appropriate rows to
+ update or delete can be identified on the subscriber side. By default,
+ this is the primary key, if there is one. Another unique index (with
+ certain additional requirements) can also be set to be the replica
+ identity. If the table does not have any suitable key, then it can be set
+ to replica identity full
, which means the entire row becomes
+ the key. This, however, is very inefficient and should only be used as a
+ fallback if no other solution is possible. If a replica identity other
+ than full
is set on the publisher side, a replica identity
+ comprising the same or fewer columns must also be set on the subscriber
+ side. See for details on
+ how to set the replica identity. If a table without a replica identity is
+ added to a publication that replicates UPDATE
+ or DELETE operations then
+ subsequent UPDATE or DELETE
+ operations will cause an error on the publisher. INSERT
+ operations can proceed regardless of any replica identity.
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 6dae79a8f0..59f14e997f 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -24,12 +24,14 @@
#include "parser/parsetree.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
+#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+#include "utils/typcache.h"
#include "utils/tqual.h"
@@ -224,13 +226,15 @@ tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
Datum values[MaxTupleAttributeNumber];
bool isnull[MaxTupleAttributeNumber];
int attrnum;
- Form_pg_attribute att;
heap_deform_tuple(tup, desc, values, isnull);
/* Check equality of the attributes. */
for (attrnum = 0; attrnum < desc->natts; attrnum++)
{
+ Form_pg_attribute att;
+ TypeCacheEntry *typentry;
+
/*
* If one value is NULL and other is not, then they are certainly not
* equal
@@ -245,8 +249,17 @@ tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
continue;
att = desc->attrs[attrnum];
- if (!datumIsEqual(values[attrnum], slot->tts_values[attrnum],
- att->attbyval, att->attlen))
+
+ typentry = lookup_type_cache(att->atttypid, TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(att->atttypid))));
+
+ if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
+ values[attrnum],
+ slot->tts_values[attrnum])))
return false;
}
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index f9cf5e4392..a63c679848 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -3,7 +3,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
# Initialize publisher node
my $node_publisher = get_new_node('publisher');
@@ -22,6 +22,10 @@ $node_publisher->safe_psql('postgres',
"CREATE TABLE tab_ins AS SELECT generate_series(1,1002) AS a");
$node_publisher->safe_psql('postgres',
"CREATE TABLE tab_full AS SELECT generate_series(1,10) AS a");
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tab_full2 (x text)");
+$node_publisher->safe_psql('postgres',
+ "INSERT INTO tab_full2 VALUES ('a'), ('b'), ('b')");
$node_publisher->safe_psql('postgres',
"CREATE TABLE tab_rep (a int primary key)");
$node_publisher->safe_psql('postgres',
@@ -33,6 +37,7 @@ $node_publisher->safe_psql('postgres',
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_notrep (a int)");
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_ins (a int)");
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_full (a int)");
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_full2 (x text)");
$node_subscriber->safe_psql('postgres',
"CREATE TABLE tab_rep (a int primary key)");
# different column count and order than on publisher
@@ -45,7 +50,7 @@ $node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub");
$node_publisher->safe_psql('postgres',
"CREATE PUBLICATION tap_pub_ins_only WITH (publish = insert)");
$node_publisher->safe_psql('postgres',
- "ALTER PUBLICATION tap_pub ADD TABLE tab_rep, tab_full, tab_mixed");
+ "ALTER PUBLICATION tap_pub ADD TABLE tab_rep, tab_full, tab_full2, tab_mixed");
$node_publisher->safe_psql('postgres',
"ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_ins");
@@ -108,13 +113,18 @@ $node_publisher->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
$node_subscriber->safe_psql('postgres',
"ALTER TABLE tab_full REPLICA IDENTITY FULL");
+$node_publisher->safe_psql('postgres',
+ "ALTER TABLE tab_full2 REPLICA IDENTITY FULL");
+$node_subscriber->safe_psql('postgres',
+ "ALTER TABLE tab_full2 REPLICA IDENTITY FULL");
$node_publisher->safe_psql('postgres',
"ALTER TABLE tab_ins REPLICA IDENTITY FULL");
$node_subscriber->safe_psql('postgres',
"ALTER TABLE tab_ins REPLICA IDENTITY FULL");
-# and do the update
+# and do the updates
$node_publisher->safe_psql('postgres', "UPDATE tab_full SET a = a * a");
+$node_publisher->safe_psql('postgres', "UPDATE tab_full2 SET x = 'bb' WHERE x = 'b'");
# Wait for subscription to catch up
$node_publisher->poll_query_until('postgres', $caughtup_query)
@@ -125,6 +135,13 @@ $result = $node_subscriber->safe_psql('postgres',
is($result, qq(20|1|100),
'update works with REPLICA IDENTITY FULL and duplicate tuples');
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT x FROM tab_full2 ORDER BY 1");
+is($result, qq(a
+bb
+bb),
+ 'update works with REPLICA IDENTITY FULL and text datums');
+
# check that change of connection string and/or publication list causes
# restart of subscription workers. Not all of these are registered as tests
# as we need to poll for a change but the test suite will fail none the less