Skip to content

Commit 976b166

Browse files
authored
Add a check for PK (#303)
* Update index check to include PK check, orginal check exclude any PK if replica identiy is set for relation. * Added test in 059_misc to enable RIF on node_3 table and execute an update, this shall not error This fixes #301 * Fix apply worker for RIF Add more tests * Update index check in pgactive_create_group to allow for replcia indexes and upudate messages accordingly as suggested by Bertrand.
1 parent 9a7c3db commit 976b166

File tree

6 files changed

+184
-10
lines changed

6 files changed

+184
-10
lines changed

include/pgactive_version.h.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
#define pgactive_VERSION "2.1.6"
2-
#define pgactive_VERSION_NUM 20106
1+
#define pgactive_VERSION "2.1.7"
2+
#define pgactive_VERSION_NUM 20107
33
#define pgactive_MIN_REMOTE_VERSION_NUM 20100
44
#define pgactive_VERSION_DATE ""
55
#define pgactive_VERSION_GITHASH ""

pgactive--2.1.6--2.1.7.sql

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/* pgactive--2.1.6--2.1.7.sql */
2+
3+
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
4+
\echo Use "ALTER EXTENSION pgactive UPDATE TO '2.1.7'" to load this file. \quit
5+
6+
SET pgactive.skip_ddl_replication = true;
7+
SET LOCAL search_path = pgactive;
8+
-- Start Upgrade SQLs/Functions/Procedures
9+
10+
DROP FUNCTION pgactive_create_group (text, text, integer, text[]);
11+
12+
CREATE FUNCTION pgactive_create_group (
13+
node_name text,
14+
node_dsn text,
15+
apply_delay integer DEFAULT NULL,
16+
replication_sets text[] DEFAULT ARRAY['default']
17+
)
18+
RETURNS void LANGUAGE plpgsql VOLATILE
19+
SET search_path = pgactive, pg_catalog
20+
-- SET pgactive.permit_unsafe_ddl_commands = on is removed for now
21+
SET pgactive.skip_ddl_replication = on
22+
-- SET pgactive.skip_ddl_locking = on is removed for now
23+
AS $body$
24+
DECLARE
25+
t record;
26+
BEGIN
27+
28+
-- Prohibit enabling pgactive where exclusion constraints exist
29+
FOR t IN
30+
SELECT n.nspname, r.relname, c.conname, c.contype
31+
FROM pg_constraint c
32+
INNER JOIN pg_namespace n ON c.connamespace = n.oid
33+
INNER JOIN pg_class r ON c.conrelid = r.oid
34+
INNER JOIN LATERAL unnest(pgactive.pgactive_get_table_replication_sets(c.conrelid)) rs(rsname) ON (rs.rsname = ANY(replication_sets))
35+
WHERE c.contype = 'x'
36+
AND r.relpersistence = 'p'
37+
AND r.relkind = 'r'
38+
AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema')
39+
LOOP
40+
RAISE USING
41+
MESSAGE = 'pgactive can''t be enabled because exclusion constraints exist on persistent tables that are not excluded from replication',
42+
ERRCODE = 'object_not_in_prerequisite_state',
43+
DETAIL = format('Table %I.%I has exclusion constraint %I.', t.nspname, t.relname, t.conname),
44+
HINT = 'Drop the exclusion constraint(s), change the table(s) to UNLOGGED if they don''t need to be replicated, or exclude the table(s) from the active replication set(s).';
45+
END LOOP;
46+
47+
-- Warn users about missing primary keys and replica identity index
48+
FOR t IN
49+
SELECT n.nspname, r.relname, c.conname, c.contype
50+
FROM pg_constraint c
51+
INNER JOIN pg_namespace n ON c.connamespace = n.oid
52+
INNER JOIN pg_class r ON c.conrelid = r.oid
53+
INNER JOIN LATERAL unnest(pgactive.pgactive_get_table_replication_sets(c.conrelid)) rs(rsname) ON (rs.rsname = ANY(replication_sets))
54+
WHERE c.contype = 'u'
55+
AND r.relpersistence = 'p'
56+
AND r.relkind = 'r'
57+
AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema')
58+
LOOP
59+
RAISE WARNING USING
60+
MESSAGE = 'secondary unique constraint(s) exist on replicated table(s)',
61+
DETAIL = format('Table %I.%I has secondary unique constraint %I. This may cause unhandled replication conflicts.', t.nspname, t.relname, t.conname),
62+
HINT = 'Drop the secondary unique constraint(s), change the table(s) to UNLOGGED if they don''t need to be replicated, or exclude the table(s) from the active replication set(s).';
63+
END LOOP;
64+
65+
-- Warn users about missing primary keys
66+
FOR t IN
67+
SELECT n.nspname, r.relname, c.conname
68+
FROM pg_class r INNER JOIN pg_namespace n ON r.relnamespace = n.oid
69+
LEFT OUTER JOIN pg_constraint c ON (c.conrelid = r.oid AND c.contype = 'p')
70+
WHERE n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema')
71+
AND relkind = 'r'
72+
AND relpersistence = 'p'
73+
AND c.oid IS NULL AND r.relreplident != 'i'
74+
LOOP
75+
RAISE WARNING USING
76+
MESSAGE = format('table %I.%I has no PRIMARY KEY', t.nspname, t.relname),
77+
HINT = 'Tables without a PRIMARY KEY and REPLICA IDENTITY INDEX cannot be UPDATED or DELETED from, only INSERTED into. Add a PRIMARY KEY or a REPLICA IDENTITY INDEX.';
78+
END LOOP;
79+
80+
-- Create ON TRUNCATE triggers for pgactive on existing tables
81+
-- See pgactive_truncate_trigger_add for the matching event trigger for tables
82+
-- created after join.
83+
--
84+
-- The triggers may be created already because the pgactive event trigger
85+
-- runs when the pgactive extension is created, even if there's no active
86+
-- pgactive connections yet, so tables created after the extension is created
87+
-- will get the trigger already. So skip tables that have a tg named
88+
-- 'truncate_trigger' calling proc 'pgactive.pgactive_queue_truncate'.
89+
FOR t IN
90+
SELECT r.oid AS relid
91+
FROM pg_class r
92+
INNER JOIN pg_namespace n ON (r.relnamespace = n.oid)
93+
LEFT JOIN pg_trigger tg ON (r.oid = tg.tgrelid AND tgname = 'truncate_trigger')
94+
LEFT JOIN pg_proc p ON (p.oid = tg.tgfoid AND p.proname = 'pgactive_queue_truncate')
95+
LEFT JOIN pg_namespace pn ON (pn.oid = p.pronamespace AND pn.nspname = 'pgactive')
96+
WHERE r.relpersistence = 'p'
97+
AND r.relkind = 'r'
98+
AND n.nspname NOT IN ('pg_catalog', 'pgactive', 'information_schema')
99+
AND tg.oid IS NULL AND p.oid IS NULL and pn.oid IS NULL
100+
LOOP
101+
-- We use a C function here because in addition to trigger creation
102+
-- we must also mark it tgisinternal.
103+
PERFORM pgactive.pgactive_internal_create_truncate_trigger(t.relid);
104+
END LOOP;
105+
106+
PERFORM pgactive.pgactive_join_group(
107+
node_name := node_name,
108+
node_dsn := node_dsn,
109+
join_using_dsn := null,
110+
apply_delay := apply_delay,
111+
replication_sets := replication_sets,
112+
bypass_user_tables_check := true);
113+
END;
114+
$body$;
115+
116+
COMMENT ON FUNCTION pgactive_create_group(text, text, integer, text[]) IS
117+
'Create a pgactive group, turning a stand-alone database into the first node in a pgactive group';
118+
119+
REVOKE ALL ON FUNCTION pgactive_create_group(text, text, integer, text[]) FROM public;
120+
121+
-- Finish Upgrade SQLs/Functions/Procedures
122+
RESET pgactive.skip_ddl_replication;
123+
RESET search_path;

pgactive.control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pgactive extension
22
comment = 'Active-Active Replication Extension for PostgreSQL'
3-
default_version = '2.1.6'
3+
default_version = '2.1.7'
44
module_pathname = '$libdir/pgactive'
55
relocatable = false
66
schema = pg_catalog

src/pgactive_apply.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -985,9 +985,13 @@ process_remote_update(StringInfo s)
985985
read_tuple_parts(s, rel, &new_tuple);
986986

987987
/* lookup index to build scankey */
988-
if (rel->rel->rd_indexvalid == 0)
989-
RelationGetIndexList(rel->rel);
990-
idxoid = rel->rel->rd_replidindex;
988+
idxoid = RelationGetReplicaIndex(rel->rel);
989+
if (!OidIsValid(idxoid))
990+
#if PG_VERSION_NUM >= 180000
991+
idxoid = RelationGetPrimaryKeyIndex(rel->rel, false);
992+
#else
993+
idxoid = RelationGetPrimaryKeyIndex(rel->rel);
994+
#endif
991995
if (!OidIsValid(idxoid))
992996
elog(ERROR, "could not find primary key for table with oid %u",
993997
RelationGetRelid(rel->rel));
@@ -1250,7 +1254,13 @@ process_remote_delete(StringInfo s)
12501254
/* lookup index to build scankey */
12511255
if (rel->rel->rd_indexvalid == 0)
12521256
RelationGetIndexList(rel->rel);
1253-
idxoid = rel->rel->rd_replidindex;
1257+
idxoid = RelationGetReplicaIndex(rel->rel);
1258+
if (!OidIsValid(idxoid))
1259+
#if PG_VERSION_NUM >= 180000
1260+
idxoid = RelationGetPrimaryKeyIndex(rel->rel, false);
1261+
#else
1262+
idxoid = RelationGetPrimaryKeyIndex(rel->rel);
1263+
#endif
12541264
if (!OidIsValid(idxoid))
12551265
elog(ERROR, "could not find primary key for table with oid %u",
12561266
RelationGetRelid(rel->rel));

src/pgactive_executor.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ pgactiveExecutorStart(QueryDesc *queryDesc, int eflags)
491491
ListCell *l;
492492
List *rangeTable;
493493
PlannedStmt *plannedstmt = queryDesc->plannedstmt;
494+
Oid idxoid;
494495

495496
if (pgactive_always_allow_writes)
496497
goto done;
@@ -596,9 +597,14 @@ pgactiveExecutorStart(QueryDesc *queryDesc, int eflags)
596597
GetCommandTagName(CreateWritableStmtTag(plannedstmt)),
597598
RelationGetRelationName(rel))));
598599

599-
if (rel->rd_indexvalid == 0)
600-
RelationGetIndexList(rel);
601-
if (OidIsValid(rel->rd_replidindex))
600+
idxoid = RelationGetReplicaIndex(rel);
601+
if (!OidIsValid(idxoid))
602+
#if PG_VERSION_NUM >= 180000
603+
idxoid = RelationGetPrimaryKeyIndex(rel, false);
604+
#else
605+
idxoid = RelationGetPrimaryKeyIndex(rel);
606+
#endif
607+
if (OidIsValid(idxoid))
602608
{
603609
RelationClose(rel);
604610
continue;

test/t/059_misc.pl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,41 @@
395395
is($node_2_res, $expected, "pgactive node node_2 has all the data");
396396
is($node_3_res, $expected, "pgactive node node_3 has all the data");
397397

398+
# Set Replica Identity FULL for fruits tables and update
399+
note "Add new fruit to node-2";
400+
$node_2->safe_psql($pgactive_test_dbname,
401+
q[INSERT INTO fruits VALUES (10, 'KIWI');]);
402+
note "Set RIF for fruits table on node-3";
403+
$node_3->safe_psql($pgactive_test_dbname,
404+
q[ALTER TABLE fruits REPLICA IDENTITY FULL;]);
405+
note "Update id=10 to Kiwi on node-3";
406+
$node_3->safe_psql($pgactive_test_dbname,
407+
q[UPDATE fruits set name ='Kiwi' WHERE id = 10;]);
408+
note "Query node 3";
409+
$node_3->safe_psql($pgactive_test_dbname,
410+
q[UPDATE fruits set name ='KiwiKiwi' WHERE id = 10;]);
411+
note "Query node 2";
412+
$node_2->safe_psql($pgactive_test_dbname,
413+
q[SELECT count(*) = 1 FROM fruits WHERE id=10 AND name = 'Kiwi';]);
414+
note "Update id=10 to KiwiKiwi on node-2";
415+
$node_2->safe_psql($pgactive_test_dbname,
416+
q[UPDATE fruits set name ='KiwiKiwi' WHERE id = 10;]);
417+
note "Query node-2";
418+
$node_2->safe_psql($pgactive_test_dbname,
419+
q[SELECT count(*) = 1 FROM fruits WHERE id=10 AND name = 'KiwiKiwi';]);
420+
note "Query node-2";
421+
$node_3->safe_psql($pgactive_test_dbname,
422+
q[SELECT count(*) = 1 FROM fruits WHERE id=10 AND name = 'KiwiKiwi';]);
423+
note "Delete id=10 on node-2";
424+
$node_2->safe_psql($pgactive_test_dbname,
425+
q[DELETE FROM fruits WHERE id = 10;]);
426+
note "Query node-2";
427+
$node_2->safe_psql($pgactive_test_dbname,
428+
q[SELECT count(*) = 0 FROM fruits WHERE id=10;]);
429+
note "Query node-3";
430+
$node_3->safe_psql($pgactive_test_dbname,
431+
q[SELECT count(*) = 0 FROM fruits WHERE id=10;]);
432+
398433
$node_2->stop;
399434
$node_3->stop;
400435

0 commit comments

Comments
 (0)