Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -46,6 +46,7 @@ Lion Yang <lion at aosc.xyz>
Luca Looz <luca.looz92 at gmail.com>
Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com>
Maciej Zimnoch <maciej.zimnoch@codilime.com>
Michael Woolnough <michael.woolnough at gmail.com>
Nicola Peduzzi <thenikso at gmail.com>
Olivier Mengué <dolmen at cpan.org>
Expand Down
24 changes: 13 additions & 11 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,18 +50,24 @@ func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver
return nil, err
}

defer mc.finish()

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

tx, err := mc.Begin()
mc.finish()
if err != nil {
return nil, err
}

select {
default:
case <-ctx.Done():
tx.Rollback()
return nil, ctx.Err()
}
return tx, err
Copy link
Member

Choose a reason for hiding this comment

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

return mc.Begin() is preferred Go style.

}

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")
}

}