Skip to content

Commit fea15af

Browse files
committed
feat: add transactions
1 parent 21a1c4a commit fea15af

File tree

5 files changed

+132
-30
lines changed

5 files changed

+132
-30
lines changed

spannerlib/exported/connection.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ type Connection struct {
5656
backend *backend.SpannerConnection
5757
}
5858

59+
type queryExecutor interface {
60+
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
61+
}
62+
5963
func (conn *Connection) close() *Message {
6064
conn.results.Range(func(key, value interface{}) bool {
6165
res := value.(*rows)
@@ -80,6 +84,7 @@ func (conn *Connection) BeginTransaction(txOpts *spannerpb.TransactionOptions) *
8084
id := conn.transactionsIdx.Add(1)
8185
res := &transaction{
8286
backend: tx,
87+
conn: conn,
8388
}
8489
conn.transactions.Store(id, res)
8590
return idMessage(id)
@@ -96,6 +101,24 @@ func convertIsolationLevel(level spannerpb.TransactionOptions_IsolationLevel) sq
96101
}
97102

98103
func (conn *Connection) Execute(statement *spannerpb.ExecuteBatchDmlRequest_Statement) *Message {
104+
return execute(conn, conn.backend.Conn, statement)
105+
}
106+
107+
func execute(conn *Connection, executor queryExecutor, statement *spannerpb.ExecuteBatchDmlRequest_Statement) *Message {
108+
params := extractParams(statement)
109+
it, err := executor.QueryContext(context.Background(), statement.Sql, params...)
110+
if err != nil {
111+
return errMessage(err)
112+
}
113+
id := conn.resultsIdx.Add(1)
114+
res := &rows{
115+
backend: it,
116+
}
117+
conn.results.Store(id, res)
118+
return idMessage(id)
119+
}
120+
121+
func extractParams(statement *spannerpb.ExecuteBatchDmlRequest_Statement) []any {
99122
paramsLen := 1
100123
if statement.Params != nil {
101124
paramsLen = 1 + len(statement.Params.Fields)
@@ -114,14 +137,5 @@ func (conn *Connection) Execute(statement *spannerpb.ExecuteBatchDmlRequest_Stat
114137
params = append(params, sql.Named(param, genericValue))
115138
}
116139
}
117-
it, err := conn.backend.Conn.QueryContext(context.Background(), statement.Sql, params...)
118-
if err != nil {
119-
return errMessage(err)
120-
}
121-
id := conn.resultsIdx.Add(1)
122-
res := &rows{
123-
backend: it,
124-
}
125-
conn.results.Store(id, res)
126-
return idMessage(id)
140+
return params
127141
}

spannerlib/exported/transaction.go

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,83 @@
11
package exported
22

3-
import "database/sql"
3+
import (
4+
"database/sql"
45

5-
type transaction struct {
6-
backend *sql.Tx
6+
"cloud.google.com/go/spanner/apiv1/spannerpb"
7+
"google.golang.org/protobuf/proto"
8+
)
9+
10+
func ExecuteTransaction(poolId, connId, txId int64, statementBytes []byte) *Message {
11+
statement := spannerpb.ExecuteBatchDmlRequest_Statement{}
12+
if err := proto.Unmarshal(statementBytes, &statement); err != nil {
13+
return errMessage(err)
14+
}
15+
tx, err := findTx(poolId, connId, txId)
16+
if err != nil {
17+
return errMessage(err)
18+
}
19+
return tx.Execute(&statement)
720
}
821

922
func Commit(poolId, connId, txId int64) *Message {
10-
res, err := findTx(poolId, connId, txId)
23+
tx, err := findTx(poolId, connId, txId)
24+
if err != nil {
25+
return errMessage(err)
26+
}
27+
conn, err := findConnection(poolId, connId)
28+
if err != nil {
29+
return errMessage(err)
30+
}
31+
conn.transactions.Delete(txId)
32+
return tx.Commit()
33+
}
34+
35+
func Rollback(poolId, connId, txId int64) *Message {
36+
tx, err := findTx(poolId, connId, txId)
37+
if err != nil {
38+
return errMessage(err)
39+
}
40+
conn, err := findConnection(poolId, connId)
1141
if err != nil {
1242
return errMessage(err)
1343
}
14-
return res.Metadata()
44+
conn.transactions.Delete(txId)
45+
return tx.Rollback()
46+
}
47+
48+
type transaction struct {
49+
backend *sql.Tx
50+
conn *Connection
51+
closed bool
52+
}
53+
54+
func (tx *transaction) Close() *Message {
55+
if tx.closed {
56+
return &Message{}
57+
}
58+
tx.closed = true
59+
if err := tx.backend.Rollback(); err != nil {
60+
return errMessage(err)
61+
}
62+
return &Message{}
63+
}
64+
65+
func (tx *transaction) Execute(statement *spannerpb.ExecuteBatchDmlRequest_Statement) *Message {
66+
return execute(tx.conn, tx.backend, statement)
67+
}
68+
69+
func (tx *transaction) Commit() *Message {
70+
tx.closed = true
71+
if err := tx.backend.Commit(); err != nil {
72+
return errMessage(err)
73+
}
74+
return &Message{}
75+
}
76+
77+
func (tx *transaction) Rollback() *Message {
78+
tx.closed = true
79+
if err := tx.backend.Rollback(); err != nil {
80+
return errMessage(err)
81+
}
82+
return &Message{}
1583
}

spannerlib/go.mod

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ require (
88
cloud.google.com/go/spanner v1.82.0
99
github.com/google/uuid v1.6.0
1010
github.com/googleapis/go-sql-spanner v1.13.2
11-
google.golang.org/grpc v1.72.1
11+
google.golang.org/grpc v1.73.0
1212
google.golang.org/protobuf v1.36.6
1313
)
1414

1515
require (
1616
cel.dev/expr v0.23.1 // indirect
17-
cloud.google.com/go v0.121.1 // indirect
17+
cloud.google.com/go v0.121.2 // indirect
1818
cloud.google.com/go/auth v0.16.1 // indirect
1919
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
20-
cloud.google.com/go/compute/metadata v0.6.0 // indirect
20+
cloud.google.com/go/compute/metadata v0.7.0 // indirect
2121
cloud.google.com/go/iam v1.5.2 // indirect
2222
cloud.google.com/go/longrunning v0.6.7 // indirect
2323
cloud.google.com/go/monitoring v1.24.2 // indirect
@@ -35,6 +35,7 @@ require (
3535
github.com/google/s2a-go v0.1.9 // indirect
3636
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
3737
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
38+
github.com/hashicorp/golang-lru v0.5.1 // indirect
3839
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
3940
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
4041
github.com/zeebo/errs v1.4.0 // indirect
@@ -55,8 +56,8 @@ require (
5556
golang.org/x/sys v0.33.0 // indirect
5657
golang.org/x/text v0.25.0 // indirect
5758
golang.org/x/time v0.11.0 // indirect
58-
google.golang.org/api v0.233.0 // indirect
59+
google.golang.org/api v0.236.0 // indirect
5960
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
6061
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
61-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
62+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
6263
)

spannerlib/go.sum

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
3838
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
3939
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
4040
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
41-
cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
42-
cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
41+
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
42+
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
4343
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
4444
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
4545
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -184,8 +184,8 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ
184184
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
185185
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
186186
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
187-
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
188-
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
187+
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
188+
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
189189
cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=
190190
cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=
191191
cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=
@@ -828,6 +828,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
828828
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
829829
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
830830
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
831+
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
831832
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
832833
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
833834
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -1363,8 +1364,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
13631364
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
13641365
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
13651366
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
1366-
google.golang.org/api v0.233.0 h1:iGZfjXAJiUFSSaekVB7LzXl6tRfEKhUN7FkZN++07tI=
1367-
google.golang.org/api v0.233.0/go.mod h1:TCIVLLlcwunlMpZIhIp7Ltk77W+vUSdUKAAIlbxY44c=
1367+
google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0=
1368+
google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4=
13681369
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
13691370
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
13701371
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1508,8 +1509,8 @@ google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRx
15081509
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
15091510
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
15101511
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
1511-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
1512-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
1512+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
1513+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
15131514
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
15141515
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
15151516
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1551,8 +1552,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v
15511552
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
15521553
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
15531554
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
1554-
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
1555-
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
1555+
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
1556+
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
15561557
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
15571558
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
15581559
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=

spannerlib/main.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ func Execute(poolId, connectionId int64, statement []byte) (int64, int32, int64,
7171
return pin(msg)
7272
}
7373

74+
//export ExecuteTransaction
75+
func ExecuteTransaction(poolId, connectionId, txId int64, statement []byte) (int64, int32, int64, int32, unsafe.Pointer) {
76+
msg := exported.ExecuteTransaction(poolId, connectionId, txId, statement)
77+
return pin(msg)
78+
}
79+
7480
//export Metadata
7581
func Metadata(poolId, connId, rowsId int64) (int64, int32, int64, int32, unsafe.Pointer) {
7682
msg := exported.Metadata(poolId, connId, rowsId)
@@ -100,3 +106,15 @@ func BeginTransaction(poolId, connectionId int64, txOpts []byte) (int64, int32,
100106
msg := exported.BeginTransaction(poolId, connectionId, txOpts)
101107
return pin(msg)
102108
}
109+
110+
//export Commit
111+
func Commit(poolId, connectionId, txId int64) (int64, int32, int64, int32, unsafe.Pointer) {
112+
msg := exported.Commit(poolId, connectionId, txId)
113+
return pin(msg)
114+
}
115+
116+
//export Rollback
117+
func Rollback(poolId, connectionId, txId int64) (int64, int32, int64, int32, unsafe.Pointer) {
118+
msg := exported.Rollback(poolId, connectionId, txId)
119+
return pin(msg)
120+
}

0 commit comments

Comments
 (0)