Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
G
golang
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
golang
Commits
e77099da
Commit
e77099da
authored
Nov 28, 2011
by
Brad Fitzpatrick
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
sql: add Tx.Stmt to use an existing prepared stmt in a transaction
R=rsc CC=golang-dev
https://golang.org/cl/5433059
parent
23227f3d
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
83 additions
and
20 deletions
+83
-20
sql.go
src/pkg/exp/sql/sql.go
+60
-19
sql_test.go
src/pkg/exp/sql/sql_test.go
+23
-1
No files found.
src/pkg/exp/sql/sql.go
View file @
e77099da
...
...
@@ -344,25 +344,26 @@ func (tx *Tx) Rollback() error {
return
tx
.
txi
.
Rollback
()
}
// Prepare creates a prepared statement.
// Prepare creates a prepared statement
for use within a transaction
.
//
// The statement is only valid within the scope of this transaction.
// The returned statement operates within the transaction and can no longer
// be used once the transaction has been committed or rolled back.
//
// To use an existing prepared statement on this transaction, see Tx.Stmt.
func
(
tx
*
Tx
)
Prepare
(
query
string
)
(
*
Stmt
,
error
)
{
// TODO(bradfitz): the restriction that the returned statement
// is only valid for this Transaction is lame and negates a
// lot of the benefit of prepared statements. We could be
// more efficient here and either provide a method to take an
// existing Stmt (created on perhaps a different Conn), and
// re-create it on this Conn if necessary. Or, better: keep a
// map in DB of query string to Stmts, and have Stmt.Execute
// do the right thing and re-prepare if the Conn in use
// doesn't have that prepared statement. But we'll want to
// avoid caching the statement in the case where we only call
// conn.Prepare implicitly (such as in db.Exec or tx.Exec),
// but the caller package can't be holding a reference to the
// returned statement. Perhaps just looking at the reference
// count (by noting Stmt.Close) would be enough. We might also
// want a finalizer on Stmt to drop the reference count.
// TODO(bradfitz): We could be more efficient here and either
// provide a method to take an existing Stmt (created on
// perhaps a different Conn), and re-create it on this Conn if
// necessary. Or, better: keep a map in DB of query string to
// Stmts, and have Stmt.Execute do the right thing and
// re-prepare if the Conn in use doesn't have that prepared
// statement. But we'll want to avoid caching the statement
// in the case where we only call conn.Prepare implicitly
// (such as in db.Exec or tx.Exec), but the caller package
// can't be holding a reference to the returned statement.
// Perhaps just looking at the reference count (by noting
// Stmt.Close) would be enough. We might also want a finalizer
// on Stmt to drop the reference count.
ci
,
err
:=
tx
.
grabConn
()
if
err
!=
nil
{
return
nil
,
err
...
...
@@ -383,6 +384,39 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) {
return
stmt
,
nil
}
// Stmt returns a transaction-specific prepared statement from
// an existing statement.
//
// Example:
// updateMoney, err := db.Prepare("UPDATE balance SET money=money+? WHERE id=?")
// ...
// tx, err := db.Begin()
// ...
// res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203)
func
(
tx
*
Tx
)
Stmt
(
stmt
*
Stmt
)
*
Stmt
{
// TODO(bradfitz): optimize this. Currently this re-prepares
// each time. This is fine for now to illustrate the API but
// we should really cache already-prepared statements
// per-Conn. See also the big comment in Tx.Prepare.
if
tx
.
db
!=
stmt
.
db
{
return
&
Stmt
{
stickyErr
:
errors
.
New
(
"sql: Tx.Stmt: statement from different database used"
)}
}
ci
,
err
:=
tx
.
grabConn
()
if
err
!=
nil
{
return
&
Stmt
{
stickyErr
:
err
}
}
defer
tx
.
releaseConn
()
si
,
err
:=
ci
.
Prepare
(
stmt
.
query
)
return
&
Stmt
{
db
:
tx
.
db
,
tx
:
tx
,
txsi
:
si
,
query
:
stmt
.
query
,
stickyErr
:
err
,
}
}
// Exec executes a query that doesn't return rows.
// For example: an INSERT and UPDATE.
func
(
tx
*
Tx
)
Exec
(
query
string
,
args
...
interface
{})
(
Result
,
error
)
{
...
...
@@ -448,8 +482,9 @@ type connStmt struct {
// Stmt is a prepared statement. Stmt is safe for concurrent use by multiple goroutines.
type
Stmt
struct
{
// Immutable:
db
*
DB
// where we came from
query
string
// that created the Sttm
db
*
DB
// where we came from
query
string
// that created the Stmt
stickyErr
error
// if non-nil, this error is returned for all operations
// If in a transaction, else both nil:
tx
*
Tx
...
...
@@ -513,6 +548,9 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) {
// statement, a function to call to release the connection, and a
// statement bound to that connection.
func
(
s
*
Stmt
)
connStmt
()
(
ci
driver
.
Conn
,
releaseConn
func
(),
si
driver
.
Stmt
,
err
error
)
{
if
s
.
stickyErr
!=
nil
{
return
nil
,
nil
,
nil
,
s
.
stickyErr
}
s
.
mu
.
Lock
()
if
s
.
closed
{
s
.
mu
.
Unlock
()
...
...
@@ -621,6 +659,9 @@ func (s *Stmt) QueryRow(args ...interface{}) *Row {
// Close closes the statement.
func
(
s
*
Stmt
)
Close
()
error
{
if
s
.
stickyErr
!=
nil
{
return
s
.
stickyErr
}
s
.
mu
.
Lock
()
defer
s
.
mu
.
Unlock
()
if
s
.
closed
{
...
...
src/pkg/exp/sql/sql_test.go
View file @
e77099da
...
...
@@ -166,7 +166,7 @@ func TestBogusPreboundParameters(t *testing.T) {
}
}
func
Test
Db
(
t
*
testing
.
T
)
{
func
Test
Exec
(
t
*
testing
.
T
)
{
db
:=
newTestDB
(
t
,
"foo"
)
defer
closeDB
(
t
,
db
)
exec
(
t
,
db
,
"CREATE|t1|name=string,age=int32,dead=bool"
)
...
...
@@ -206,3 +206,25 @@ func TestDb(t *testing.T) {
}
}
}
func
TestTxStmt
(
t
*
testing
.
T
)
{
db
:=
newTestDB
(
t
,
""
)
defer
closeDB
(
t
,
db
)
exec
(
t
,
db
,
"CREATE|t1|name=string,age=int32,dead=bool"
)
stmt
,
err
:=
db
.
Prepare
(
"INSERT|t1|name=?,age=?"
)
if
err
!=
nil
{
t
.
Fatalf
(
"Stmt, err = %v, %v"
,
stmt
,
err
)
}
tx
,
err
:=
db
.
Begin
()
if
err
!=
nil
{
t
.
Fatalf
(
"Begin = %v"
,
err
)
}
_
,
err
=
tx
.
Stmt
(
stmt
)
.
Exec
(
"Bobby"
,
7
)
if
err
!=
nil
{
t
.
Fatalf
(
"Exec = %v"
,
err
)
}
err
=
tx
.
Commit
()
if
err
!=
nil
{
t
.
Fatalf
(
"Commit = %v"
,
err
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment