Skip to content

Commit 79b716c

Browse files
author
Amit Kapila
committed
Reorder subskiplsn in pg_subscription to avoid alignment issues.
The column 'subskiplsn' uses TYPALIGN_DOUBLE (which has 4 bytes alignment on AIX) for storage. But the C Struct (Form_pg_subscription) has 8-byte alignment for this field, so retrieving it from storage causes an unaligned read. To fix this, we rearranged the 'subskiplsn' column in the catalog so that it naturally comes at an 8-byte boundary. We have fixed a similar problem in commit f3b421d. This patch adds a test to avoid a similar mistake in the future. Reported-by: Noah Misch Diagnosed-by: Noah Misch, Masahiko Sawada, Amit Kapila Author: Masahiko Sawada Reviewed-by: Noah Misch, Amit Kapila Discussion: https://postgr.es/m/[email protected] https://postgr.es/m/CAD21AoDeScrsHhLyEPYqN3sydg6PxAPVBboK=30xJfUVihNZDA@mail.gmail.com
1 parent e41aed6 commit 79b716c

File tree

11 files changed

+130
-20
lines changed

11 files changed

+130
-20
lines changed

doc/src/sgml/catalogs.sgml

+10-10
Original file line numberDiff line numberDiff line change
@@ -7823,6 +7823,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
78237823
</para></entry>
78247824
</row>
78257825

7826+
<row>
7827+
<entry role="catalog_table_entry"><para role="column_definition">
7828+
<structfield>subskiplsn</structfield> <type>pg_lsn</type>
7829+
</para>
7830+
<para>
7831+
Finish LSN of the transaction whose changes are to be skipped, if a valid
7832+
LSN; otherwise <literal>0/0</literal>.
7833+
</para></entry>
7834+
</row>
7835+
78267836
<row>
78277837
<entry role="catalog_table_entry"><para role="column_definition">
78287838
<structfield>subname</structfield> <type>name</type>
@@ -7893,16 +7903,6 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
78937903
</para></entry>
78947904
</row>
78957905

7896-
<row>
7897-
<entry role="catalog_table_entry"><para role="column_definition">
7898-
<structfield>subskiplsn</structfield> <type>pg_lsn</type>
7899-
</para>
7900-
<para>
7901-
Finish LSN of the transaction whose changes are to be skipped, if a valid
7902-
LSN; otherwise <literal>0/0</literal>.
7903-
</para></entry>
7904-
</row>
7905-
79067906
<row>
79077907
<entry role="catalog_table_entry"><para role="column_definition">
79087908
<structfield>subconninfo</structfield> <type>text</type>

src/backend/catalog/pg_subscription.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ GetSubscription(Oid subid, bool missing_ok)
6363
sub = (Subscription *) palloc(sizeof(Subscription));
6464
sub->oid = subid;
6565
sub->dbid = subform->subdbid;
66+
sub->skiplsn = subform->subskiplsn;
6667
sub->name = pstrdup(NameStr(subform->subname));
6768
sub->owner = subform->subowner;
6869
sub->enabled = subform->subenabled;
6970
sub->binary = subform->subbinary;
7071
sub->stream = subform->substream;
7172
sub->twophasestate = subform->subtwophasestate;
7273
sub->disableonerr = subform->subdisableonerr;
73-
sub->skiplsn = subform->subskiplsn;
7474

7575
/* Get conninfo */
7676
datum = SysCacheGetAttr(SUBSCRIPTIONOID,

src/backend/catalog/system_views.sql

+2-2
Original file line numberDiff line numberDiff line change
@@ -1285,8 +1285,8 @@ REVOKE ALL ON pg_replication_origin_status FROM public;
12851285

12861286
-- All columns of pg_subscription except subconninfo are publicly readable.
12871287
REVOKE ALL ON pg_subscription FROM public;
1288-
GRANT SELECT (oid, subdbid, subname, subowner, subenabled, subbinary,
1289-
substream, subtwophasestate, subdisableonerr, subskiplsn, subslotname,
1288+
GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
1289+
subbinary, substream, subtwophasestate, subdisableonerr, subslotname,
12901290
subsynccommit, subpublications)
12911291
ON pg_subscription TO public;
12921292

src/backend/commands/subscriptioncmds.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
596596
Anum_pg_subscription_oid);
597597
values[Anum_pg_subscription_oid - 1] = ObjectIdGetDatum(subid);
598598
values[Anum_pg_subscription_subdbid - 1] = ObjectIdGetDatum(MyDatabaseId);
599+
values[Anum_pg_subscription_subskiplsn - 1] = LSNGetDatum(InvalidXLogRecPtr);
599600
values[Anum_pg_subscription_subname - 1] =
600601
DirectFunctionCall1(namein, CStringGetDatum(stmt->subname));
601602
values[Anum_pg_subscription_subowner - 1] = ObjectIdGetDatum(owner);
@@ -607,7 +608,6 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
607608
LOGICALREP_TWOPHASE_STATE_PENDING :
608609
LOGICALREP_TWOPHASE_STATE_DISABLED);
609610
values[Anum_pg_subscription_subdisableonerr - 1] = BoolGetDatum(opts.disableonerr);
610-
values[Anum_pg_subscription_subskiplsn - 1] = LSNGetDatum(InvalidXLogRecPtr);
611611
values[Anum_pg_subscription_subconninfo - 1] =
612612
CStringGetTextDatum(conninfo);
613613
if (opts.slot_name)

src/include/catalog/catversion.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@
5353
*/
5454

5555
/* yyyymmddN */
56-
#define CATALOG_VERSION_NO 202204062
56+
#define CATALOG_VERSION_NO 202204071
5757

5858
#endif

src/include/catalog/pg_subscription.h

+6-5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
5454

5555
Oid subdbid BKI_LOOKUP(pg_database); /* Database the
5656
* subscription is in. */
57+
58+
XLogRecPtr subskiplsn; /* All changes finished at this LSN are
59+
* skipped */
60+
5761
NameData subname; /* Name of the subscription */
5862

5963
Oid subowner BKI_LOOKUP(pg_authid); /* Owner of the subscription */
@@ -71,9 +75,6 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
7175
bool subdisableonerr; /* True if a worker error should cause the
7276
* subscription to be disabled */
7377

74-
XLogRecPtr subskiplsn; /* All changes finished at this LSN are
75-
* skipped */
76-
7778
#ifdef CATALOG_VARLEN /* variable-length fields start here */
7879
/* Connection string to the publisher */
7980
text subconninfo BKI_FORCE_NOT_NULL;
@@ -103,6 +104,8 @@ typedef struct Subscription
103104
Oid oid; /* Oid of the subscription */
104105
Oid dbid; /* Oid of the database which subscription is
105106
* in */
107+
XLogRecPtr skiplsn; /* All changes finished at this LSN are
108+
* skipped */
106109
char *name; /* Name of the subscription */
107110
Oid owner; /* Oid of the subscription owner */
108111
bool enabled; /* Indicates if the subscription is enabled */
@@ -113,8 +116,6 @@ typedef struct Subscription
113116
bool disableonerr; /* Indicates if the subscription should be
114117
* automatically disabled if a worker error
115118
* occurs */
116-
XLogRecPtr skiplsn; /* All changes finished at this LSN are
117-
* skipped */
118119
char *conninfo; /* Connection string to the publisher */
119120
char *slotname; /* Name of the replication slot */
120121
char *synccommit; /* Synchronous commit setting for worker */

src/test/regress/expected/sanity_check.out

+29
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,32 @@ SELECT relname, relkind
2525
---------+---------
2626
(0 rows)
2727

28+
--
29+
-- When ALIGNOF_DOUBLE==4 (e.g. AIX), the C ABI may impose 8-byte alignment on
30+
-- some of the C types that correspond to TYPALIGN_DOUBLE SQL types. To ensure
31+
-- catalog C struct layout matches catalog tuple layout, arrange for the tuple
32+
-- offset of each fixed-width, attalign='d' catalog column to be divisible by 8
33+
-- unconditionally. Keep such columns before the first NameData column of the
34+
-- catalog, since packagers can override NAMEDATALEN to an odd number.
35+
--
36+
WITH check_columns AS (
37+
SELECT relname, attname,
38+
array(
39+
SELECT t.oid
40+
FROM pg_type t JOIN pg_attribute pa ON t.oid = pa.atttypid
41+
WHERE pa.attrelid = a.attrelid AND
42+
pa.attnum > 0 AND pa.attnum <= a.attnum
43+
ORDER BY pa.attnum) AS coltypes
44+
FROM pg_attribute a JOIN pg_class c ON c.oid = attrelid
45+
JOIN pg_namespace n ON c.relnamespace = n.oid
46+
WHERE attalign = 'd' AND relkind = 'r' AND
47+
attnotnull AND attlen <> -1 AND n.nspname = 'pg_catalog'
48+
)
49+
SELECT relname, attname, coltypes, get_column_offset(coltypes)
50+
FROM check_columns
51+
WHERE get_column_offset(coltypes) % 8 != 0 OR
52+
'name'::regtype::oid = ANY(coltypes);
53+
relname | attname | coltypes | get_column_offset
54+
---------+---------+----------+-------------------
55+
(0 rows)
56+

src/test/regress/expected/test_setup.out

+4
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ CREATE FUNCTION ttdummy ()
206206
RETURNS trigger
207207
AS :'regresslib'
208208
LANGUAGE C;
209+
CREATE FUNCTION get_column_offset (oid[])
210+
RETURNS int
211+
AS :'regresslib'
212+
LANGUAGE C STRICT STABLE PARALLEL SAFE;
209213
-- Use hand-rolled hash functions and operator classes to get predictable
210214
-- result on different machines. The hash function for int4 simply returns
211215
-- the sum of the values passed to it and the one for text returns the length

src/test/regress/regress.c

+45
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "storage/spin.h"
4242
#include "utils/builtins.h"
4343
#include "utils/geo_decls.h"
44+
#include "utils/lsyscache.h"
4445
#include "utils/memutils.h"
4546
#include "utils/rel.h"
4647
#include "utils/typcache.h"
@@ -1216,3 +1217,47 @@ binary_coercible(PG_FUNCTION_ARGS)
12161217

12171218
PG_RETURN_BOOL(IsBinaryCoercible(srctype, targettype));
12181219
}
1220+
1221+
/*
1222+
* Return the column offset of the last data in the given array of
1223+
* data types. The input data types must be fixed-length data types.
1224+
*/
1225+
PG_FUNCTION_INFO_V1(get_column_offset);
1226+
Datum
1227+
get_column_offset(PG_FUNCTION_ARGS)
1228+
{
1229+
ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
1230+
Oid *type_oids;
1231+
int ntypes;
1232+
int column_offset = 0;
1233+
1234+
if (ARR_HASNULL(ta) && array_contains_nulls(ta))
1235+
elog(ERROR, "argument must not contain nulls");
1236+
1237+
if (ARR_NDIM(ta) > 1)
1238+
elog(ERROR, "argument must be empty or one-dimensional array");
1239+
1240+
type_oids = (Oid *) ARR_DATA_PTR(ta);
1241+
ntypes = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
1242+
for (int i = 0; i < ntypes; i++)
1243+
{
1244+
Oid typeoid = type_oids[i];
1245+
int16 typlen;
1246+
bool typbyval;
1247+
char typalign;
1248+
1249+
get_typlenbyvalalign(typeoid, &typlen, &typbyval, &typalign);
1250+
1251+
/* the data type must be fixed-length */
1252+
if (!(typbyval || (typlen > 0)))
1253+
elog(ERROR, "type %u is not fixed-length data type", typeoid);
1254+
1255+
column_offset = att_align_nominal(column_offset, typalign);
1256+
1257+
/* not include the last type size */
1258+
if (i != (ntypes - 1))
1259+
column_offset += typlen;
1260+
}
1261+
1262+
PG_RETURN_INT32(column_offset);
1263+
}

src/test/regress/sql/sanity_check.sql

+26
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,29 @@ SELECT relname, relkind
1919
FROM pg_class
2020
WHERE relkind IN ('v', 'c', 'f', 'p', 'I')
2121
AND relfilenode <> 0;
22+
23+
--
24+
-- When ALIGNOF_DOUBLE==4 (e.g. AIX), the C ABI may impose 8-byte alignment on
25+
-- some of the C types that correspond to TYPALIGN_DOUBLE SQL types. To ensure
26+
-- catalog C struct layout matches catalog tuple layout, arrange for the tuple
27+
-- offset of each fixed-width, attalign='d' catalog column to be divisible by 8
28+
-- unconditionally. Keep such columns before the first NameData column of the
29+
-- catalog, since packagers can override NAMEDATALEN to an odd number.
30+
--
31+
WITH check_columns AS (
32+
SELECT relname, attname,
33+
array(
34+
SELECT t.oid
35+
FROM pg_type t JOIN pg_attribute pa ON t.oid = pa.atttypid
36+
WHERE pa.attrelid = a.attrelid AND
37+
pa.attnum > 0 AND pa.attnum <= a.attnum
38+
ORDER BY pa.attnum) AS coltypes
39+
FROM pg_attribute a JOIN pg_class c ON c.oid = attrelid
40+
JOIN pg_namespace n ON c.relnamespace = n.oid
41+
WHERE attalign = 'd' AND relkind = 'r' AND
42+
attnotnull AND attlen <> -1 AND n.nspname = 'pg_catalog'
43+
)
44+
SELECT relname, attname, coltypes, get_column_offset(coltypes)
45+
FROM check_columns
46+
WHERE get_column_offset(coltypes) % 8 != 0 OR
47+
'name'::regtype::oid = ANY(coltypes);

src/test/regress/sql/test_setup.sql

+5
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ CREATE FUNCTION ttdummy ()
253253
AS :'regresslib'
254254
LANGUAGE C;
255255

256+
CREATE FUNCTION get_column_offset (oid[])
257+
RETURNS int
258+
AS :'regresslib'
259+
LANGUAGE C STRICT STABLE PARALLEL SAFE;
260+
256261
-- Use hand-rolled hash functions and operator classes to get predictable
257262
-- result on different machines. The hash function for int4 simply returns
258263
-- the sum of the values passed to it and the one for text returns the length

0 commit comments

Comments
 (0)