11# ##############################################################################
22# Test of proper transaction isolation.
3+ # Based on Martin Kleppmann tests, https://github.com/ept/hermitage
34# ##############################################################################
45
56use strict;
67use warnings;
78use PostgresNode;
89use TestLib;
9- use Test::More tests => 1 ;
10- use DBI() ;
11- use DBD::Pg() ;
10+ use Test::More tests => 2 ;
11+ use DBI;
12+ use DBD::Pg ' :async ' ;
1213
1314sub query_row
1415{
@@ -23,8 +24,16 @@ sub query_row
2324sub query_exec
2425{
2526 my ($dbi , $sql ) = @_ ;
26- print " query_exec('$sql ')\n " ;
2727 my $rv = $dbi -> do($sql ) || die ;
28+ print " query_exec('$sql ')\n " ;
29+ return $rv ;
30+ }
31+
32+ sub query_exec_async
33+ {
34+ my ($dbi , $sql ) = @_ ;
35+ my $rv = $dbi -> do($sql , {pg_async => PG_ASYNC}) || die ;
36+ print " query_exec('$sql ')\n " ;
2837 return $rv ;
2938}
3039
@@ -50,33 +59,33 @@ sub PostgresNode::psql_fails {
5059my $node1 = get_new_node(" node1" );
5160$node1 -> init;
5261$node1 -> append_conf(' postgresql.conf' , qq(
53- max_prepared_transactions = 10
54- shared_preload_libraries = 'pg_tsdtm'
62+ max_prepared_transactions = 10
63+ shared_preload_libraries = 'pg_tsdtm'
5564) );
5665$node1 -> start;
5766
5867# Setup second node
5968my $node2 = get_new_node(" node2" );
6069$node2 -> init;
6170$node2 -> append_conf(' postgresql.conf' , qq(
62- max_prepared_transactions = 10
63- shared_preload_libraries = 'pg_tsdtm'
71+ max_prepared_transactions = 10
72+ shared_preload_libraries = 'pg_tsdtm'
6473) );
6574$node2 -> start;
6675
67- $node1 -> psql(' postgres' , " create extension pg_tsdtm; " );
68- $node1 -> psql(' postgres' , " create table t(u int primary key, v int)" );
69- $node1 -> psql(' postgres' , " insert into t (select generate_series(0, 9), 0 )" );
76+ $node1 -> psql(' postgres' , " create extension pg_tsdtm" );
77+ $node1 -> psql(' postgres' , " create table t(id int primary key, v int)" );
78+ $node1 -> psql(' postgres' , " insert into t values(1, 10 )" );
7079
71- $node2 -> psql(' postgres' , " create extension pg_tsdtm; " );
72- $node2 -> psql(' postgres' , " create table t(u int primary key, v int)" );
73- $node2 -> psql(' postgres' , " insert into t (select generate_series(0, 9), 0 )" );
80+ $node2 -> psql(' postgres' , " create extension pg_tsdtm" );
81+ $node2 -> psql(' postgres' , " create table t(id int primary key, v int)" );
82+ $node2 -> psql(' postgres' , " insert into t values(2, 20 )" );
7483
7584# we need two connections to each node (run two simultameous global tx)
76- my $conn11 = DBI-> connect (' DBI:Pg:' . $node1 -> connstr(' postgres' ));
77- my $conn21 = DBI-> connect (' DBI:Pg:' . $node2 -> connstr(' postgres' ));
78- my $conn12 = DBI-> connect (' DBI:Pg:' . $node1 -> connstr(' postgres' ));
79- my $conn22 = DBI-> connect (' DBI:Pg:' . $node2 -> connstr(' postgres' ));
85+ my $conn1a = DBI-> connect (' DBI:Pg:' . $node1 -> connstr(' postgres' ));
86+ my $conn2a = DBI-> connect (' DBI:Pg:' . $node2 -> connstr(' postgres' ));
87+ my $conn1b = DBI-> connect (' DBI:Pg:' . $node1 -> connstr(' postgres' ));
88+ my $conn2b = DBI-> connect (' DBI:Pg:' . $node2 -> connstr(' postgres' ));
8089
8190sub count_total
8291{
@@ -100,34 +109,94 @@ sub count_total
100109 return $tot ;
101110}
102111
112+ sub start_global
113+ {
114+ my ($gtid , $c1 , $c2 ) = @_ ;
115+
116+ query_exec($c1 , " begin transaction" );
117+ query_exec($c2 , " begin transaction" );
118+ my $snapshot = query_row($c1 , " select dtm_extend('$gtid ')" );
119+ query_exec($c2 , " select dtm_access($snapshot , '$gtid ')" );
120+ }
121+
122+ sub commit_global
123+ {
124+ my ($gtid , $c1 , $c2 ) = @_ ;
125+
126+ query_exec($c1 , " prepare transaction '$gtid '" );
127+ query_exec($c2 , " prepare transaction '$gtid '" );
128+ query_exec($c1 , " select dtm_begin_prepare('$gtid ')" );
129+ query_exec($c2 , " select dtm_begin_prepare('$gtid ')" );
130+ my $csn = query_row($c1 , " select dtm_prepare('$gtid ', 0)" );
131+ query_exec($c2 , " select dtm_prepare('$gtid ', $csn )" );
132+ query_exec($c1 , " select dtm_end_prepare('$gtid ', $csn )" );
133+ query_exec($c2 , " select dtm_end_prepare('$gtid ', $csn )" );
134+ query_exec($c1 , " commit prepared '$gtid '" );
135+ query_exec($c2 , " commit prepared '$gtid '" );
136+ }
137+
103138# ##############################################################################
104- # Sanity check on dirty reads
139+ # Sanity check for dirty reads
140+ # ##############################################################################
141+
142+ start_global(" gtx1" , $conn1a , $conn2a );
143+
144+ query_exec($conn1a , " update t set v = v - 10 where id=1" );
145+
146+ my $intermediate_total = count_total($conn1b , $conn2b );
147+
148+ query_exec($conn2a , " update t set v = v + 10 where id=2" );
149+
150+ commit_global(" gtx1" , $conn1a , $conn2a );
151+
152+ is($intermediate_total , 30, " Check for absence of dirty reads" );
153+
105154# ##############################################################################
155+ # G0
156+ # ##############################################################################
157+
158+ my $fail = 0;
159+ $node1 -> psql(' postgres' , " update t set v = 10 where id = 2" );
160+ $node2 -> psql(' postgres' , " update t set v = 20 where id = 2" );
161+
162+ start_global(" gtx2a" , $conn1a , $conn2a );
163+ start_global(" gtx2b" , $conn1b , $conn2b );
164+
165+ query_exec($conn1a , " update t set v = 11 where id = 1" );
166+ query_exec_async($conn1b , " update t set v = 12 where id = 1" );
167+
168+ # last update should be locked
169+ $fail = 1 if $conn1b -> pg_ready != 0;
170+
171+ query_exec($conn2a , " update t set v = 21 where id = 2" );
172+ commit_global(" gtx2a" , $conn1a , $conn2a );
173+
174+ # here transaction can continue
175+ $conn1b -> pg_result;
176+
177+ my $v1 = query_row($conn1a , " select v from t where id = 1" );
178+ my $v2 = query_row($conn2a , " select v from t where id = 2" );
179+
180+ # we shouldn't see second's tx data
181+ $fail = 1 if $v1 != 11 or $v2 != 21;
182+
183+ query_exec($conn2b , " update t set v = 22 where id = 2" );
184+ commit_global(" gtx2b" , $conn1b , $conn2b );
185+
186+ $v1 = query_row($conn1a , " select v from t where id = 1" );
187+ $v2 = query_row($conn2a , " select v from t where id = 2" );
188+
189+ $fail = 1 if $v1 != 12 or $v2 != 22;
190+
191+ is($fail , 0, " Global transactions prevents Write Cycles (G0)" );
192+
193+
194+
195+
196+
197+
198+
199+
200+
106201
107- my $gtid1 = " gtx1" ;
108-
109- # start global tx
110- query_exec($conn11 , " begin transaction" );
111- query_exec($conn21 , " begin transaction" );
112- my $snapshot = query_row($conn11 , " select dtm_extend('$gtid1 ')" );
113- query_exec($conn21 , " select dtm_access($snapshot , '$gtid1 ')" );
114-
115- # transfer some amount of integers to different node
116- query_exec($conn11 , " update t set v = v - 10 where u=1" );
117- my $intermediate_total = count_total($conn12 , $conn22 );
118- query_exec($conn21 , " update t set v = v + 10 where u=2" );
119-
120- # commit our global tx
121- query_exec($conn11 , " prepare transaction '$gtid1 '" );
122- query_exec($conn21 , " prepare transaction '$gtid1 '" );
123- query_exec($conn11 , " select dtm_begin_prepare('$gtid1 ')" );
124- query_exec($conn21 , " select dtm_begin_prepare('$gtid1 ')" );
125- my $csn = query_row($conn11 , " select dtm_prepare('$gtid1 ', 0)" );
126- query_exec($conn21 , " select dtm_prepare('$gtid1 ', $csn )" );
127- query_exec($conn11 , " select dtm_end_prepare('$gtid1 ', $csn )" );
128- query_exec($conn21 , " select dtm_end_prepare('$gtid1 ', $csn )" );
129- query_exec($conn11 , " commit prepared '$gtid1 '" );
130- query_exec($conn21 , " commit prepared '$gtid1 '" );
131-
132- is($intermediate_total , 0, " Check for absence of dirty reads" );
133202
0 commit comments