| -- Distributed snapshot tests |
| |
| -- Scenario1: Test to validate GetSnapshotData()'s computation of globalXmin using |
| -- distributed snapshot. It mainly uses a old read-only transaction to help |
| -- create situation where globalXmin can be lower than distributed oldestXmin |
| -- when calling DistributedLog_AdvanceOldestXmin(). |
| |
| -- Setup |
| CREATE TABLE distributed_snapshot_test1 (a int); |
| |
| 1: BEGIN; |
| 1: INSERT INTO distributed_snapshot_test1 values(1); |
| -- Read transaction which helps to get lower globalXmin for session 3. As this |
| -- will have MyProc->xmin set to transaction 1's xid. |
| 2: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
| 2: SELECT * from distributed_snapshot_test1; |
| -- Complete transaction 1, so that it no more appears in in-progress transaction |
| -- list for following transactions. |
| 1: COMMIT; |
| |
| -- Transaction to bump the latestCompletedXid |
| 1: INSERT INTO distributed_snapshot_test1 values(1); |
| |
| -- Hold after walking over ProcArray in GetSnpashotData(), right at start of |
| -- DistributedLog_AdvanceOldestXmin() |
| 1: SELECT gp_inject_fault('distributedlog_advance_oldest_xmin', 'suspend', |
| '', 'postgres', '', 1, -1, 5, dbid) from gp_segment_configuration |
| where content = 0 and role = 'p'; |
| 3&:@db_name postgres: SELECT count(*) > 0 from gp_dist_random('gp_id'); |
| 1: SELECT gp_wait_until_triggered_fault('distributedlog_advance_oldest_xmin', 1, dbid) |
| from gp_segment_configuration where content = 0 and role = 'p'; |
| 2: COMMIT; |
| |
| -- Transaction used to bump the distributed oldestXmin |
| 1: INSERT INTO distributed_snapshot_test1 values(1); |
| -- let session 3 now move forward to compute distributed oldest xmin |
| 1: SELECT gp_inject_fault('distributedlog_advance_oldest_xmin', 'reset', dbid) |
| from gp_segment_configuration where content = 0 and role = 'p'; |
| 3<: |
| |
| -- Scenario2: This scenario tests the boundary condition for Xmax in distributed snapshot |
| |
| -- Setup |
| CREATE TABLE distributed_snapshot_test2 (a int); |
| |
| -- start transaction assigns distributed xid. |
| 1: BEGIN ISOLATION LEVEL REPEATABLE READ; |
| -- this sets latestCompletedXid |
| 2: INSERT INTO distributed_snapshot_test2 VALUES(1); |
| -- here, take distributed snapshot |
| 1: SELECT 123 AS "establish snapshot"; |
| 2: INSERT INTO distributed_snapshot_test2 VALUES(2); |
| -- expected to see just VALUES(1) |
| 1: SELECT * FROM distributed_snapshot_test2; |
| 1: COMMIT; |
| |
| DROP TABLE distributed_snapshot_test2; |
| |
| -- Scenario3: Test the one-phase commit transactions don't break repeatable read isolation. |
| -- |
| -- Direct dispatch causes the select statements to be dispatched only to one of |
| -- the three demo cluster segments. A segment acquires local snapshot only when it |
| -- receives the dispatched statement. If one phase commit relied on local |
| -- snapshots only, wrong results are possible depending on the order of local |
| -- snapshot acquisition by the segments. This scenario validates that distributed |
| -- snapshot is used by the segments to evaluate tuple visibility in case of |
| -- one-phase commit and correct results are returned. |
| -- |
| -- connection 40 inserts 100, 100 and 300 serially using one-phase commit |
| -- protocol. Repeatable read transactions may read (100), (100,100) or |
| -- (100,100,300), but not (100, 300). |
| CREATE TABLE distributed_snapshot_test3 (a int); |
| 10: BEGIN ISOLATION LEVEL REPEATABLE READ; |
| 20: BEGIN ISOLATION LEVEL REPEATABLE READ; |
| 30: BEGIN ISOLATION LEVEL REPEATABLE READ; |
| 40: INSERT INTO distributed_snapshot_test3 VALUES(100); |
| 10: SELECT gp_segment_id, * FROM distributed_snapshot_test3 where a = 100; |
| 40: INSERT INTO distributed_snapshot_test3 VALUES(100); |
| 30: SELECT 123 AS "establish snapshot"; |
| 40: INSERT INTO distributed_snapshot_test3 VALUES(300); |
| 10: SELECT gp_segment_id, * FROM distributed_snapshot_test3; |
| 20: SELECT gp_segment_id, * FROM distributed_snapshot_test3; |
| 30: SELECT gp_segment_id, * FROM distributed_snapshot_test3; |
| 10: COMMIT; |
| 20: COMMIT; |
| 30: COMMIT; |
| DROP TABLE distributed_snapshot_test3; |
| |
| -- The following test cases are to test that QEs can get |
| -- latest distribute snapshot to scan normal tables (not catalog). |
| -- Cloudberry tests the visibility of heap tuples firstly using |
| -- distributed snapshot. Distributed snapshot is generated on |
| -- QD and then dispatched to QEs. Some utility statement needs |
| -- to work under latest snapshot when executing, so that they |
| -- invoke the function `GetLatestSnapshot` in QEs. But remember |
| -- we cannot get the latest distributed snapshot. |
| |
| -- Subtle cases are: Alter Table or Alter Domain statements on QD |
| -- get snapshot in Portal Run and then try to hold locks on the |
| -- target table in ProcessUtilitySlow. Here is the key point: |
| -- 1. try to hold lock ==> it might be blocked by other transcations |
| -- 2. then it will be waked up to continue |
| -- 3. when it can continue, the world has changed because other transcations |
| -- then blocks it have been over |
| |
| -- Previously, on QD we do not getsnapshot before we dispatch utility |
| -- statement to QEs which leads to the distributed snapshot does not |
| -- reflect the "world change". This will lead to some bugs. For example, |
| -- if the first transaction is to rewrite the whole heap, and then |
| -- the second Alter Table or Alter Domain statements continues with |
| -- the distributed snapshot that txn1 does not commit yet, it will |
| -- see no tuples in the new heap! |
| -- See Github issue https://github.com/greenplum-db/gpdb/issues/10216 |
| |
| -- Now this has been fixed, the following cases are tests to check this. |
| |
| -- Case 1: concurrently alter column type (will do rewrite heap) |
| create table t_alter_snapshot_test(a int, b int, c int); |
| insert into t_alter_snapshot_test values (1, 1, 1), (1, 1, 1); |
| |
| select * from t_alter_snapshot_test; |
| |
| 1: begin; |
| 1: alter table t_alter_snapshot_test alter column b type text; |
| |
| -- the following statement will hang |
| 2&: alter table t_alter_snapshot_test alter column c type text; |
| |
| 1: end; |
| -- after 1 commit, 2 can continue, it should use latest distributed |
| -- snapshot so that the data will not be lost. |
| 2<: |
| |
| select * from t_alter_snapshot_test; |
| drop table t_alter_snapshot_test; |
| |
| -- Case 2: concurrently add exclude constrain |
| create table t_alter_snapshot_test(a int, b int); |
| insert into t_alter_snapshot_test values (1, 1), (1, 1); |
| |
| select a from t_alter_snapshot_test; |
| |
| 1: begin; |
| 1: alter table t_alter_snapshot_test alter column b type int using b::int; |
| |
| 2&: alter table t_alter_snapshot_test add exclude using btree (a WITH =); |
| |
| 1: end; |
| -- after 1 commit, 2 can go on and it should fail |
| 2<: |
| |
| drop table t_alter_snapshot_test; |
| |
| -- Case 3: concurrently split partition |
| create table t_alter_snapshot_test(id int, rank int, year int) |
| distributed by (id) |
| partition by range (year) |
| ( start (0) end (20) every (4), default partition extra ); |
| |
| insert into t_alter_snapshot_test select i,i,i from generate_series(1, 100)i; |
| select count(*) from t_alter_snapshot_test; |
| |
| 1: begin; |
| 1: alter table t_alter_snapshot_test alter column rank type text; |
| |
| 2&: alter table t_alter_snapshot_test split partition for (5) at (5) into (partition pa, partition pb); |
| |
| 1: end; |
| -- after 1 commit, 2 can go on and it should not lose data |
| 2<: |
| |
| select count(*) from t_alter_snapshot_test; |
| drop table t_alter_snapshot_test; |
| |
| -- case 4: concurrently validate check |
| create table t_alter_snapshot_test(a int, b int); |
| insert into t_alter_snapshot_test values (1, 1), (2, 2); |
| alter table t_alter_snapshot_test ADD CONSTRAINT mychk CHECK(a > 20) NOT VALID; |
| |
| 1: begin; |
| 1: alter table t_alter_snapshot_test alter column b type text; |
| |
| 2&: alter table t_alter_snapshot_test validate CONSTRAINT mychk; |
| |
| 1: end; |
| -- after 1 commit, 2 can go on and it should fail |
| 2<: |
| |
| drop table t_alter_snapshot_test; |
| |
| -- case 5: concurrently domain check |
| create domain domain_snapshot_test as int; |
| create table t_alter_snapshot_test(i domain_snapshot_test, j int, k int); |
| insert into t_alter_snapshot_test values(200,1,1); |
| alter domain domain_snapshot_test ADD CONSTRAINT mychk CHECK(VALUE > 300) NOT VALID; |
| |
| 1: begin; |
| 1: alter table t_alter_snapshot_test alter column k type text; |
| |
| 2&: alter domain domain_snapshot_test validate CONSTRAINT mychk; |
| |
| 1:end; |
| -- after 1 commit, 2 can go on and it should fail |
| 2<: |
| |
| drop table t_alter_snapshot_test; |
| drop domain domain_snapshot_test; |
| |
| -- case 6: alter table expand table |
| create table t_alter_snapshot_test(a int, b int); |
| set allow_system_table_mods = on; |
| update gp_distribution_policy set numsegments = 2 where localoid = 't_alter_snapshot_test'::regclass::oid; |
| insert into t_alter_snapshot_test select i,i from generate_series(1, 10)i; |
| select gp_segment_id, * from t_alter_snapshot_test; |
| |
| 1: begin; |
| 1: alter table t_alter_snapshot_test alter column b type text; |
| |
| 2&: alter table t_alter_snapshot_test expand table; |
| |
| 1: end; |
| -- after 1 commit, 2 can go on and data should not be lost |
| 2<: |
| |
| select gp_segment_id, * from t_alter_snapshot_test; |
| drop table t_alter_snapshot_test; |
| |
| -- case 7: alter table set distributed by |
| create table t_alter_snapshot_test(a int, b int) distributed randomly; |
| insert into t_alter_snapshot_test select i,i from generate_series(1, 10)i; |
| select count(*) from t_alter_snapshot_test; |
| |
| 1: begin; |
| 1: alter table t_alter_snapshot_test alter column b type text; |
| |
| 2&: alter table t_alter_snapshot_test set distributed by (a); |
| |
| 1: end; |
| -- after 1 commit, 2 can continue and data should not be lost |
| 2<: |
| |
| select count(*) from t_alter_snapshot_test; |
| drop table t_alter_snapshot_test; |
| |
| -- case 8: DML concurrent with Alter Table |
| create table t_alter_snapshot_test(a int, b int); |
| |
| ---- test for insert |
| 1: begin; |
| 1: insert into t_alter_snapshot_test values (1, 1); |
| 2&: alter table t_alter_snapshot_test alter column b type text; |
| 1: end; |
| -- 2 can continue, and we should not lose data |
| 2<: |
| select * from t_alter_snapshot_test; |
| |
| ---- test for update |
| truncate t_alter_snapshot_test; |
| insert into t_alter_snapshot_test values (1, 1); |
| 1: begin; |
| 1: update t_alter_snapshot_test set b = '3'; |
| 2&: alter table t_alter_snapshot_test alter column b type int using b::int; |
| 1: end; |
| -- 2 can continue and we should see the data has been updated |
| 2<: |
| select * from t_alter_snapshot_test; |
| |
| ---- test for delete |
| truncate t_alter_snapshot_test; |
| insert into t_alter_snapshot_test values (1, 1); |
| 1: begin; |
| 1: delete from t_alter_snapshot_test; |
| 2&: alter table t_alter_snapshot_test alter column b type text; |
| 1: end; |
| -- 2 can continue and we should see the data has been deleted |
| 2<: |
| select * from t_alter_snapshot_test; |
| drop table t_alter_snapshot_test; |
| |
| -- Case 9: Repeatable Read Isolation Level Test |
| create table t_alter_snapshot_test(a int, b int); |
| insert into t_alter_snapshot_test values (1, 1); |
| 1: begin; |
| 1: insert into t_alter_snapshot_test values (1, 1); |
| |
| 2: begin isolation level repeatable read; |
| 2: select * from t_alter_snapshot_test; |
| 2&: alter table t_alter_snapshot_test alter column b type text; |
| |
| 1: end; |
| -- 2 can continue and after its alter rewrite the heap |
| -- it can see all the data even under repeatable read |
| 2<: |
| 2: select * from t_alter_snapshot_test; |
| 2: end; |
| |
| select * from t_alter_snapshot_test; |
| drop table t_alter_snapshot_test; |