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
import (
"context"
+ "database/sql/internal"
"errors"
"reflect"
)
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)
}
--- /dev/null
+// 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
+)
import (
"context"
"database/sql/driver"
+ "database/sql/internal"
"errors"
"fmt"
"io"
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.
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) {
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) {
"compress/lzw": {"L4"},
"compress/zlib": {"L4", "compress/flate"},
"context": {"errors", "fmt", "reflect", "sync", "time"},
- "database/sql": {"L4", "container/list", "context", "database/sql/driver"},
- "database/sql/driver": {"L4", "context", "time"},
+ "database/sql": {"L4", "container/list", "context", "database/sql/driver", "database/sql/internal"},
+ "database/sql/driver": {"L4", "context", "time", "database/sql/internal"},
"debug/dwarf": {"L4"},
"debug/elf": {"L4", "OS", "debug/dwarf", "compress/zlib"},
"debug/gosym": {"L4"},