Commit ce6aa2eb authored by Daniel Theophanes's avatar Daniel Theophanes Committed by Brad Fitzpatrick

database/sql: add context helper methods and transaction types

Prior to this change, it was implied that transaction properties
would be carried in the context value. However, no such properties
were defined, not even common ones. Define two common properties:
isolation level and read-only. Drivers may choose to support
additional transaction properties. It is not expected any
further transaction properties will be added in the future.

Change-Id: I2f680115a14a1333c65ba6f943d9a1149d412918
Reviewed-on: https://go-review.googlesource.com/31258
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 042264ef
......@@ -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
......
......@@ -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)
}
......
// 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
)
......@@ -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) {
......
......@@ -231,8 +231,8 @@ var pkgDeps = map[string][]string{
"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"},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment