Merge pull request #707 from citusdata/feature/allow_single_ddl_xact_block

Permit single DDL commands in transaction blocks

cr: @marcocitus
pull/762/head
Jason Petersen 2016-08-31 10:44:45 -06:00 committed by GitHub
commit 7168fdf62e
11 changed files with 232 additions and 22 deletions

View File

@ -233,6 +233,8 @@ CitusCopyFrom(CopyStmt *copyStatement, char *completionTag)
errmsg("unsupported partition method"))); errmsg("unsupported partition method")));
} }
} }
XactModificationLevel = XACT_MODIFICATION_DATA;
} }

View File

@ -94,7 +94,7 @@ MultiClientConnect(const char *nodeName, uint32 nodePort, const char *nodeDataba
char *effectiveDatabaseName = NULL; char *effectiveDatabaseName = NULL;
char *effectiveUserName = NULL; char *effectiveUserName = NULL;
if (IsModifyingTransaction) if (XactModificationLevel > XACT_MODIFICATION_NONE)
{ {
ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("cannot open new connections after the first modification " errmsg("cannot open new connections after the first modification "
@ -181,7 +181,7 @@ MultiClientConnectStart(const char *nodeName, uint32 nodePort, const char *nodeD
return connectionId; return connectionId;
} }
if (IsModifyingTransaction) if (XactModificationLevel > XACT_MODIFICATION_NONE)
{ {
ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("cannot open new connections after the first modification " errmsg("cannot open new connections after the first modification "

View File

@ -138,6 +138,14 @@ RouterExecutorStart(QueryDesc *queryDesc, int eflags, Task *task)
{ {
eflags |= EXEC_FLAG_SKIP_TRIGGERS; eflags |= EXEC_FLAG_SKIP_TRIGGERS;
if (XactModificationLevel == XACT_MODIFICATION_SCHEMA)
{
ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("distributed data modifications must not appear in "
"transaction blocks which contain distributed DDL "
"commands")));
}
/* /*
* We could naturally handle function-based transactions (i.e. those * We could naturally handle function-based transactions (i.e. those
* using PL/pgSQL or similar) by checking the type of queryDesc->dest, * using PL/pgSQL or similar) by checking the type of queryDesc->dest,
@ -228,7 +236,7 @@ InitTransactionStateForTask(Task *task)
participantEntry->connection = connection; participantEntry->connection = connection;
} }
IsModifyingTransaction = true; XactModificationLevel = XACT_MODIFICATION_DATA;
} }
@ -1178,7 +1186,7 @@ RegisterRouterExecutorXactCallbacks(void)
static void static void
RouterTransactionCallback(XactEvent event, void *arg) RouterTransactionCallback(XactEvent event, void *arg)
{ {
if (xactParticipantHash == NULL) if (XactModificationLevel != XACT_MODIFICATION_DATA)
{ {
return; return;
} }
@ -1235,7 +1243,7 @@ RouterTransactionCallback(XactEvent event, void *arg)
} }
/* reset transaction state */ /* reset transaction state */
IsModifyingTransaction = false; XactModificationLevel = XACT_MODIFICATION_NONE;
xactParticipantHash = NULL; xactParticipantHash = NULL;
xactShardConnSetList = NIL; xactShardConnSetList = NIL;
subXactAbortAttempted = false; subXactAbortAttempted = false;
@ -1275,6 +1283,11 @@ ExecuteTransactionEnd(bool commit)
NodeConnectionEntry *participant; NodeConnectionEntry *participant;
bool completed = !commit; /* aborts are assumed completed */ bool completed = !commit; /* aborts are assumed completed */
if (xactParticipantHash == NULL)
{
return;
}
hash_seq_init(&scan, xactParticipantHash); hash_seq_init(&scan, xactParticipantHash);
while ((participant = (NodeConnectionEntry *) hash_seq_search(&scan))) while ((participant = (NodeConnectionEntry *) hash_seq_search(&scan)))
{ {

View File

@ -1255,7 +1255,13 @@ ExecuteDistributedDDLCommand(Oid relationId, const char *ddlCommandString,
{ {
bool executionOK = false; bool executionOK = false;
PreventTransactionChain(isTopLevel, "distributed DDL commands"); if (XactModificationLevel > XACT_MODIFICATION_NONE)
{
ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("distributed DDL commands must not appear within "
"transaction blocks containing other modifications")));
}
ShowNoticeIfNotUsing2PC(); ShowNoticeIfNotUsing2PC();
executionOK = ExecuteCommandOnWorkerShards(relationId, ddlCommandString); executionOK = ExecuteCommandOnWorkerShards(relationId, ddlCommandString);
@ -1265,6 +1271,8 @@ ExecuteDistributedDDLCommand(Oid relationId, const char *ddlCommandString,
{ {
ereport(ERROR, (errmsg("could not execute DDL command on worker node shards"))); ereport(ERROR, (errmsg("could not execute DDL command on worker node shards")));
} }
XactModificationLevel = XACT_MODIFICATION_SCHEMA;
} }

View File

@ -283,6 +283,7 @@ CompleteShardPlacementTransactions(XactEvent event, void *arg)
CloseConnections(shardPlacementConnectionList); CloseConnections(shardPlacementConnectionList);
shardPlacementConnectionList = NIL; shardPlacementConnectionList = NIL;
XactModificationLevel = XACT_MODIFICATION_NONE;
} }

View File

@ -30,8 +30,8 @@
#include "utils/palloc.h" #include "utils/palloc.h"
/* state needed to prevent new connections during modifying transactions */ /* state needed to keep track of operations used during a transaction */
bool IsModifyingTransaction = false; XactModificationType XactModificationLevel = XACT_MODIFICATION_NONE;
/* /*
* NodeConnectionHash is the connection hash itself. It begins uninitialized. * NodeConnectionHash is the connection hash itself. It begins uninitialized.
@ -377,7 +377,7 @@ ConnectToNode(char *nodeName, int32 nodePort, char *nodeUser)
sprintf(nodePortString, "%d", nodePort); sprintf(nodePortString, "%d", nodePort);
if (IsModifyingTransaction) if (XactModificationLevel > XACT_MODIFICATION_NONE)
{ {
ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("cannot open new connections after the first modification " errmsg("cannot open new connections after the first modification "

View File

@ -32,7 +32,6 @@
/* SQL statement for testing */ /* SQL statement for testing */
#define TEST_SQL "DO $$ BEGIN RAISE EXCEPTION 'Raised remotely!'; END $$" #define TEST_SQL "DO $$ BEGIN RAISE EXCEPTION 'Raised remotely!'; END $$"
/* /*
* NodeConnectionKey acts as the key to index into the (process-local) hash * NodeConnectionKey acts as the key to index into the (process-local) hash
* keeping track of open connections. Node name and port are sufficient. * keeping track of open connections. Node name and port are sufficient.
@ -53,8 +52,18 @@ typedef struct NodeConnectionEntry
} NodeConnectionEntry; } NodeConnectionEntry;
/* describes what kind of modifications have occurred in the current transaction */
typedef enum
{
XACT_MODIFICATION_INVALID = 0, /* placeholder initial value */
XACT_MODIFICATION_NONE, /* no modifications have taken place */
XACT_MODIFICATION_DATA, /* data modifications (DML) have occurred */
XACT_MODIFICATION_SCHEMA /* schema modifications (DDL) have occurred */
} XactModificationType;
/* state needed to prevent new connections during modifying transactions */ /* state needed to prevent new connections during modifying transactions */
extern bool IsModifyingTransaction; extern XactModificationType XactModificationLevel;
/* function declarations for obtaining and using a connection */ /* function declarations for obtaining and using a connection */

View File

@ -100,6 +100,7 @@ SELECT name FROM researchers WHERE lab_id = 4;
BEGIN; BEGIN;
DO $$ DO $$
BEGIN BEGIN
INSERT INTO researchers VALUES (11, 11, 'Whitfield Diffie');
INSERT INTO researchers VALUES (NULL, 10, 'Edsger Dijkstra'); INSERT INTO researchers VALUES (NULL, 10, 'Edsger Dijkstra');
EXCEPTION EXCEPTION
WHEN not_null_violation THEN WHEN not_null_violation THEN
@ -150,19 +151,41 @@ SELECT count(*) FROM researchers WHERE lab_id = 6;
ERROR: no transaction participant matches localhost:57638 ERROR: no transaction participant matches localhost:57638
DETAIL: Transactions which modify distributed tables may only target nodes affected by the modification command which began the transaction. DETAIL: Transactions which modify distributed tables may only target nodes affected by the modification command which began the transaction.
ABORT; ABORT;
-- applies to DDL or COPY, too -- applies to DDL, too
BEGIN; BEGIN;
INSERT INTO labs VALUES (6, 'Bell Labs'); INSERT INTO labs VALUES (6, 'Bell Labs');
ALTER TABLE labs ADD COLUMN text motto; ALTER TABLE labs ADD COLUMN motto text;
ERROR: distributed DDL commands cannot run inside a transaction block ERROR: distributed DDL commands must not appear within transaction blocks containing other modifications
COMMIT; COMMIT;
-- whether it occurs first or second
BEGIN;
ALTER TABLE labs ADD COLUMN motto text;
NOTICE: using one-phase commit for distributed DDL commands
HINT: You can enable two-phase commit for extra safety with: SET citus.multi_shard_commit_protocol TO '2pc'
INSERT INTO labs VALUES (6, 'Bell Labs');
ERROR: distributed data modifications must not appear in transaction blocks which contain distributed DDL commands
COMMIT;
-- but the DDL should correctly roll back
\d labs
Table "public.labs"
Column | Type | Modifiers
--------+--------+-----------
id | bigint | not null
name | text | not null
SELECT * FROM labs WHERE id = 6;
id | name
----+------
(0 rows)
-- COPY can't happen second,
BEGIN; BEGIN;
INSERT INTO labs VALUES (6, 'Bell Labs'); INSERT INTO labs VALUES (6, 'Bell Labs');
\copy labs from stdin delimiter ',' \copy labs from stdin delimiter ','
ERROR: cannot open new connections after the first modification command within a transaction ERROR: cannot open new connections after the first modification command within a transaction
CONTEXT: COPY labs, line 1: "10,Weyland-Yutani" CONTEXT: COPY labs, line 1: "10,Weyland-Yutani"
COMMIT; COMMIT;
-- though the copy will work if before any modifications -- though it will work if before any modifications
BEGIN; BEGIN;
\copy labs from stdin delimiter ',' \copy labs from stdin delimiter ','
SELECT name FROM labs WHERE id = 10; SELECT name FROM labs WHERE id = 10;
@ -173,6 +196,59 @@ SELECT name FROM labs WHERE id = 10;
INSERT INTO labs VALUES (6, 'Bell Labs'); INSERT INTO labs VALUES (6, 'Bell Labs');
COMMIT; COMMIT;
-- but a double-copy isn't allowed (the first will persist)
BEGIN;
\copy labs from stdin delimiter ','
\copy labs from stdin delimiter ','
ERROR: cannot open new connections after the first modification command within a transaction
CONTEXT: COPY labs, line 1: "12,fsociety"
COMMIT;
SELECT name FROM labs WHERE id = 11;
name
----------------
Planet Express
(1 row)
-- finally, ALTER and copy aren't compatible
BEGIN;
ALTER TABLE labs ADD COLUMN motto text;
\copy labs from stdin delimiter ','
ERROR: cannot open new connections after the first modification command within a transaction
CONTEXT: COPY labs, line 1: "12,fsociety,lol"
COMMIT;
-- but the DDL should correctly roll back
\d labs
Table "public.labs"
Column | Type | Modifiers
--------+--------+-----------
id | bigint | not null
name | text | not null
SELECT * FROM labs WHERE id = 12;
id | name
----+------
(0 rows)
-- and if the copy is before the ALTER...
BEGIN;
\copy labs from stdin delimiter ','
ALTER TABLE labs ADD COLUMN motto text;
ERROR: distributed DDL commands must not appear within transaction blocks containing other modifications
COMMIT;
-- the DDL fails, but copy persists
\d labs
Table "public.labs"
Column | Type | Modifiers
--------+--------+-----------
id | bigint | not null
name | text | not null
SELECT * FROM labs WHERE id = 12;
id | name
----+----------
12 | fsociety
(1 row)
-- now, for some special failures... -- now, for some special failures...
CREATE TABLE objects ( CREATE TABLE objects (
id bigint PRIMARY KEY, id bigint PRIMARY KEY,

View File

@ -165,7 +165,7 @@ COMMIT;
SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter'; SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter';
DROP INDEX temp_index_1; DROP INDEX temp_index_1;
-- verify that distributed ddl commands are not allowed inside a transaction block -- verify that single distributed ddl commands are allowed inside a transaction block
SET citus.enable_ddl_propagation to true; SET citus.enable_ddl_propagation to true;
BEGIN; BEGIN;
CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey); CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey);
@ -173,6 +173,27 @@ COMMIT;
SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter'; SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter';
DROP INDEX temp_index_2; DROP INDEX temp_index_2;
-- but that multiple ddl statements in a block results in ROLLBACK
BEGIN;
CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey);
ALTER TABLE lineitem_alter ADD COLUMN first integer;
COMMIT;
SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter';
-- and distributed SELECTs cannot appear after ALTER
BEGIN;
CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey);
SELECT l_orderkey FROM lineitem_alter LIMIT 0;
COMMIT;
-- but are allowed before
BEGIN;
SELECT l_orderkey FROM lineitem_alter LIMIT 0;
CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey);
COMMIT;
SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter';
DROP INDEX temp_index_2;
--- verify that distributed ddl commands can be used with 2pc --- verify that distributed ddl commands can be used with 2pc
SET citus.multi_shard_commit_protocol TO '2pc'; SET citus.multi_shard_commit_protocol TO '2pc';
CREATE INDEX temp_index_3 ON lineitem_alter(l_orderkey); CREATE INDEX temp_index_3 ON lineitem_alter(l_orderkey);

View File

@ -424,19 +424,51 @@ SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter';
(1 row) (1 row)
DROP INDEX temp_index_1; DROP INDEX temp_index_1;
-- verify that distributed ddl commands are not allowed inside a transaction block -- verify that single distributed ddl commands are allowed inside a transaction block
SET citus.enable_ddl_propagation to true; SET citus.enable_ddl_propagation to true;
BEGIN; BEGIN;
CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey); CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey);
ERROR: distributed DDL commands cannot run inside a transaction block COMMIT;
SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter';
indexname | tablename
--------------+----------------
temp_index_2 | lineitem_alter
(1 row)
DROP INDEX temp_index_2;
-- but that multiple ddl statements in a block results in ROLLBACK
BEGIN;
CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey);
ALTER TABLE lineitem_alter ADD COLUMN first integer;
ERROR: distributed DDL commands must not appear within transaction blocks containing other modifications
COMMIT; COMMIT;
SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter'; SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter';
indexname | tablename indexname | tablename
-----------+----------- -----------+-----------
(0 rows) (0 rows)
-- and distributed SELECTs cannot appear after ALTER
BEGIN;
CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey);
SELECT l_orderkey FROM lineitem_alter LIMIT 0;
ERROR: cannot open new connections after the first modification command within a transaction
COMMIT;
-- but are allowed before
BEGIN;
SELECT l_orderkey FROM lineitem_alter LIMIT 0;
l_orderkey
------------
(0 rows)
CREATE INDEX temp_index_2 ON lineitem_alter(l_orderkey);
COMMIT;
SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'lineitem_alter';
indexname | tablename
--------------+----------------
temp_index_2 | lineitem_alter
(1 row)
DROP INDEX temp_index_2; DROP INDEX temp_index_2;
ERROR: index "temp_index_2" does not exist
--- verify that distributed ddl commands can be used with 2pc --- verify that distributed ddl commands can be used with 2pc
SET citus.multi_shard_commit_protocol TO '2pc'; SET citus.multi_shard_commit_protocol TO '2pc';
CREATE INDEX temp_index_3 ON lineitem_alter(l_orderkey); CREATE INDEX temp_index_3 ON lineitem_alter(l_orderkey);

View File

@ -78,6 +78,7 @@ SELECT name FROM researchers WHERE lab_id = 4;
BEGIN; BEGIN;
DO $$ DO $$
BEGIN BEGIN
INSERT INTO researchers VALUES (11, 11, 'Whitfield Diffie');
INSERT INTO researchers VALUES (NULL, 10, 'Edsger Dijkstra'); INSERT INTO researchers VALUES (NULL, 10, 'Edsger Dijkstra');
EXCEPTION EXCEPTION
WHEN not_null_violation THEN WHEN not_null_violation THEN
@ -122,12 +123,23 @@ INSERT INTO labs VALUES (6, 'Bell Labs');
SELECT count(*) FROM researchers WHERE lab_id = 6; SELECT count(*) FROM researchers WHERE lab_id = 6;
ABORT; ABORT;
-- applies to DDL or COPY, too -- applies to DDL, too
BEGIN; BEGIN;
INSERT INTO labs VALUES (6, 'Bell Labs'); INSERT INTO labs VALUES (6, 'Bell Labs');
ALTER TABLE labs ADD COLUMN text motto; ALTER TABLE labs ADD COLUMN motto text;
COMMIT; COMMIT;
-- whether it occurs first or second
BEGIN;
ALTER TABLE labs ADD COLUMN motto text;
INSERT INTO labs VALUES (6, 'Bell Labs');
COMMIT;
-- but the DDL should correctly roll back
\d labs
SELECT * FROM labs WHERE id = 6;
-- COPY can't happen second,
BEGIN; BEGIN;
INSERT INTO labs VALUES (6, 'Bell Labs'); INSERT INTO labs VALUES (6, 'Bell Labs');
\copy labs from stdin delimiter ',' \copy labs from stdin delimiter ','
@ -135,7 +147,7 @@ INSERT INTO labs VALUES (6, 'Bell Labs');
\. \.
COMMIT; COMMIT;
-- though the copy will work if before any modifications -- though it will work if before any modifications
BEGIN; BEGIN;
\copy labs from stdin delimiter ',' \copy labs from stdin delimiter ','
10,Weyland-Yutani 10,Weyland-Yutani
@ -144,6 +156,42 @@ SELECT name FROM labs WHERE id = 10;
INSERT INTO labs VALUES (6, 'Bell Labs'); INSERT INTO labs VALUES (6, 'Bell Labs');
COMMIT; COMMIT;
-- but a double-copy isn't allowed (the first will persist)
BEGIN;
\copy labs from stdin delimiter ','
11,Planet Express
\.
\copy labs from stdin delimiter ','
12,fsociety
\.
COMMIT;
SELECT name FROM labs WHERE id = 11;
-- finally, ALTER and copy aren't compatible
BEGIN;
ALTER TABLE labs ADD COLUMN motto text;
\copy labs from stdin delimiter ','
12,fsociety,lol
\.
COMMIT;
-- but the DDL should correctly roll back
\d labs
SELECT * FROM labs WHERE id = 12;
-- and if the copy is before the ALTER...
BEGIN;
\copy labs from stdin delimiter ','
12,fsociety
\.
ALTER TABLE labs ADD COLUMN motto text;
COMMIT;
-- the DDL fails, but copy persists
\d labs
SELECT * FROM labs WHERE id = 12;
-- now, for some special failures... -- now, for some special failures...
CREATE TABLE objects ( CREATE TABLE objects (
id bigint PRIMARY KEY, id bigint PRIMARY KEY,