Skip to content

Commit ce6aa2e

Browse files
kardianosbradfitz
authored andcommitted
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 042264e commit ce6aa2e

File tree

5 files changed

+96
-10
lines changed

5 files changed

+96
-10
lines changed

src/database/sql/ctxutil.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,22 @@ func ctxDriverBegin(ctx context.Context, ci driver.Conn) (driver.Tx, error) {
232232
if ciCtx, is := ci.(driver.ConnBeginContext); is {
233233
return ciCtx.BeginContext(ctx)
234234
}
235+
235236
if ctx.Done() == context.Background().Done() {
236237
return ci.Begin()
237238
}
238239

239-
// TODO(kardianos): check the transaction level in ctx. If set and non-default
240+
// Check the transaction level in ctx. If set and non-default
240241
// then return an error here as the BeginContext driver value is not supported.
242+
if level, ok := driver.IsolationFromContext(ctx); ok && level != driver.IsolationLevel(LevelDefault) {
243+
return nil, errors.New("sql: driver does not support non-default isolation level")
244+
}
245+
246+
// Check for a read-only parameter in ctx. If a read-only transaction is
247+
// requested return an error as the BeginContext driver value is not supported.
248+
if ro := driver.ReadOnlyFromContext(ctx); ro {
249+
return nil, errors.New("sql: driver does not support read-only transactions")
250+
}
241251

242252
type R struct {
243253
err error

src/database/sql/driver/driver.go

+32-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package driver
1010

1111
import (
1212
"context"
13+
"database/sql/internal"
1314
"errors"
1415
"reflect"
1516
)
@@ -132,12 +133,40 @@ type ConnPrepareContext interface {
132133
PrepareContext(ctx context.Context, query string) (Stmt, error)
133134
}
134135

136+
// IsolationLevel is the transaction isolation level stored in Context.
137+
//
138+
// This type should be considered identical to sql.IsolationLevel along
139+
// with any values defined on it.
140+
type IsolationLevel int
141+
142+
// IsolationFromContext extracts the isolation level from a Context.
143+
func IsolationFromContext(ctx context.Context) (level IsolationLevel, ok bool) {
144+
level, ok = ctx.Value(internal.IsolationLevelKey{}).(IsolationLevel)
145+
return level, ok
146+
}
147+
148+
// ReadOnlyFromContext extracts the read-only property from a Context.
149+
// When readonly is true the transaction must be set to read-only
150+
// or return an error.
151+
func ReadOnlyFromContext(ctx context.Context) (readonly bool) {
152+
readonly, _ = ctx.Value(internal.ReadOnlyKey{}).(bool)
153+
return readonly
154+
}
155+
135156
// ConnBeginContext enhances the Conn interface with context.
136157
type ConnBeginContext interface {
137158
// BeginContext starts and returns a new transaction.
138-
// the provided context should be used to roll the transaction back
139-
// if it is cancelled. If there is an isolation level in context
140-
// that is not supported by the driver an error must be returned.
159+
// The provided context should be used to roll the transaction back
160+
// if it is cancelled.
161+
//
162+
// This must call IsolationFromContext to determine if there is a set
163+
// isolation level. If the driver does not support setting the isolation
164+
// level and one is set or if there is a set isolation level
165+
// but the set level is not supported, an error must be returned.
166+
//
167+
// This must also call ReadOnlyFromContext to determine if the read-only
168+
// value is true to either set the read-only transaction property if supported
169+
// or return an error if it is not supported.
141170
BeginContext(ctx context.Context) (Tx, error)
142171
}
143172

src/database/sql/internal/types.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package internal
6+
7+
// Context keys that set transaction properties for sql.BeginContext.
8+
type (
9+
IsolationLevelKey struct{} // context value is driver.IsolationLevel
10+
ReadOnlyKey struct{} // context value is bool
11+
)

src/database/sql/sql.go

+40-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package sql
1515
import (
1616
"context"
1717
"database/sql/driver"
18+
"database/sql/internal"
1819
"errors"
1920
"fmt"
2021
"io"
@@ -89,6 +90,38 @@ func Param(name string, value interface{}) NamedParam {
8990
return NamedParam{Name: name, Value: value}
9091
}
9192

93+
// IsolationLevel is the transaction isolation level stored in Context.
94+
// The IsolationLevel is set with IsolationContext and the context
95+
// should be passed to BeginContext.
96+
type IsolationLevel int
97+
98+
// Various isolation levels that drivers may support in BeginContext.
99+
// If a driver does not support a given isolation level an error may be returned.
100+
const (
101+
LevelDefault IsolationLevel = iota
102+
LevelReadUncommited
103+
LevelReadCommited
104+
LevelWriteCommited
105+
LevelRepeatableRead
106+
LevelSnapshot
107+
LevelSerializable
108+
LevelLinearizable
109+
)
110+
111+
// IsolationContext returns a new Context that carries the provided isolation level.
112+
// The context must contain the isolation level before beginning the transaction
113+
// with BeginContext.
114+
func IsolationContext(ctx context.Context, level IsolationLevel) context.Context {
115+
return context.WithValue(ctx, internal.IsolationLevelKey{}, driver.IsolationLevel(level))
116+
}
117+
118+
// ReadOnlyWithContext returns a new Context that carries the provided
119+
// read-only transaction property. The context must contain the read-only property
120+
// before beginning the transaction with BeginContext.
121+
func ReadOnlyContext(ctx context.Context) context.Context {
122+
return context.WithValue(ctx, internal.ReadOnlyKey{}, true)
123+
}
124+
92125
// RawBytes is a byte slice that holds a reference to memory owned by
93126
// the database itself. After a Scan into a RawBytes, the slice is only
94127
// valid until the next call to Next, Scan, or Close.
@@ -1224,7 +1257,10 @@ func (db *DB) QueryRow(query string, args ...interface{}) *Row {
12241257
return db.QueryRowContext(context.Background(), query, args...)
12251258
}
12261259

1227-
// BeginContext starts a transaction. If a non-default isolation level is used
1260+
// BeginContext starts a transaction.
1261+
//
1262+
// An isolation level may be set by setting the value in the context
1263+
// before calling this. If a non-default isolation level is used
12281264
// that the driver doesn't support an error will be returned. Different drivers
12291265
// may have slightly different meanings for the same isolation level.
12301266
func (db *DB) BeginContext(ctx context.Context) (*Tx, error) {
@@ -2212,9 +2248,9 @@ func (rs *Rows) isClosed() bool {
22122248
return atomic.LoadInt32(&rs.closed) != 0
22132249
}
22142250

2215-
// Close closes the Rows, preventing further enumeration. If Next and
2216-
// NextResultSet both return
2217-
// false, the Rows are closed automatically and it will suffice to check the
2251+
// Close closes the Rows, preventing further enumeration. If Next is called
2252+
// and returns false and there are no further result sets,
2253+
// the Rows are closed automatically and it will suffice to check the
22182254
// result of Err. Close is idempotent and does not affect the result of Err.
22192255
func (rs *Rows) Close() error {
22202256
if !atomic.CompareAndSwapInt32(&rs.closed, 0, 1) {

src/go/build/deps_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ var pkgDeps = map[string][]string{
231231
"compress/lzw": {"L4"},
232232
"compress/zlib": {"L4", "compress/flate"},
233233
"context": {"errors", "fmt", "reflect", "sync", "time"},
234-
"database/sql": {"L4", "container/list", "context", "database/sql/driver"},
235-
"database/sql/driver": {"L4", "context", "time"},
234+
"database/sql": {"L4", "container/list", "context", "database/sql/driver", "database/sql/internal"},
235+
"database/sql/driver": {"L4", "context", "time", "database/sql/internal"},
236236
"debug/dwarf": {"L4"},
237237
"debug/elf": {"L4", "OS", "debug/dwarf", "compress/zlib"},
238238
"debug/gosym": {"L4"},

0 commit comments

Comments
 (0)