1
1
# ##############################################################################
2
2
# Test of proper transaction isolation.
3
+ # Based on Martin Kleppmann tests, https://github.com/ept/hermitage
3
4
# ##############################################################################
4
5
5
6
use strict;
6
7
use warnings;
7
8
use PostgresNode;
8
9
use 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 ' ;
12
13
13
14
sub query_row
14
15
{
@@ -23,8 +24,16 @@ sub query_row
23
24
sub query_exec
24
25
{
25
26
my ($dbi , $sql ) = @_ ;
26
- print " query_exec('$sql ')\n " ;
27
27
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 " ;
28
37
return $rv ;
29
38
}
30
39
@@ -50,33 +59,33 @@ sub PostgresNode::psql_fails {
50
59
my $node1 = get_new_node(" node1" );
51
60
$node1 -> init;
52
61
$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'
55
64
) );
56
65
$node1 -> start;
57
66
58
67
# Setup second node
59
68
my $node2 = get_new_node(" node2" );
60
69
$node2 -> init;
61
70
$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'
64
73
) );
65
74
$node2 -> start;
66
75
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 )" );
70
79
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 )" );
74
83
75
84
# 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' ));
80
89
81
90
sub count_total
82
91
{
@@ -100,34 +109,94 @@ sub count_total
100
109
return $tot ;
101
110
}
102
111
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
+
103
138
# ##############################################################################
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
+
105
154
# ##############################################################################
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
+
106
201
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" );
133
202
0 commit comments