Skip to content

Commit d2a8175

Browse files
zimnxjulienschmidt
authored andcommittedJun 16, 2017
Transaction isolation levels (go-sql-driver#619)
* Added Gogland IDE internal directory to .gitignore * Support transaction isolation level in BeginTx * Review fixes * Simplyfied TestContextBeginIsolationLevel test * Applied more review comments * Applied review remarks
1 parent a825be0 commit d2a8175

6 files changed

+136
-15
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
Icon?
77
ehthumbs.db
88
Thumbs.db
9+
.idea

‎AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Lion Yang <lion at aosc.xyz>
4646
Luca Looz <luca.looz92 at gmail.com>
4747
Lucas Liu <extrafliu at gmail.com>
4848
Luke Scott <luke at webconnex.com>
49+
Maciej Zimnoch <maciej.zimnoch@codilime.com>
4950
Michael Woolnough <michael.woolnough at gmail.com>
5051
Nicola Peduzzi <thenikso at gmail.com>
5152
Olivier Mengué <dolmen at cpan.org>

‎connection_go18.go

+12-15
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,6 @@ func (mc *mysqlConn) Ping(ctx context.Context) error {
4141

4242
// BeginTx implements driver.ConnBeginTx interface
4343
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
44-
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
45-
// TODO: support isolation levels
46-
return nil, errors.New("mysql: isolation levels not supported")
47-
}
4844
if opts.ReadOnly {
4945
// TODO: support read-only transactions
5046
return nil, errors.New("mysql: read-only transactions not supported")
@@ -54,19 +50,20 @@ func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver
5450
return nil, err
5551
}
5652

57-
tx, err := mc.Begin()
58-
mc.finish()
59-
if err != nil {
60-
return nil, err
61-
}
53+
defer mc.finish()
6254

63-
select {
64-
default:
65-
case <-ctx.Done():
66-
tx.Rollback()
67-
return nil, ctx.Err()
55+
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
56+
level, err := mapIsolationLevel(opts.Isolation)
57+
if err != nil {
58+
return nil, err
59+
}
60+
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
61+
if err != nil {
62+
return nil, err
63+
}
6864
}
69-
return tx, err
65+
66+
return mc.Begin()
7067
}
7168

7269
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {

‎driver_go18_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,55 @@ func TestContextCancelBegin(t *testing.T) {
468468
}
469469
})
470470
}
471+
472+
func TestContextBeginIsolationLevel(t *testing.T) {
473+
runTests(t, dsn, func(dbt *DBTest) {
474+
dbt.mustExec("CREATE TABLE test (v INTEGER)")
475+
ctx, cancel := context.WithCancel(context.Background())
476+
defer cancel()
477+
478+
tx1, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
479+
Isolation: sql.LevelRepeatableRead,
480+
})
481+
if err != nil {
482+
dbt.Fatal(err)
483+
}
484+
485+
tx2, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
486+
Isolation: sql.LevelReadCommitted,
487+
})
488+
if err != nil {
489+
dbt.Fatal(err)
490+
}
491+
492+
_, err = tx1.ExecContext(ctx, "INSERT INTO test VALUES (1)")
493+
if err != nil {
494+
dbt.Fatal(err)
495+
}
496+
497+
var v int
498+
row := tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
499+
if err := row.Scan(&v); err != nil {
500+
dbt.Fatal(err)
501+
}
502+
// Because writer transaction wasn't commited yet, it should be available
503+
if v != 0 {
504+
dbt.Errorf("expected val to be 0, got %d", v)
505+
}
506+
507+
err = tx1.Commit()
508+
if err != nil {
509+
dbt.Fatal(err)
510+
}
511+
512+
row = tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
513+
if err := row.Scan(&v); err != nil {
514+
dbt.Fatal(err)
515+
}
516+
// Data written by writer transaction is already commited, it should be selectable
517+
if v != 1 {
518+
dbt.Errorf("expected val to be 1, got %d", v)
519+
}
520+
tx2.Commit()
521+
})
522+
}

‎utils_go18.go

+16
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package mysql
1212

1313
import (
1414
"crypto/tls"
15+
"database/sql"
1516
"database/sql/driver"
1617
"errors"
1718
)
@@ -31,3 +32,18 @@ func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
3132
}
3233
return dargs, nil
3334
}
35+
36+
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
37+
switch sql.IsolationLevel(level) {
38+
case sql.LevelRepeatableRead:
39+
return "REPEATABLE READ", nil
40+
case sql.LevelReadCommitted:
41+
return "READ COMMITTED", nil
42+
case sql.LevelReadUncommitted:
43+
return "READ UNCOMMITTED", nil
44+
case sql.LevelSerializable:
45+
return "SERIALIZABLE", nil
46+
default:
47+
return "", errors.New("mysql: unsupported isolation level: " + string(level))
48+
}
49+
}

‎utils_go18_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
2+
//
3+
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
4+
//
5+
// This Source Code Form is subject to the terms of the Mozilla Public
6+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
7+
// You can obtain one at http://mozilla.org/MPL/2.0/.
8+
9+
// +build go1.8
10+
11+
package mysql
12+
13+
import (
14+
"database/sql"
15+
"database/sql/driver"
16+
"testing"
17+
)
18+
19+
func TestIsolationLevelMapping(t *testing.T) {
20+
21+
data := []struct {
22+
level driver.IsolationLevel
23+
expected string
24+
}{
25+
{
26+
level: driver.IsolationLevel(sql.LevelReadCommitted),
27+
expected: "READ COMMITTED",
28+
},
29+
{
30+
level: driver.IsolationLevel(sql.LevelRepeatableRead),
31+
expected: "REPEATABLE READ",
32+
},
33+
{
34+
level: driver.IsolationLevel(sql.LevelReadUncommitted),
35+
expected: "READ UNCOMMITTED",
36+
},
37+
{
38+
level: driver.IsolationLevel(sql.LevelSerializable),
39+
expected: "SERIALIZABLE",
40+
},
41+
}
42+
43+
for i, td := range data {
44+
if actual, err := mapIsolationLevel(td.level); actual != td.expected || err != nil {
45+
t.Fatal(i, td.expected, actual, err)
46+
}
47+
}
48+
49+
// check unsupported mapping
50+
if actual, err := mapIsolationLevel(driver.IsolationLevel(sql.LevelLinearizable)); actual != "" || err == nil {
51+
t.Fatal("Expected error on unsupported isolation level")
52+
}
53+
54+
}

0 commit comments

Comments
 (0)
Please sign in to comment.