From 124ead1bd5a8b03acc34280de1f8aabe51f0befa Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Fri, 15 Sep 2017 10:53:09 +0900 Subject: [PATCH] support READ-ONLY transactions --- connection.go | 12 +++++++++++- connection_go18.go | 9 +-------- driver_go18_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/connection.go b/connection.go index b07528653..948a59561 100644 --- a/connection.go +++ b/connection.go @@ -92,11 +92,21 @@ func (mc *mysqlConn) markBadConn(err error) error { } func (mc *mysqlConn) Begin() (driver.Tx, error) { + return mc.begin(false) +} + +func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) { if mc.closed.IsSet() { errLog.Print(ErrInvalidConn) return nil, driver.ErrBadConn } - err := mc.exec("START TRANSACTION") + var q string + if readOnly { + q = "START TRANSACTION READ ONLY" + } else { + q = "START TRANSACTION" + } + err := mc.exec(q) if err == nil { return &mysqlTx{mc}, err } diff --git a/connection_go18.go b/connection_go18.go index 3ff6ff24f..48a9cca64 100644 --- a/connection_go18.go +++ b/connection_go18.go @@ -14,7 +14,6 @@ import ( "context" "database/sql" "database/sql/driver" - "errors" ) // Ping implements driver.Pinger interface @@ -41,15 +40,9 @@ 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 opts.ReadOnly { - // TODO: support read-only transactions - return nil, errors.New("mysql: read-only transactions not supported") - } - if err := mc.watchCancel(ctx); err != nil { return nil, err } - defer mc.finish() if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault { @@ -63,7 +56,7 @@ func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver } } - return mc.Begin() + return mc.begin(opts.ReadOnly) } func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { diff --git a/driver_go18_test.go b/driver_go18_test.go index f2184add0..4962838f2 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -520,3 +520,41 @@ func TestContextBeginIsolationLevel(t *testing.T) { tx2.Commit() }) } + +func TestContextBeginReadOnly(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + dbt.mustExec("CREATE TABLE test (v INTEGER)") + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tx, err := dbt.db.BeginTx(ctx, &sql.TxOptions{ + ReadOnly: true, + }) + if _, ok := err.(*MySQLError); ok { + dbt.Skip("It seems that your MySQL does not support READ ONLY transactions") + return + } else if err != nil { + dbt.Fatal(err) + } + + // INSERT queries fail in a READ ONLY transaction. + _, err = tx.ExecContext(ctx, "INSERT INTO test VALUES (1)") + if _, ok := err.(*MySQLError); !ok { + dbt.Errorf("expected MySQLError, got %v", err) + } + + // SELECT queries can be executed. + var v int + row := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM test") + if err := row.Scan(&v); err != nil { + dbt.Fatal(err) + } + if v != 0 { + dbt.Errorf("expected val to be 0, got %d", v) + } + + if err := tx.Commit(); err != nil { + dbt.Fatal(err) + } + }) +}