Skip to content

Commit 304082f

Browse files
committed
Merge branch 'main' into return-metadata-and-stats
2 parents 8ac5e6b + e2620e6 commit 304082f

File tree

13 files changed

+580
-180
lines changed

13 files changed

+580
-180
lines changed

benchmarks/go.mod

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@ module github.com/googleapis/go-sql-spanner/benchmarks
22

33
go 1.24
44

5-
toolchain go1.24.3
5+
toolchain go1.24.4
66

77
replace github.com/googleapis/go-sql-spanner => ../
88

99
require (
1010
cloud.google.com/go v0.121.2
1111
cloud.google.com/go/spanner v1.82.0
1212
github.com/google/uuid v1.6.0
13-
github.com/googleapis/go-sql-spanner v1.13.2
14-
google.golang.org/api v0.236.0
13+
github.com/googleapis/go-sql-spanner v1.14.0
14+
google.golang.org/api v0.237.0
1515
google.golang.org/grpc v1.73.0
1616
google.golang.org/protobuf v1.36.6
1717
)
1818

1919
require (
2020
cel.dev/expr v0.23.1 // indirect
21-
cloud.google.com/go/auth v0.16.1 // indirect
21+
cloud.google.com/go/auth v0.16.2 // indirect
2222
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
2323
cloud.google.com/go/compute/metadata v0.7.0 // indirect
2424
cloud.google.com/go/iam v1.5.2 // indirect
@@ -45,20 +45,20 @@ require (
4545
go.opencensus.io v0.24.0 // indirect
4646
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
4747
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect
48-
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
49-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
50-
go.opentelemetry.io/otel v1.35.0 // indirect
51-
go.opentelemetry.io/otel/metric v1.35.0 // indirect
52-
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
53-
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
54-
go.opentelemetry.io/otel/trace v1.35.0 // indirect
55-
golang.org/x/crypto v0.38.0 // indirect
56-
golang.org/x/net v0.40.0 // indirect
48+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
49+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
50+
go.opentelemetry.io/otel v1.36.0 // indirect
51+
go.opentelemetry.io/otel/metric v1.36.0 // indirect
52+
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
53+
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
54+
go.opentelemetry.io/otel/trace v1.36.0 // indirect
55+
golang.org/x/crypto v0.39.0 // indirect
56+
golang.org/x/net v0.41.0 // indirect
5757
golang.org/x/oauth2 v0.30.0 // indirect
58-
golang.org/x/sync v0.14.0 // indirect
58+
golang.org/x/sync v0.15.0 // indirect
5959
golang.org/x/sys v0.33.0 // indirect
60-
golang.org/x/text v0.25.0 // indirect
61-
golang.org/x/time v0.11.0 // indirect
60+
golang.org/x/text v0.26.0 // indirect
61+
golang.org/x/time v0.12.0 // indirect
6262
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
6363
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
6464
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect

benchmarks/go.sum

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo
101101
cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
102102
cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
103103
cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
104-
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
105-
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
104+
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
105+
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
106106
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
107107
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
108108
cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
@@ -925,20 +925,20 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
925925
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
926926
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
927927
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
928-
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
929-
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
930-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
931-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
932-
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
933-
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
934-
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
935-
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
936-
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
937-
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
938-
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
939-
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
940-
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
941-
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
928+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
929+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
930+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
931+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
932+
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
933+
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
934+
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
935+
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
936+
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
937+
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
938+
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
939+
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
940+
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
941+
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
942942
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
943943
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
944944
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
@@ -951,8 +951,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
951951
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
952952
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
953953
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
954-
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
955-
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
954+
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
955+
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
956956
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
957957
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
958958
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1067,8 +1067,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
10671067
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
10681068
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
10691069
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
1070-
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
1071-
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
1070+
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
1071+
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
10721072
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
10731073
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
10741074
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1116,8 +1116,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
11161116
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
11171117
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
11181118
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1119-
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
1120-
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
1119+
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
1120+
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
11211121
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
11221122
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
11231123
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1221,16 +1221,16 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
12211221
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
12221222
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
12231223
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
1224-
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
1225-
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
1224+
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
1225+
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
12261226
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
12271227
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
12281228
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
12291229
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
12301230
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
12311231
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1232-
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
1233-
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
1232+
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
1233+
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
12341234
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
12351235
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
12361236
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1365,8 +1365,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
13651365
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
13661366
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
13671367
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
1368-
google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0=
1369-
google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4=
1368+
google.golang.org/api v0.237.0 h1:MP7XVsGZesOsx3Q8WVa4sUdbrsTvDSOERd3Vh4xj/wc=
1369+
google.golang.org/api v0.237.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
13701370
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
13711371
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
13721372
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

conn.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"database/sql"
2020
"database/sql/driver"
21+
"errors"
2122
"log/slog"
2223
"slices"
2324
"time"
@@ -521,7 +522,11 @@ func (c *conn) runDDLBatch(ctx context.Context) (driver.Result, error) {
521522
return c.execDDL(ctx, statements...)
522523
}
523524

524-
func (c *conn) runDMLBatch(ctx context.Context) (driver.Result, error) {
525+
func (c *conn) runDMLBatch(ctx context.Context) (SpannerResult, error) {
526+
if c.inTransaction() {
527+
return c.tx.RunDmlBatch(ctx)
528+
}
529+
525530
statements := c.batch.statements
526531
options := c.batch.options
527532
options.QueryOptions.LastStatement = true
@@ -566,7 +571,7 @@ func (c *conn) execDDL(ctx context.Context, statements ...spanner.Statement) (dr
566571
return driver.ResultNoRows, nil
567572
}
568573

569-
func (c *conn) execBatchDML(ctx context.Context, statements []spanner.Statement, options ExecOptions) (driver.Result, error) {
574+
func (c *conn) execBatchDML(ctx context.Context, statements []spanner.Statement, options ExecOptions) (SpannerResult, error) {
570575
if len(statements) == 0 {
571576
return &result{}, nil
572577
}
@@ -585,7 +590,7 @@ func (c *conn) execBatchDML(ctx context.Context, statements []spanner.Statement,
585590
return err
586591
}, options.TransactionOptions)
587592
}
588-
return &result{rowsAffected: sum(affected)}, err
593+
return &result{rowsAffected: sum(affected), batchUpdateCounts: affected}, err
589594
}
590595

591596
func sum(affected []int64) int64 {
@@ -793,13 +798,22 @@ func (c *conn) queryContext(ctx context.Context, query string, execOptions ExecO
793798
return nil, err
794799
}
795800
}
796-
return &rows{
801+
res := &rows{
797802
it: iter,
798803
decodeOption: execOptions.DecodeOption,
799804
decodeToNativeArrays: execOptions.DecodeToNativeArrays,
800805
returnResultSetMetadata: execOptions.ReturnResultSetMetadata,
801806
returnResultSetStats: execOptions.ReturnResultSetStats,
802-
}, nil
807+
}
808+
if execOptions.DirectExecuteQuery {
809+
// This call to res.getColumns() triggers the execution of the statement, as it needs to fetch the metadata.
810+
res.getColumns()
811+
if res.dirtyErr != nil && !errors.Is(res.dirtyErr, iterator.Done) {
812+
_ = res.Close()
813+
return nil, res.dirtyErr
814+
}
815+
}
816+
return res, nil
803817
}
804818

805819
func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {

driver.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ func determineDefaultStatementCacheSize() {
112112
}
113113
}
114114

115+
// SpannerResult is the result type returned by Spanner connections for
116+
// DML batches. This interface extends the standard sql.Result interface
117+
// and adds a BatchRowsAffected function that returns the affected rows
118+
// per statement.
119+
type SpannerResult interface {
120+
driver.Result
121+
122+
// BatchRowsAffected returns the affected rows per statement in a DML batch.
123+
// It returns an error if the statement was not a DML batch.
124+
BatchRowsAffected() ([]int64, error)
125+
}
126+
115127
// ExecOptions can be passed in as an argument to the Query, QueryContext,
116128
// Exec, and ExecContext functions to specify additional execution options
117129
// for a statement.
@@ -185,6 +197,13 @@ type ExecOptions struct {
185197
// You have to call [sql.Rows.NextResultSet] after fetching all query data in
186198
// order to move to the result set that contains the spannerpb.ResultSetStats.
187199
ReturnResultSetStats bool
200+
201+
// DirectExecute determines whether a query is executed directly when the
202+
// [sql.DB.QueryContext] method is called, or whether the actual query execution
203+
// is delayed until the first call to [sql.Rows.Next]. The default is to delay
204+
// the execution. Set this flag to true to execute the query directly when
205+
// [sql.DB.QueryContext] is called.
206+
DirectExecuteQuery bool
188207
}
189208

190209
type DecodeOption int
@@ -1055,6 +1074,94 @@ func clearTempReadOnlyTransactionOptions(conn *sql.Conn) {
10551074
_ = conn.Close()
10561075
}
10571076

1077+
// DmlBatch is used to execute a batch of DML statements on Spanner in a single round-trip.
1078+
type DmlBatch interface {
1079+
// ExecContext buffers the given statement for execution on Spanner.
1080+
// All buffered statements are sent to Spanner as a single request when the DmlBatch
1081+
// function returns successfully.
1082+
ExecContext(ctx context.Context, dml string, args ...any) error
1083+
}
1084+
1085+
var _ DmlBatch = &dmlBatch{}
1086+
1087+
type dmlBatch struct {
1088+
conn *sql.Conn
1089+
}
1090+
1091+
// ExecuteBatchDml executes a batch of DML statements in a single round-trip to Spanner.
1092+
func ExecuteBatchDml(ctx context.Context, db *sql.DB, f func(ctx context.Context, batch DmlBatch) error) (SpannerResult, error) {
1093+
conn, err := db.Conn(ctx)
1094+
if err != nil {
1095+
return nil, err
1096+
}
1097+
return ExecuteBatchDmlOnConn(ctx, conn, f)
1098+
}
1099+
1100+
// ExecuteBatchDmlOnConn executes a batch of DML statements on a specific connection in a single round-trip to Spanner.
1101+
func ExecuteBatchDmlOnConn(ctx context.Context, connection *sql.Conn, f func(ctx context.Context, batch DmlBatch) error) (SpannerResult, error) {
1102+
// Start the DML batch.
1103+
if err := connection.Raw(func(driverConn any) error {
1104+
c, ok := driverConn.(*conn)
1105+
if !ok {
1106+
return spanner.ToSpannerError(status.Error(codes.InvalidArgument, "connection is not a Spanner connection"))
1107+
}
1108+
if _, err := c.startBatchDML(false); err != nil {
1109+
return err
1110+
}
1111+
return nil
1112+
}); err != nil {
1113+
return nil, err
1114+
}
1115+
1116+
// Let the callback execute the statements on the batch.
1117+
b := &dmlBatch{conn: connection}
1118+
if err := f(ctx, b); err != nil {
1119+
// The callback returned an error, abort the batch.
1120+
_ = connection.Raw(func(driverConn any) error {
1121+
c, _ := driverConn.(*conn)
1122+
_ = c.AbortBatch()
1123+
return nil
1124+
})
1125+
return nil, err
1126+
}
1127+
1128+
// Send the batch to Spanner.
1129+
var res SpannerResult
1130+
if err := connection.Raw(func(driverConn any) error {
1131+
// We know that the connection is a Spanner connection, so we don't bother to check that again here.
1132+
c, _ := driverConn.(*conn)
1133+
var err error
1134+
res, err = c.runDMLBatch(ctx)
1135+
if err != nil {
1136+
// Make sure the batch is removed from the connection/transaction.
1137+
_ = c.AbortBatch()
1138+
return err
1139+
}
1140+
return nil
1141+
}); err != nil {
1142+
return nil, err
1143+
}
1144+
1145+
return res, nil
1146+
}
1147+
1148+
func (b *dmlBatch) ExecContext(ctx context.Context, dml string, args ...any) error {
1149+
if err := b.conn.Raw(func(driverConn any) error {
1150+
c, ok := driverConn.(*conn)
1151+
if !ok {
1152+
return spanner.ToSpannerError(status.Error(codes.InvalidArgument, "connection is not a Spanner connection"))
1153+
}
1154+
if !c.InDMLBatch() {
1155+
return spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "this batch is no longer active"))
1156+
}
1157+
return nil
1158+
}); err != nil {
1159+
return err
1160+
}
1161+
_, err := b.conn.ExecContext(ctx, dml, args...)
1162+
return err
1163+
}
1164+
10581165
// AutocommitDMLMode indicates whether a single DML statement should be executed
10591166
// in a normal atomic transaction or as a Partitioned DML statement.
10601167
// See https://cloud.google.com/spanner/docs/dml-partitioned for more information.

0 commit comments

Comments
 (0)