@@ -17,6 +17,7 @@ limitations under the License.
1717package spanner
1818
1919import (
20+ "context"
2021 "math/big"
2122 "reflect"
2223 "sort"
@@ -26,6 +27,7 @@ import (
2627
2728 "cloud.google.com/go/civil"
2829 sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
30+ . "cloud.google.com/go/spanner/internal/testutil"
2931 proto3 "google.golang.org/protobuf/types/known/structpb"
3032)
3133
@@ -430,6 +432,139 @@ func TestStructToMutationParams(t *testing.T) {
430432 }
431433}
432434
435+ func TestStructToMutationParams_ReadOnly (t * testing.T ) {
436+ t .Parallel ()
437+ type ReadOnly struct {
438+ ID int64
439+ Name string `spanner:"->"`
440+ }
441+ in := & ReadOnly {ID : 1 , Name : "foo" }
442+ wantCols := []string {"ID" }
443+ wantVals := []interface {}{int64 (1 )}
444+ gotCols , gotVals , err := structToMutationParams (in )
445+ if err != nil {
446+ t .Fatal (err )
447+ }
448+ if ! testEqual (gotCols , wantCols ) {
449+ t .Errorf ("got cols %v, want %v" , gotCols , wantCols )
450+ }
451+ if ! testEqual (gotVals , wantVals ) {
452+ t .Errorf ("got vals %v, want %v" , gotVals , wantVals )
453+ }
454+ }
455+
456+ func TestReadWrite_Generated (t * testing.T ) {
457+ t .Parallel ()
458+ server , client , teardown := setupMockedTestServer (t )
459+ defer teardown ()
460+ // The full name is generated by the server.
461+ server .TestSpanner .PutStatementResult (
462+ "SELECT Id, FirstName, LastName, FullName FROM Users WHERE Id = 1" ,
463+ & StatementResult {
464+ Type : StatementResultResultSet ,
465+ ResultSet : & sppb.ResultSet {
466+ Metadata : & sppb.ResultSetMetadata {
467+ RowType : & sppb.StructType {
468+ Fields : []* sppb.StructType_Field {
469+ {Name : "Id" , Type : & sppb.Type {Code : sppb .TypeCode_INT64 }},
470+ {Name : "FirstName" , Type : & sppb.Type {Code : sppb .TypeCode_STRING }},
471+ {Name : "LastName" , Type : & sppb.Type {Code : sppb .TypeCode_STRING }},
472+ {Name : "FullName" , Type : & sppb.Type {Code : sppb .TypeCode_STRING }},
473+ },
474+ },
475+ },
476+ Rows : []* proto3.ListValue {
477+ {
478+ Values : []* proto3.Value {
479+ intProto (1 ),
480+ stringProto ("First" ),
481+ stringProto ("Last" ),
482+ stringProto ("First Last" ),
483+ },
484+ },
485+ },
486+ },
487+ },
488+ )
489+
490+ type User struct {
491+ ID int64 `spanner:"Id"`
492+ FirstName string
493+ LastName string
494+ FullName string `spanner:"->"`
495+ }
496+ user := & User {
497+ ID : 1 ,
498+ FirstName : "First" ,
499+ LastName : "Last" ,
500+ }
501+ m , err := InsertStruct ("Users" , user )
502+ if err != nil {
503+ t .Fatal (err )
504+ }
505+ _ , err = client .Apply (context .Background (), []* Mutation {m })
506+ if err != nil {
507+ t .Fatal (err )
508+ }
509+ // Verify that the generated column 'FullName' was excluded from the write mutation.
510+ reqs := drainRequestsFromServer (server .TestSpanner )
511+ var commitReq * sppb.CommitRequest
512+ for _ , r := range reqs {
513+ if c , ok := r .(* sppb.CommitRequest ); ok {
514+ commitReq = c
515+ }
516+ }
517+ if commitReq == nil {
518+ t .Fatalf ("no CommitRequest captured; got %v" , reqs )
519+ }
520+ // Find the write mutation for Users.
521+ var write * sppb.Mutation_Write
522+ for _ , mut := range commitReq .Mutations {
523+ if ins := mut .GetInsert (); ins != nil && ins .Table == "Users" {
524+ write = ins
525+ break
526+ }
527+ }
528+ if write == nil {
529+ t .Fatalf ("no write mutation for table Users in CommitRequest: %v" , commitReq .Mutations )
530+ }
531+ // Ensure FullName is not present in columns.
532+ for _ , col := range write .Columns {
533+ if col == "FullName" {
534+ t .Fatalf ("generated column FullName must be excluded from write.Columns: %v" , write .Columns )
535+ }
536+ }
537+ wantCols := []string {"Id" , "FirstName" , "LastName" }
538+ if ! reflect .DeepEqual (write .Columns , wantCols ) {
539+ t .Fatalf ("write.Columns mismatch\n got %v\n want %v" , write .Columns , wantCols )
540+ }
541+ if g , w := len (write .Values ), 1 ; g != w {
542+ t .Fatalf ("write.Values length mismatch: got %d, want 1" , len (write .Values ))
543+ }
544+ if g , w := len (write .Values [0 ].Values ), len (wantCols ); g != w {
545+ t .Fatalf ("write.Values[0] length mismatch\n Got: %v\n Want: %v" , g , w )
546+ }
547+
548+ iter := client .Single ().Query (context .Background (), NewStatement ("SELECT Id, FirstName, LastName, FullName FROM Users WHERE Id = 1" ))
549+ row , err := iter .Next ()
550+ if err != nil {
551+ t .Fatal (err )
552+ }
553+ var got User
554+ if err := row .ToStruct (& got ); err != nil {
555+ t .Fatal (err )
556+ }
557+ want := & User {
558+ ID : 1 ,
559+ FirstName : "First" ,
560+ LastName : "Last" ,
561+ FullName : "First Last" ,
562+ }
563+ if ! testEqual (got , * want ) {
564+ t .Errorf ("got %v, want %v" , got , * want )
565+ }
566+ }
567+
433568// Test encoding Mutation into proto.
434569func TestEncodeMutation (t * testing.T ) {
435570 for _ , test := range []struct {
0 commit comments