vitess.io/vitess@v0.16.2/go/test/endtoend/vtgate/transaction/tx_test.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package transaction 18 19 import ( 20 "context" 21 _ "embed" 22 "flag" 23 "fmt" 24 "os" 25 "testing" 26 27 "vitess.io/vitess/go/test/endtoend/utils" 28 29 "github.com/stretchr/testify/require" 30 31 "vitess.io/vitess/go/mysql" 32 "vitess.io/vitess/go/test/endtoend/cluster" 33 ) 34 35 var ( 36 clusterInstance *cluster.LocalProcessCluster 37 vtParams mysql.ConnParams 38 keyspaceName = "ks" 39 cell = "zone1" 40 hostname = "localhost" 41 42 //go:embed schema.sql 43 SchemaSQL string 44 45 //go:embed vschema.json 46 VSchema string 47 ) 48 49 func TestMain(m *testing.M) { 50 defer cluster.PanicHandler(nil) 51 flag.Parse() 52 53 exitcode, err := func() (int, error) { 54 clusterInstance = cluster.NewCluster(cell, hostname) 55 defer clusterInstance.Teardown() 56 57 // Reserve vtGate port in order to pass it to vtTablet 58 clusterInstance.VtgateGrpcPort = clusterInstance.GetAndReservePort() 59 // Set extra tablet args for twopc 60 clusterInstance.VtTabletExtraArgs = []string{ 61 "--twopc_enable", 62 "--twopc_coordinator_address", fmt.Sprintf("localhost:%d", clusterInstance.VtgateGrpcPort), 63 "--twopc_abandon_age", "3600", 64 } 65 66 // Start topo server 67 if err := clusterInstance.StartTopo(); err != nil { 68 return 1, err 69 } 70 71 // Start keyspace 72 keyspace := &cluster.Keyspace{ 73 Name: keyspaceName, 74 SchemaSQL: SchemaSQL, 75 VSchema: VSchema, 76 } 77 if err := clusterInstance.StartKeyspace(*keyspace, []string{"-80", "80-"}, 1, false); err != nil { 78 return 1, err 79 } 80 81 // Starting Vtgate in default MULTI transaction mode 82 if err := clusterInstance.StartVtgate(); err != nil { 83 return 1, err 84 } 85 vtParams = clusterInstance.GetVTParams(keyspaceName) 86 87 return m.Run(), nil 88 }() 89 if err != nil { 90 fmt.Printf("%v\n", err) 91 os.Exit(1) 92 } else { 93 os.Exit(exitcode) 94 } 95 } 96 97 // TestTransactionModes tests transactions using twopc mode 98 func TestTransactionModes(t *testing.T) { 99 defer cluster.PanicHandler(t) 100 101 ctx := context.Background() 102 conn, err := mysql.Connect(ctx, &vtParams) 103 require.NoError(t, err) 104 defer conn.Close() 105 106 // set transaction mode to SINGLE. 107 utils.Exec(t, conn, "set transaction_mode = 'single'") 108 109 // Insert targeted to multiple tables should fail as Transaction mode is SINGLE 110 utils.Exec(t, conn, "begin") 111 utils.Exec(t, conn, "insert into twopc_user(user_id, name) values(1,'john')") 112 _, err = conn.ExecuteFetch("insert into twopc_user(user_id, name) values(6,'vick')", 1000, false) 113 want := "multi-db transaction attempted" 114 require.Error(t, err) 115 require.Contains(t, err.Error(), want) 116 utils.Exec(t, conn, "rollback") 117 118 // set transaction mode to TWOPC. 119 utils.Exec(t, conn, "set transaction_mode = 'twopc'") 120 121 // Insert targeted to multiple db should PASS with TWOPC trx mode 122 utils.Exec(t, conn, "begin") 123 utils.Exec(t, conn, "insert into twopc_user(user_id, name) values(3,'mark')") 124 utils.Exec(t, conn, "insert into twopc_user(user_id, name) values(4,'doug')") 125 utils.Exec(t, conn, "insert into twopc_lookup(name, id) values('Tim',7)") 126 utils.Exec(t, conn, "commit") 127 128 // Verify the values are present 129 utils.AssertMatches(t, conn, "select user_id from twopc_user where name='mark'", `[[INT64(3)]]`) 130 utils.AssertMatches(t, conn, "select name from twopc_lookup where id=3", `[[VARCHAR("mark")]]`) 131 132 // DELETE from multiple tables using TWOPC transaction mode 133 utils.Exec(t, conn, "begin") 134 utils.Exec(t, conn, "delete from twopc_user where user_id = 3") 135 utils.Exec(t, conn, "delete from twopc_lookup where id = 3") 136 utils.Exec(t, conn, "commit") 137 138 // VERIFY that values are deleted 139 utils.AssertMatches(t, conn, "select user_id from twopc_user where user_id=3", `[]`) 140 utils.AssertMatches(t, conn, "select name from twopc_lookup where id=3", `[]`) 141 } 142 143 // TestTransactionIsolation tests transaction isolation level. 144 func TestTransactionIsolation(t *testing.T) { 145 defer cluster.PanicHandler(t) 146 ctx := context.Background() 147 148 conn, err := mysql.Connect(ctx, &vtParams) 149 require.NoError(t, err) 150 defer conn.Close() 151 152 // inserting some data. 153 utils.Exec(t, conn, "insert into test(id, msg) values (1,'v1'), (2, 'v2')") 154 defer utils.Exec(t, conn, "delete from test") 155 156 conn1, err := mysql.Connect(ctx, &vtParams) 157 require.NoError(t, err) 158 defer conn1.Close() 159 160 conn2, err := mysql.Connect(ctx, &vtParams) 161 require.NoError(t, err) 162 defer conn2.Close() 163 164 // on connection 1 change the isolation level to read-committed. 165 // start a transaction and read the data for id = 1. 166 utils.Exec(t, conn1, "set transaction isolation level read committed") 167 utils.Exec(t, conn1, "begin") 168 utils.AssertMatches(t, conn1, "select id, msg from test where id = 1", `[[INT64(1) VARCHAR("v1")]]`) 169 170 // change the value of msg for id = 1 on connection 2. 171 utils.Exec(t, conn2, "update test set msg = 'foo' where id = 1") 172 173 // new value should be reflected on connection 1 within the open transaction. 174 utils.AssertMatches(t, conn1, "select id, msg from test where id = 1", `[[INT64(1) VARCHAR("foo")]]`) 175 utils.Exec(t, conn1, "rollback") 176 } 177 178 func TestTransactionAccessModes(t *testing.T) { 179 closer := start(t) 180 defer closer() 181 182 ctx := context.Background() 183 184 conn, err := mysql.Connect(ctx, &vtParams) 185 require.NoError(t, err) 186 defer conn.Close() 187 188 // start a transaction with read-only characteristic. 189 utils.Exec(t, conn, "start transaction read only") 190 _, err = utils.ExecAllowError(t, conn, "insert into test(id, msg) values (42,'foo')") 191 require.Error(t, err) 192 require.Contains(t, err.Error(), "Cannot execute statement in a READ ONLY transaction") 193 utils.Exec(t, conn, "rollback") 194 195 // trying autocommit, this should pass as transaction characteristics are limited to single transaction. 196 utils.Exec(t, conn, "insert into test(id, msg) values (42,'foo')") 197 198 // target replica 199 utils.Exec(t, conn, "use `ks@replica`") 200 // start a transaction with read-only characteristic. 201 utils.Exec(t, conn, "start transaction read only") 202 utils.Exec(t, conn, "select * from test") 203 204 // start a transaction with read-write characteristic. This should fail 205 utils.Exec(t, conn, "start transaction read write") 206 _, err = utils.ExecAllowError(t, conn, "select connection_id()") 207 require.Error(t, err) 208 require.Contains(t, err.Error(), "cannot start read write transaction on a read only tablet") 209 utils.Exec(t, conn, "rollback") 210 } 211 212 // TestTransactionIsolationInTx tests transaction isolation level inside transaction 213 // and setting isolation level to different values. 214 func TestTransactionIsolationInTx(t *testing.T) { 215 ctx := context.Background() 216 217 conn, err := mysql.Connect(ctx, &vtParams) 218 require.NoError(t, err) 219 defer conn.Close() 220 221 utils.Exec(t, conn, "set transaction isolation level read committed") 222 utils.Exec(t, conn, "begin") 223 utils.AssertMatches(t, conn, "select @@transaction_isolation", `[[VARCHAR("READ-COMMITTED")]]`) 224 utils.Exec(t, conn, "commit") 225 226 utils.Exec(t, conn, "set transaction isolation level serializable") 227 utils.Exec(t, conn, "begin") 228 utils.AssertMatches(t, conn, "select @@transaction_isolation", `[[VARCHAR("SERIALIZABLE")]]`) 229 utils.Exec(t, conn, "commit") 230 231 utils.Exec(t, conn, "set transaction isolation level read committed") 232 utils.Exec(t, conn, "begin") 233 utils.AssertMatches(t, conn, "select @@transaction_isolation", `[[VARCHAR("READ-COMMITTED")]]`) 234 utils.Exec(t, conn, "commit") 235 } 236 237 func start(t *testing.T) func() { 238 deleteAll := func() { 239 conn, err := mysql.Connect(context.Background(), &vtParams) 240 require.NoError(t, err) 241 tables := []string{"test", "twopc_user"} 242 for _, table := range tables { 243 _, _ = utils.ExecAllowError(t, conn, "delete from "+table) 244 } 245 conn.Close() 246 } 247 248 deleteAll() 249 250 return func() { 251 deleteAll() 252 cluster.PanicHandler(t) 253 } 254 }