Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
Icon?
ehthumbs.db
Thumbs.db
.idea
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Xiangyu Hu <xiangyu.hu at outlook.com>
Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc>
Zhenye Xie <xiezhenye at gmail.com>
Maciej Zimnoch <maciej.zimnoch@codilime.com>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please insert your name to alphabetical order


# Organizations

Expand Down
17 changes: 13 additions & 4 deletions connection_go18.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ func (mc *mysqlConn) Ping(ctx context.Context) error {

// BeginTx implements driver.ConnBeginTx interface
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
// TODO: support isolation levels
return nil, errors.New("mysql: isolation levels not supported")
}
if opts.ReadOnly {
// TODO: support read-only transactions
return nil, errors.New("mysql: read-only transactions not supported")
Expand All @@ -54,6 +50,19 @@ func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver
return nil, err
}

if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
level, err := mapIsolationLevel(opts.Isolation)
if err != nil {
mc.finish()
return nil, err
}
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
if err != nil {
mc.finish()
return nil, err
}
}

tx, err := mc.Begin()
mc.finish()
if err != nil {
Expand Down
52 changes: 52 additions & 0 deletions driver_go18_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,55 @@ func TestContextCancelBegin(t *testing.T) {
}
})
}

func TestContextBeginIsolationLevel(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice test👍

but, this test can be written more concisely, can't it?
for example

tx1, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
    Isolation: sql.LevelRepeatableRead,
})
tx2, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
    Isolation: sql.LevelReadCommitted,
})

row := tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
// check row.

_, err = tx1.ExecContext(ctx, "INSERT INTO test VALUES (1)")

row = tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
// check row.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks for advice.

runTests(t, dsn, func(dbt *DBTest) {
dbt.mustExec("CREATE TABLE test (v INTEGER)")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

tx1, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelRepeatableRead,
})
if err != nil {
dbt.Fatal(err)
}

tx2, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
})
if err != nil {
dbt.Fatal(err)
}

_, err = tx1.ExecContext(ctx, "INSERT INTO test VALUES (1)")
if err != nil {
dbt.Fatal(err)
}

var v int
row := tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
if err := row.Scan(&v); err != nil {
dbt.Fatal(err)
}
// Because writer transaction wasn't commited yet, it should be available
if v != 0 {
dbt.Errorf("expected val to be 0, got %d", v)
}

err = tx1.Commit()
if err != nil {
dbt.Fatal(err)
}

row = tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
if err := row.Scan(&v); err != nil {
dbt.Fatal(err)
}
// Data written by writer transaction is already commited, it should be selectable
if v != 1 {
dbt.Errorf("expected val to be 1, got %d", v)
}
tx2.Commit()
})
}
16 changes: 16 additions & 0 deletions utils_go18.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package mysql

import (
"crypto/tls"
"database/sql"
"database/sql/driver"
"errors"
)
Expand All @@ -31,3 +32,18 @@ func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
}
return dargs, nil
}

func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
switch sql.IsolationLevel(level) {
case sql.LevelRepeatableRead:
return "REPEATABLE READ", nil
case sql.LevelReadCommitted:
return "READ COMMITTED", nil
case sql.LevelReadUncommitted:
return "READ UNCOMMITTED", nil
case sql.LevelSerializable:
return "SERIALIZABLE", nil
default:
return "", errors.New("mysql: unsupported isolation level: " + string(level))
}
}
54 changes: 54 additions & 0 deletions utils_go18_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// +build go1.8

package mysql

import (
"database/sql"
"database/sql/driver"
"testing"
)

func TestIsolationLevelMapping(t *testing.T) {

data := []struct {
level driver.IsolationLevel
expected string
}{
{
level: driver.IsolationLevel(sql.LevelReadCommitted),
expected: "READ COMMITTED",
},
{
level: driver.IsolationLevel(sql.LevelRepeatableRead),
expected: "REPEATABLE READ",
},
{
level: driver.IsolationLevel(sql.LevelReadUncommitted),
expected: "READ UNCOMMITTED",
},
{
level: driver.IsolationLevel(sql.LevelSerializable),
expected: "SERIALIZABLE",
},
}

for i, td := range data {
if actual, err := mapIsolationLevel(td.level); actual != td.expected || err != nil {
t.Fatal(i, td.expected, actual, err)
}
}

// check unsupported mapping
if actual, err := mapIsolationLevel(driver.IsolationLevel(sql.LevelLinearizable)); actual != "" || err == nil {
t.Fatal("Expected error on unsupported isolation level")
}

}