spanner/spannertest: support DATE types
This supports them fully for storage and retrieval.
Also add a little bit of internal documentation about value
representation inside this package.
Updates #1181.
Change-Id: Ia2fb3b2ea89f22f60f590934fcf72c722bd8622d
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/43710
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Knut Olav Løite <koloite@gmail.com>
diff --git a/spanner/spannertest/README.md b/spanner/spannertest/README.md
index 5848539..578e9e9 100644
--- a/spanner/spannertest/README.md
+++ b/spanner/spannertest/README.md
@@ -22,7 +22,7 @@
- DML statements
- case insensitivity
- alternate literal types (esp. strings)
-- DATE, TIMESTAMP types
+- TIMESTAMP types
- STRUCT types
- expression functions
- expression type casting, coercion
diff --git a/spanner/spannertest/db.go b/spanner/spannertest/db.go
index 439b8d8..677b2e3 100644
--- a/spanner/spannertest/db.go
+++ b/spanner/spannertest/db.go
@@ -26,6 +26,7 @@
"sort"
"strconv"
"sync"
+ "time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -59,6 +60,20 @@
Type spansql.Type
}
+/*
+row represents a list of data elements.
+
+The mapping between Spanner types and Go types internal to this package are:
+ BOOL bool
+ INT64 int64
+ FLOAT64 float64
+ STRING string
+ BYTES TODO
+ DATE string (RFC 3339 date; "YYYY-MM-DD")
+ TIMESTAMP TODO
+ ARRAY<T> []T
+ STRUCT TODO
+*/
type row []interface{}
func (r row) copyDataElem(index int) interface{} {
@@ -540,7 +555,7 @@
for i := 0; i < len(a); i++ {
// The only value key column types are represented internally
// as Go types comparable with == and !=.
- // TODO: DATE, TIMESTAMP might violate this.
+ // TODO: TIMESTAMP might violate this.
if a[i] != b[i] {
return false
}
@@ -596,6 +611,17 @@
if ok {
return sv.StringValue, nil
}
+ case spansql.Date:
+ // The Spanner protocol encodes DATE in RFC 3339 date format.
+ sv, ok := v.Kind.(*structpb.Value_StringValue)
+ if ok {
+ // Store it internally as a string, but validate its value.
+ s := sv.StringValue
+ if _, err := time.Parse("2006-01-02", s); err != nil {
+ return nil, fmt.Errorf("bad DATE string %q: %v", s, err)
+ }
+ return s, nil
+ }
}
return nil, fmt.Errorf("unsupported inserting value kind %T into column of type %s", v.Kind, t.SQL())
}
diff --git a/spanner/spannertest/db_eval.go b/spanner/spannertest/db_eval.go
index 62af253..a11e104 100644
--- a/spanner/spannertest/db_eval.go
+++ b/spanner/spannertest/db_eval.go
@@ -389,6 +389,7 @@
}
return 0
case string:
+ // This handles DATE too.
return strings.Compare(x, y.(string))
}
}
diff --git a/spanner/spannertest/db_test.go b/spanner/spannertest/db_test.go
index 8579cc0..9510549 100644
--- a/spanner/spannertest/db_test.go
+++ b/spanner/spannertest/db_test.go
@@ -157,6 +157,26 @@
t.Errorf("ReadAll data wrong.\n got %v\nwant %v", all, wantAll)
}
+ // Add a DATE column, populate it with some data.
+ st = db.ApplyDDL(spansql.AlterTable{
+ Name: "Staff",
+ Alteration: spansql.AddColumn{Def: spansql.ColumnDef{
+ Name: "FirstSeen",
+ Type: spansql.Type{Base: spansql.Date},
+ }},
+ })
+ if st.Code() != codes.OK {
+ t.Fatalf("Adding column: %v", st.Err())
+ }
+ err = db.Update("Staff", []string{"Name", "ID", "FirstSeen"}, []*structpb.ListValue{
+ listV(stringV("Jack"), stringV("1"), stringV("1994-10-28")),
+ listV(stringV("Daniel"), stringV("2"), stringV("1994-10-28")),
+ listV(stringV("George"), stringV("5"), stringV("1997-07-27")),
+ })
+ if err != nil {
+ t.Fatalf("Updating rows: %v", err)
+ }
+
// Do some complex queries.
tests := []struct {
q string
@@ -236,6 +256,13 @@
{int64(4)},
},
},
+ {
+ `SELECT Name FROM Staff WHERE FirstSeen >= @min`,
+ queryParams{"min": "1996-01-01"},
+ [][]interface{}{
+ {"George"},
+ },
+ },
}
for _, test := range tests {
q, err := spansql.ParseQuery(test.q)