Skip to content

Commit 316c7f3

Browse files
committed
support READ-ONLY transactions
1 parent a825be0 commit 316c7f3

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

connection_go18.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,18 @@ func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver
4545
// TODO: support isolation levels
4646
return nil, errors.New("mysql: isolation levels not supported")
4747
}
48-
if opts.ReadOnly {
49-
// TODO: support read-only transactions
50-
return nil, errors.New("mysql: read-only transactions not supported")
51-
}
5248

5349
if err := mc.watchCancel(ctx); err != nil {
5450
return nil, err
5551
}
5652

57-
tx, err := mc.Begin()
53+
var err error
54+
var tx driver.Tx
55+
if opts.ReadOnly {
56+
tx, err = mc.beginReadOnly()
57+
} else {
58+
tx, err = mc.Begin()
59+
}
5860
mc.finish()
5961
if err != nil {
6062
return nil, err
@@ -69,6 +71,19 @@ func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver
6971
return tx, err
7072
}
7173

74+
func (mc *mysqlConn) beginReadOnly() (driver.Tx, error) {
75+
if mc.closed.IsSet() {
76+
errLog.Print(ErrInvalidConn)
77+
return nil, driver.ErrBadConn
78+
}
79+
err := mc.exec("START TRANSACTION READ ONLY")
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
return &mysqlTx{mc}, nil
85+
}
86+
7287
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
7388
dargs, err := namedValueToValue(args)
7489
if err != nil {

driver_go18_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,41 @@ func TestContextCancelBegin(t *testing.T) {
468468
}
469469
})
470470
}
471+
472+
func TestContextBeginReadOnly(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+
tx, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
479+
ReadOnly: true,
480+
})
481+
if _, ok := err.(*MySQLError); ok {
482+
dbt.Skip("It seems that your MySQL does not support READ ONLY transactions")
483+
return
484+
} else if err != nil {
485+
dbt.Fatal(err)
486+
}
487+
488+
// INSERT queries fail in a READ ONLY transaction.
489+
_, err = tx.ExecContext(ctx, "INSERT INTO test VALUES (1)")
490+
if _, ok := err.(*MySQLError); !ok {
491+
dbt.Errorf("expected MySQLError, got %v", err)
492+
}
493+
494+
// SELECT queries can be executed.
495+
var v int
496+
row := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
497+
if err := row.Scan(&v); err != nil {
498+
dbt.Fatal(err)
499+
}
500+
if v != 0 {
501+
dbt.Errorf("expected val to be 0, got %d", v)
502+
}
503+
504+
if err := tx.Commit(); err != nil {
505+
dbt.Fatal(err)
506+
}
507+
})
508+
}

0 commit comments

Comments
 (0)