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)