Skip to content

Commit

Permalink
Fix logical replication between different encodings
Browse files Browse the repository at this point in the history
When sending a tuple attribute, the previous coding erroneously sent the
length byte before encoding conversion, which would lead to protocol
failures on the receiving side if the length did not match the following
string.

To fix that, use pq_sendcountedtext() for sending tuple attributes,
which takes care of all of that internally.  To match the API of
pq_sendcountedtext(), send even text values without a trailing zero byte
and have the receiving end put it in place instead.  This matches how
the standard FE/BE protocol behaves.

Reported-by: Kyotaro HORIGUCHI <[email protected]>
  • Loading branch information
petere committed Apr 6, 2017
1 parent 5f21f52 commit 6f1b9aa
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 8 deletions.
7 changes: 5 additions & 2 deletions doc/src/sgml/protocol.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -6107,11 +6107,14 @@ TupleData
</varlistentry>
<varlistentry>
<term>
String
Byte<replaceable>n</replaceable>
</term>
<listitem>
<para>
The text value.
The value of the column, in text format. (A future release
might support additional formats.)
<replaceable>n</replaceable> is the above length.

</para>
</listitem>
</varlistentry>
Expand Down
10 changes: 4 additions & 6 deletions src/backend/replication/logical/proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,6 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple)
Form_pg_type typclass;
Form_pg_attribute att = desc->attrs[i];
char *outputstr;
int len;

/* skip dropped columns */
if (att->attisdropped)
Expand All @@ -442,10 +441,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple)
pq_sendbyte(out, 't'); /* 'text' data follows */

outputstr = OidOutputFunctionCall(typclass->typoutput, values[i]);
len = strlen(outputstr) + 1; /* null terminated */
pq_sendint(out, len, 4); /* length */
pq_sendstring(out, outputstr); /* data */

pq_sendcountedtext(out, outputstr, strlen(outputstr), false);
pfree(outputstr);

ReleaseSysCache(typtup);
Expand Down Expand Up @@ -492,7 +488,9 @@ logicalrep_read_tuple(StringInfo in, LogicalRepTupleData *tuple)
len = pq_getmsgint(in, 4); /* read length */

/* and data */
tuple->values[i] = (char *) pq_getmsgbytes(in, len);
tuple->values[i] = palloc(len + 1);
pq_copymsgbytes(in, tuple->values[i], len);
tuple->values[i][len] = '\0';
}
break;
default:
Expand Down
46 changes: 46 additions & 0 deletions src/test/subscription/t/005_encoding.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Test replication between databases with different encodings
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 1;

sub wait_for_caught_up
{
my ($node, $appname) = @_;

$node->poll_query_until('postgres',
"SELECT pg_current_wal_location() <= replay_location FROM pg_stat_replication WHERE application_name = '$appname';")
or die "Timed out while waiting for subscriber to catch up";
}

my $node_publisher = get_new_node('publisher');
$node_publisher->init(allows_streaming => 'logical', extra => ['--locale=C', '--encoding=UTF8']);
$node_publisher->start;

my $node_subscriber = get_new_node('subscriber');
$node_subscriber->init(allows_streaming => 'logical', extra => ['--locale=C', '--encoding=LATIN1']);
$node_subscriber->start;

my $ddl = "CREATE TABLE test1 (a int, b text);";
$node_publisher->safe_psql('postgres', $ddl);
$node_subscriber->safe_psql('postgres', $ddl);

my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
my $appname = 'encoding_test';

$node_publisher->safe_psql('postgres', "CREATE PUBLICATION mypub FOR ALL TABLES;");
$node_subscriber->safe_psql('postgres', "CREATE SUBSCRIPTION mysub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION mypub;");

wait_for_caught_up($node_publisher, $appname);

$node_publisher->safe_psql('postgres', q{INSERT INTO test1 VALUES (1, E'Mot\xc3\xb6rhead')}); # hand-rolled UTF-8

wait_for_caught_up($node_publisher, $appname);

is($node_subscriber->safe_psql('postgres', q{SELECT a FROM test1 WHERE b = E'Mot\xf6rhead'}), # LATIN1
qq(1),
'data replicated to subscriber');

$node_subscriber->stop;
$node_publisher->stop;

0 comments on commit 6f1b9aa

Please sign in to comment.