aboutsummaryrefslogtreecommitdiff
path: root/src/database/sql
diff options
context:
space:
mode:
Diffstat (limited to 'src/database/sql')
-rw-r--r--src/database/sql/ctxutil.go12
-rw-r--r--src/database/sql/driver/driver.go35
-rw-r--r--src/database/sql/internal/types.go11
-rw-r--r--src/database/sql/sql.go44
4 files changed, 94 insertions, 8 deletions
diff --git a/src/database/sql/ctxutil.go b/src/database/sql/ctxutil.go
index 173f6a9d2b..ddc4b7228f 100644
--- a/src/database/sql/ctxutil.go
+++ b/src/database/sql/ctxutil.go
@@ -232,12 +232,22 @@ func ctxDriverBegin(ctx context.Context, ci driver.Conn) (driver.Tx, error) {
if ciCtx, is := ci.(driver.ConnBeginContext); is {
return ciCtx.BeginContext(ctx)
}
+
if ctx.Done() == context.Background().Done() {
return ci.Begin()
}
- // TODO(kardianos): check the transaction level in ctx. If set and non-default
+ // Check the transaction level in ctx. If set and non-default
// then return an error here as the BeginContext driver value is not supported.
+ if level, ok := driver.IsolationFromContext(ctx); ok && level != driver.IsolationLevel(LevelDefault) {
+ return nil, errors.New("sql: driver does not support non-default isolation level")
+ }
+
+ // Check for a read-only parameter in ctx. If a read-only transaction is
+ // requested return an error as the BeginContext driver value is not supported.
+ if ro := driver.ReadOnlyFromContext(ctx); ro {
+ return nil, errors.New("sql: driver does not support read-only transactions")
+ }
type R struct {
err error
diff --git a/src/database/sql/driver/driver.go b/src/database/sql/driver/driver.go
index ad988cc785..bc6aa3b26e 100644
--- a/src/database/sql/driver/driver.go
+++ b/src/database/sql/driver/driver.go
@@ -10,6 +10,7 @@ package driver
import (
"context"
+ "database/sql/internal"
"errors"
"reflect"
)
@@ -132,12 +133,40 @@ type ConnPrepareContext interface {
PrepareContext(ctx context.Context, query string) (Stmt, error)
}
+// IsolationLevel is the transaction isolation level stored in Context.
+//
+// This type should be considered identical to sql.IsolationLevel along
+// with any values defined on it.
+type IsolationLevel int
+
+// IsolationFromContext extracts the isolation level from a Context.
+func IsolationFromContext(ctx context.Context) (level IsolationLevel, ok bool) {
+ level, ok = ctx.Value(internal.IsolationLevelKey{}).(IsolationLevel)
+ return level, ok
+}
+
+// ReadOnlyFromContext extracts the read-only property from a Context.
+// When readonly is true the transaction must be set to read-only
+// or return an error.
+func ReadOnlyFromContext(ctx context.Context) (readonly bool) {
+ readonly, _ = ctx.Value(internal.ReadOnlyKey{}).(bool)
+ return readonly
+}
+
// ConnBeginContext enhances the Conn interface with context.
type ConnBeginContext interface {
// BeginContext starts and returns a new transaction.
- // the provided context should be used to roll the transaction back
- // if it is cancelled. If there is an isolation level in context
- // that is not supported by the driver an error must be returned.
+ // The provided context should be used to roll the transaction back
+ // if it is cancelled.
+ //
+ // This must call IsolationFromContext to determine if there is a set
+ // isolation level. If the driver does not support setting the isolation
+ // level and one is set or if there is a set isolation level
+ // but the set level is not supported, an error must be returned.
+ //
+ // This must also call ReadOnlyFromContext to determine if the read-only
+ // value is true to either set the read-only transaction property if supported
+ // or return an error if it is not supported.
BeginContext(ctx context.Context) (Tx, error)
}
diff --git a/src/database/sql/internal/types.go b/src/database/sql/internal/types.go
new file mode 100644
index 0000000000..1895144cb2
--- /dev/null
+++ b/src/database/sql/internal/types.go
@@ -0,0 +1,11 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+// Context keys that set transaction properties for sql.BeginContext.
+type (
+ IsolationLevelKey struct{} // context value is driver.IsolationLevel
+ ReadOnlyKey struct{} // context value is bool
+)
diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go
index d363008993..3cef4b6404 100644
--- a/src/database/sql/sql.go
+++ b/src/database/sql/sql.go
@@ -15,6 +15,7 @@ package sql
import (
"context"
"database/sql/driver"
+ "database/sql/internal"
"errors"
"fmt"
"io"
@@ -89,6 +90,38 @@ func Param(name string, value interface{}) NamedParam {
return NamedParam{Name: name, Value: value}
}
+// IsolationLevel is the transaction isolation level stored in Context.
+// The IsolationLevel is set with IsolationContext and the context
+// should be passed to BeginContext.
+type IsolationLevel int
+
+// Various isolation levels that drivers may support in BeginContext.
+// If a driver does not support a given isolation level an error may be returned.
+const (
+ LevelDefault IsolationLevel = iota
+ LevelReadUncommited
+ LevelReadCommited
+ LevelWriteCommited
+ LevelRepeatableRead
+ LevelSnapshot
+ LevelSerializable
+ LevelLinearizable
+)
+
+// IsolationContext returns a new Context that carries the provided isolation level.
+// The context must contain the isolation level before beginning the transaction
+// with BeginContext.
+func IsolationContext(ctx context.Context, level IsolationLevel) context.Context {
+ return context.WithValue(ctx, internal.IsolationLevelKey{}, driver.IsolationLevel(level))
+}
+
+// ReadOnlyWithContext returns a new Context that carries the provided
+// read-only transaction property. The context must contain the read-only property
+// before beginning the transaction with BeginContext.
+func ReadOnlyContext(ctx context.Context) context.Context {
+ return context.WithValue(ctx, internal.ReadOnlyKey{}, true)
+}
+
// RawBytes is a byte slice that holds a reference to memory owned by
// the database itself. After a Scan into a RawBytes, the slice is only
// valid until the next call to Next, Scan, or Close.
@@ -1224,7 +1257,10 @@ func (db *DB) QueryRow(query string, args ...interface{}) *Row {
return db.QueryRowContext(context.Background(), query, args...)
}
-// BeginContext starts a transaction. If a non-default isolation level is used
+// BeginContext starts a transaction.
+//
+// An isolation level may be set by setting the value in the context
+// before calling this. If a non-default isolation level is used
// that the driver doesn't support an error will be returned. Different drivers
// may have slightly different meanings for the same isolation level.
func (db *DB) BeginContext(ctx context.Context) (*Tx, error) {
@@ -2212,9 +2248,9 @@ func (rs *Rows) isClosed() bool {
return atomic.LoadInt32(&rs.closed) != 0
}
-// Close closes the Rows, preventing further enumeration. If Next and
-// NextResultSet both return
-// false, the Rows are closed automatically and it will suffice to check the
+// Close closes the Rows, preventing further enumeration. If Next is called
+// and returns false and there are no further result sets,
+// the Rows are closed automatically and it will suffice to check the
// result of Err. Close is idempotent and does not affect the result of Err.
func (rs *Rows) Close() error {
if !atomic.CompareAndSwapInt32(&rs.closed, 0, 1) {