CREATE TABLE artists (
    id bigint NOT NULL,
    name text NOT NULL
);
SELECT create_distributed_table('artists', 'id');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

-- add some data
INSERT INTO artists VALUES (1, 'Pablo Picasso');
INSERT INTO artists VALUES (2, 'Vincent van Gogh');
INSERT INTO artists VALUES (3, 'Claude Monet');
INSERT INTO artists VALUES (4, 'William Kurelek');
-- RELEASE SAVEPOINT
BEGIN;
INSERT INTO artists VALUES (5, 'Asher Lev');
SAVEPOINT s1;
DELETE FROM artists WHERE id=5;
RELEASE SAVEPOINT s1;
COMMIT;
SELECT * FROM artists WHERE id=5;
 id | name
---------------------------------------------------------------------
(0 rows)

-- ROLLBACK TO SAVEPOINT
BEGIN;
INSERT INTO artists VALUES (5, 'Asher Lev');
SAVEPOINT s1;
DELETE FROM artists WHERE id=5;
ROLLBACK TO SAVEPOINT s1;
COMMIT;
SELECT * FROM artists WHERE id=5;
 id |   name
---------------------------------------------------------------------
  5 | Asher Lev
(1 row)

-- Serial sub-transaction releases
BEGIN;
SAVEPOINT s1;
DELETE FROM artists WHERE id=5;
RELEASE SAVEPOINT s1;
SAVEPOINT s2;
INSERT INTO artists VALUES (5, 'Jacob Kahn');
RELEASE SAVEPOINT s2;
COMMIT;
SELECT * FROM artists WHERE id=5;
 id |    name
---------------------------------------------------------------------
  5 | Jacob Kahn
(1 row)

-- Serial sub-transaction rollbacks
BEGIN;
SAVEPOINT s1;
UPDATE artists SET name='A' WHERE id=5;
ROLLBACK TO SAVEPOINT s1;
SAVEPOINT s2;
DELETE FROM artists WHERE id=5;
ROLLBACK TO SAVEPOINT s2;
COMMIT;
SELECT * FROM artists WHERE id=5;
 id |    name
---------------------------------------------------------------------
  5 | Jacob Kahn
(1 row)

-- Multiple sub-transaction activity before first query
BEGIN;
SAVEPOINT s0;
SAVEPOINT s1;
SAVEPOINT s2;
SAVEPOINT s3;
ROLLBACK TO SAVEPOINT s2;
RELEASE SAVEPOINT s1;
INSERT INTO artists VALUES (6, 'John J. Audubon');
ROLLBACK TO SAVEPOINT s0;
INSERT INTO artists VALUES (6, 'Emily Carr');
COMMIT;
SELECT * FROM artists WHERE id=6;
 id |    name
---------------------------------------------------------------------
  6 | Emily Carr
(1 row)

-- Release after rollback
BEGIN;
SAVEPOINT s1;
ROLLBACK TO s1;
RELEASE SAVEPOINT s1;
SAVEPOINT s2;
INSERT INTO artists VALUES (7, 'John J. Audubon');
ROLLBACK TO s2;
RELEASE SAVEPOINT s2;
COMMIT;
SELECT * FROM artists WHERE id=7;
 id | name
---------------------------------------------------------------------
(0 rows)

-- Recover from errors
\set VERBOSITY terse
BEGIN;
SAVEPOINT s1;
SAVEPOINT s2;
INSERT INTO artists VALUES (7, NULL);
ERROR:  null value in column "name" violates not-null constraint
ROLLBACK TO SAVEPOINT s1;
COMMIT;
-- Don't recover from errors
BEGIN;
SAVEPOINT s1;
SAVEPOINT s2;
INSERT INTO artists VALUES (7, NULL);
ERROR:  null value in column "name" violates not-null constraint
SAVEPOINT s3;
ERROR:  current transaction is aborted, commands ignored until end of transaction block
ROLLBACK TO SAVEPOINT s3;
ERROR:  savepoint "s3" does not exist
COMMIT;
-- Recover from multi-shard modify errors
BEGIN;
INSERT INTO artists VALUES (8, 'Sogand');
SAVEPOINT s1;
UPDATE artists SET name = NULL;
ERROR:  null value in column "name" violates not-null constraint
ROLLBACK TO s1;
INSERT INTO artists VALUES (9, 'Mohsen Namjoo');
COMMIT;
SELECT * FROM artists WHERE id IN (7, 8, 9) ORDER BY id;
 id |     name
---------------------------------------------------------------------
  8 | Sogand
  9 | Mohsen Namjoo
(2 rows)

-- Recover from multi-shard copy shutdown failure.
-- Constraint check for non-partition columns happen only at copy shutdown.
BEGIN;
DELETE FROM artists;
SAVEPOINT s1;
INSERT INTO artists SELECT i, NULL FROM generate_series(1, 5) i;
ERROR:  null value in column "name" violates not-null constraint
ROLLBACK TO s1;
INSERT INTO artists VALUES (10, 'Mahmoud Farshchian');
COMMIT;
SELECT * FROM artists WHERE id IN (9, 10) ORDER BY id;
 id |        name
---------------------------------------------------------------------
 10 | Mahmoud Farshchian
(1 row)

-- Recover from multi-shard copy send failure.
-- Constraint check for partition column happens at copy send.
BEGIN;
DELETE FROM artists;
SAVEPOINT s1;
INSERT INTO artists SELECT NULL, NULL FROM generate_series(1, 5) i;
ERROR:  the partition column of table public.artists cannot be NULL
ROLLBACK TO s1;
INSERT INTO artists VALUES (11, 'Egon Schiele');
COMMIT;
SELECT * FROM artists WHERE id IN (10, 11) ORDER BY id;
 id |     name
---------------------------------------------------------------------
 11 | Egon Schiele
(1 row)

-- Recover from multi-shard copy startup failure.
-- Check for existence of a value for partition columnn happens at copy startup.
BEGIN;
DELETE FROM artists;
SAVEPOINT s1;
INSERT INTO artists(name) SELECT 'a' FROM generate_series(1, 5) i;
ERROR:  the partition column of table public.artists should have a value
ROLLBACK TO s1;
INSERT INTO artists VALUES (12, 'Marc Chagall');
COMMIT;
SELECT * FROM artists WHERE id IN (11, 12) ORDER BY id;
 id |     name
---------------------------------------------------------------------
 12 | Marc Chagall
(1 row)

-- Recover from multi-shard CTE modify failures
create table t1(a int, b int);
create table t2(a int, b int CHECK(b > 0));
ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 1190000;
select create_distributed_table('t1', 'a'),
       create_distributed_table('t2', 'a');
 create_distributed_table | create_distributed_table
---------------------------------------------------------------------
                          |
(1 row)

begin;
insert into t2 select i, i+1 from generate_series(1, 3) i;
with r AS (
    update t2 set b = b + 1
    returning *
) insert into t1 select * from r;
savepoint s1;
with r AS (
    update t1 set b = b - 10
    returning *
) insert into t2 select * from r;
ERROR:  new row for relation "t2_xxxxxxx" violates check constraint "t2_b_check"
rollback to savepoint s1;
savepoint s2;
with r AS (
    update t2 set b = b - 10
    returning *
) insert into t1 select * from r;
ERROR:  new row for relation "t2_xxxxxxx" violates check constraint "t2_b_check"
rollback to savepoint s2;
savepoint s3;
with r AS (
    insert into t2 select i, i+1 from generate_series(-10,-5) i
    returning *
) insert into t1 select * from r;
ERROR:  new row for relation "t2_xxxxxxx" violates check constraint "t2_b_check"
rollback to savepoint s3;
savepoint s4;
with r AS (
    insert into t1 select i, i+1 from generate_series(-10,-5) i
    returning *
) insert into t2 select * from r;
ERROR:  new row for relation "t2_xxxxxxx" violates check constraint "t2_b_check"
rollback to savepoint s4;
with r AS (
    update t2 set b = b + 1
    returning *
) insert into t1 select * from r;
commit;
select * from t2 order by a, b;
 a | b
---------------------------------------------------------------------
 1 | 4
 2 | 5
 3 | 6
(3 rows)

select * from t1 order by a, b;
 a | b
---------------------------------------------------------------------
 1 | 3
 1 | 4
 2 | 4
 2 | 5
 3 | 5
 3 | 6
(6 rows)

drop table t1, t2;
-- ===================================================================
-- Tests for replication factor > 1
-- ===================================================================
CREATE TABLE researchers (
  id bigint NOT NULL,
  lab_id int NOT NULL,
  name text NOT NULL
);
SET citus.shard_count TO 2;
SELECT create_distributed_table('researchers', 'lab_id', 'hash');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

-- Basic rollback and release
BEGIN;
INSERT INTO researchers VALUES (7, 4, 'Jan Plaza');
SAVEPOINT s1;
INSERT INTO researchers VALUES (8, 4, 'Alonzo Church');
ROLLBACK TO s1;
RELEASE SAVEPOINT s1;
COMMIT;
SELECT * FROM researchers WHERE id in (7, 8);
 id | lab_id |   name
---------------------------------------------------------------------
  7 |      4 | Jan Plaza
(1 row)

-- Recover from failure on one of nodes
BEGIN;
SAVEPOINT s1;
INSERT INTO researchers VALUES (11, 11, 'Dana Scott');
INSERT INTO researchers VALUES (NULL, 10, 'Stephen Kleene');
ERROR:  null value in column "id" violates not-null constraint
ROLLBACK TO SAVEPOINT s1;
INSERT INTO researchers VALUES (12, 10, 'Stephen Kleene');
COMMIT;
SELECT * FROM researchers WHERE lab_id=10;
 id | lab_id |      name
---------------------------------------------------------------------
 12 |     10 | Stephen Kleene
(1 row)

-- Don't recover, but rollback
BEGIN;
SAVEPOINT s1;
INSERT INTO researchers VALUES (NULL, 10, 'Raymond Smullyan');
ERROR:  null value in column "id" violates not-null constraint
RELEASE SAVEPOINT s1;
ERROR:  current transaction is aborted, commands ignored until end of transaction block
SAVEPOINT s2;
ERROR:  current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
SELECT * FROM researchers WHERE lab_id=10;
 id | lab_id |      name
---------------------------------------------------------------------
 12 |     10 | Stephen Kleene
(1 row)

-- Don't recover, and commit
BEGIN;
SAVEPOINT s1;
INSERT INTO researchers VALUES (NULL, 10, 'Raymond Smullyan');
ERROR:  null value in column "id" violates not-null constraint
RELEASE SAVEPOINT s1;
ERROR:  current transaction is aborted, commands ignored until end of transaction block
SAVEPOINT s2;
ERROR:  current transaction is aborted, commands ignored until end of transaction block
COMMIT;
SELECT * FROM researchers WHERE lab_id=10;
 id | lab_id |      name
---------------------------------------------------------------------
 12 |     10 | Stephen Kleene
(1 row)

-- Implicit savepoints via pl/pgsql exceptions
BEGIN;
DO $$
BEGIN
  INSERT INTO researchers VALUES (15, 10, 'Melvin Fitting');
  INSERT INTO researchers VALUES (NULL, 10, 'Raymond Smullyan');
EXCEPTION
    WHEN not_null_violation THEN
        RAISE NOTICE 'caught not_null_violation';
END $$;
NOTICE:  caught not_null_violation
COMMIT;
SELECT * FROM researchers WHERE lab_id=10;
 id | lab_id |      name
---------------------------------------------------------------------
 12 |     10 | Stephen Kleene
(1 row)

BEGIN;
DO $$
BEGIN
  INSERT INTO researchers VALUES (15, 10, 'Melvin Fitting');
  RAISE EXCEPTION plpgsql_error;
EXCEPTION
    WHEN plpgsql_error THEN
        RAISE NOTICE 'caught manual plpgsql_error';
END $$;
NOTICE:  caught manual plpgsql_error
COMMIT;
SELECT * FROM researchers WHERE lab_id=10;
 id | lab_id |      name
---------------------------------------------------------------------
 12 |     10 | Stephen Kleene
(1 row)

BEGIN;
DO $$
BEGIN
  INSERT INTO researchers VALUES (15, 10, 'Melvin Fitting');
  INSERT INTO researchers VALUES (NULL, 10, 'Raymond Smullyan');
EXCEPTION
    WHEN not_null_violation THEN
        RAISE EXCEPTION not_null_violation; -- rethrow it
END $$;
ERROR:  not_null_violation
COMMIT;
SELECT * FROM researchers WHERE lab_id=10;
 id | lab_id |      name
---------------------------------------------------------------------
 12 |     10 | Stephen Kleene
(1 row)

-- Insert something after catching error.
BEGIN;
DO $$
BEGIN
  INSERT INTO researchers VALUES (15, 10, 'Melvin Fitting');
  INSERT INTO researchers VALUES (NULL, 10, 'Raymond Smullyan');
EXCEPTION
    WHEN not_null_violation THEN
        INSERT INTO researchers VALUES (32, 10, 'Raymond Smullyan');
END $$;
COMMIT;
SELECT * FROM researchers WHERE lab_id=10;
 id | lab_id |       name
---------------------------------------------------------------------
 12 |     10 | Stephen Kleene
 32 |     10 | Raymond Smullyan
(2 rows)

-- Clean-up
DROP TABLE artists;
DROP TABLE researchers;
