@@ -24,6 +24,7 @@ import (
2424 "cloud.google.com/go/spanner/apiv1/spannerpb"
2525 "github.com/googleapis/go-sql-spanner/testutil"
2626 "google.golang.org/grpc/codes"
27+ "google.golang.org/protobuf/types/known/structpb"
2728)
2829
2930func TestCreateAndCloseConnection (t * testing.T ) {
@@ -143,3 +144,159 @@ func TestCloseConnectionTwice(t *testing.T) {
143144 t .Fatalf ("ClosePool returned unexpected error: %v" , err )
144145 }
145146}
147+
148+ func TestWriteMutations (t * testing.T ) {
149+ t .Parallel ()
150+
151+ ctx := context .Background ()
152+ server , teardown := setupMockServer (t )
153+ defer teardown ()
154+ dsn := fmt .Sprintf ("%s/projects/p/instances/i/databases/d?useplaintext=true" , server .Address )
155+
156+ poolId , err := CreatePool (ctx , dsn )
157+ if err != nil {
158+ t .Fatalf ("CreatePool returned unexpected error: %v" , err )
159+ }
160+ connId , err := CreateConnection (ctx , poolId )
161+ if err != nil {
162+ t .Fatalf ("CreateConnection returned unexpected error: %v" , err )
163+ }
164+
165+ mutations := & spannerpb.BatchWriteRequest_MutationGroup {Mutations : []* spannerpb.Mutation {
166+ {Operation : & spannerpb.Mutation_Insert {Insert : & spannerpb.Mutation_Write {
167+ Table : "my_table" ,
168+ Columns : []string {"id" , "value" },
169+ Values : []* structpb.ListValue {
170+ {Values : []* structpb.Value {structpb .NewStringValue ("1" ), structpb .NewStringValue ("One" )}},
171+ {Values : []* structpb.Value {structpb .NewStringValue ("2" ), structpb .NewStringValue ("Two" )}},
172+ {Values : []* structpb.Value {structpb .NewStringValue ("3" ), structpb .NewStringValue ("Three" )}},
173+ },
174+ }}},
175+ {Operation : & spannerpb.Mutation_Update {Update : & spannerpb.Mutation_Write {
176+ Table : "my_table" ,
177+ Columns : []string {"id" , "value" },
178+ Values : []* structpb.ListValue {
179+ {Values : []* structpb.Value {structpb .NewStringValue ("0" ), structpb .NewStringValue ("Zero" )}},
180+ },
181+ }}},
182+ }}
183+ resp , err := WriteMutations (ctx , poolId , connId , mutations )
184+ if err != nil {
185+ t .Fatalf ("WriteMutations returned unexpected error: %v" , err )
186+ }
187+ if resp .CommitTimestamp == nil {
188+ t .Fatalf ("CommitTimestamp is nil" )
189+ }
190+ requests := server .TestSpanner .DrainRequestsFromServer ()
191+ beginRequests := testutil .RequestsOfType (requests , reflect .TypeOf (& spannerpb.BeginTransactionRequest {}))
192+ if g , w := len (beginRequests ), 1 ; g != w {
193+ t .Fatalf ("num BeginTransaction requests mismatch\n Got: %d\n Want: %d" , g , w )
194+ }
195+ commitRequests := testutil .RequestsOfType (requests , reflect .TypeOf (& spannerpb.CommitRequest {}))
196+ if g , w := len (commitRequests ), 1 ; g != w {
197+ t .Fatalf ("num CommitRequests mismatch\n Got: %d\n Want: %d" , g , w )
198+ }
199+ commitRequest := commitRequests [0 ].(* spannerpb.CommitRequest )
200+ if g , w := len (commitRequest .Mutations ), 2 ; g != w {
201+ t .Fatalf ("num mutations mismatch\n Got: %d\n Want: %d" , g , w )
202+ }
203+
204+ // Write the same mutations in a transaction.
205+ if err := BeginTransaction (ctx , poolId , connId , & spannerpb.TransactionOptions {}); err != nil {
206+ t .Fatalf ("BeginTransaction returned unexpected error: %v" , err )
207+ }
208+ resp , err = WriteMutations (ctx , poolId , connId , mutations )
209+ if err != nil {
210+ t .Fatalf ("WriteMutations returned unexpected error: %v" , err )
211+ }
212+ if resp != nil {
213+ t .Fatalf ("WriteMutations returned unexpected response: %v" , resp )
214+ }
215+ resp , err = Commit (ctx , poolId , connId )
216+ if err != nil {
217+ t .Fatalf ("Commit returned unexpected error: %v" , err )
218+ }
219+ if resp == nil {
220+ t .Fatalf ("Commit returned nil response" )
221+ }
222+ if resp .CommitTimestamp == nil {
223+ t .Fatalf ("CommitTimestamp is nil" )
224+ }
225+ requests = server .TestSpanner .DrainRequestsFromServer ()
226+ beginRequests = testutil .RequestsOfType (requests , reflect .TypeOf (& spannerpb.BeginTransactionRequest {}))
227+ if g , w := len (beginRequests ), 1 ; g != w {
228+ t .Fatalf ("num BeginTransaction requests mismatch\n Got: %d\n Want: %d" , g , w )
229+ }
230+ commitRequests = testutil .RequestsOfType (requests , reflect .TypeOf (& spannerpb.CommitRequest {}))
231+ if g , w := len (commitRequests ), 1 ; g != w {
232+ t .Fatalf ("num CommitRequests mismatch\n Got: %d\n Want: %d" , g , w )
233+ }
234+ commitRequest = commitRequests [0 ].(* spannerpb.CommitRequest )
235+ if g , w := len (commitRequest .Mutations ), 2 ; g != w {
236+ t .Fatalf ("num mutations mismatch\n Got: %d\n Want: %d" , g , w )
237+ }
238+
239+ if err := ClosePool (ctx , poolId ); err != nil {
240+ t .Fatalf ("ClosePool returned unexpected error: %v" , err )
241+ }
242+ }
243+
244+ func TestWriteMutationsInReadOnlyTx (t * testing.T ) {
245+ t .Parallel ()
246+
247+ ctx := context .Background ()
248+ server , teardown := setupMockServer (t )
249+ defer teardown ()
250+ dsn := fmt .Sprintf ("%s/projects/p/instances/i/databases/d?useplaintext=true" , server .Address )
251+
252+ poolId , err := CreatePool (ctx , dsn )
253+ if err != nil {
254+ t .Fatalf ("CreatePool returned unexpected error: %v" , err )
255+ }
256+ connId , err := CreateConnection (ctx , poolId )
257+ if err != nil {
258+ t .Fatalf ("CreateConnection returned unexpected error: %v" , err )
259+ }
260+
261+ // Start a read-only transaction and try to write mutations to that transaction. That should return an error.
262+ if err := BeginTransaction (ctx , poolId , connId , & spannerpb.TransactionOptions {
263+ Mode : & spannerpb.TransactionOptions_ReadOnly_ {ReadOnly : & spannerpb.TransactionOptions_ReadOnly {}},
264+ }); err != nil {
265+ t .Fatalf ("BeginTransaction returned unexpected error: %v" , err )
266+ }
267+
268+ mutations := & spannerpb.BatchWriteRequest_MutationGroup {Mutations : []* spannerpb.Mutation {
269+ {Operation : & spannerpb.Mutation_Insert {Insert : & spannerpb.Mutation_Write {
270+ Table : "my_table" ,
271+ Columns : []string {"id" , "value" },
272+ Values : []* structpb.ListValue {
273+ {Values : []* structpb.Value {structpb .NewStringValue ("1" ), structpb .NewStringValue ("One" )}},
274+ },
275+ }}},
276+ }}
277+ _ , err = WriteMutations (ctx , poolId , connId , mutations )
278+ if g , w := spanner .ErrCode (err ), codes .FailedPrecondition ; g != w {
279+ t .Fatalf ("WriteMutations error code mismatch\n Got: %d\n Want: %d" , g , w )
280+ }
281+
282+ // Committing the read-only transaction should not lead to any commits on Spanner.
283+ _ , err = Commit (ctx , poolId , connId )
284+ if err != nil {
285+ t .Fatalf ("Commit returned unexpected error: %v" , err )
286+ }
287+ requests := server .TestSpanner .DrainRequestsFromServer ()
288+ // There should also not be any BeginTransaction requests on Spanner, as the transaction was never really started
289+ // by a query or other statement.
290+ beginRequests := testutil .RequestsOfType (requests , reflect .TypeOf (& spannerpb.BeginTransactionRequest {}))
291+ if g , w := len (beginRequests ), 0 ; g != w {
292+ t .Fatalf ("num BeginTransaction requests mismatch\n Got: %d\n Want: %d" , g , w )
293+ }
294+ commitRequests := testutil .RequestsOfType (requests , reflect .TypeOf (& spannerpb.CommitRequest {}))
295+ if g , w := len (commitRequests ), 0 ; g != w {
296+ t .Fatalf ("num CommitRequests mismatch\n Got: %d\n Want: %d" , g , w )
297+ }
298+
299+ if err := ClosePool (ctx , poolId ); err != nil {
300+ t .Fatalf ("ClosePool returned unexpected error: %v" , err )
301+ }
302+ }
0 commit comments