spanner: more tests, part 1

Add tests to fulfill the Cloud Spanner client test plan.

These tests are translated from the first half of
https://github.com/GoogleCloudPlatform/google-cloud-java/blob/master/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITReadTest.java.

Change-Id: Iaef1133cf5e1036e6d9ec074596f52e47b0d82f9
Reviewed-on: https://code-review.googlesource.com/12978
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Vikas Kedia <vikask@google.com>
diff --git a/spanner/spanner_test.go b/spanner/spanner_test.go
index e5070df..15cde9a 100644
--- a/spanner/spanner_test.go
+++ b/spanner/spanner_test.go
@@ -31,6 +31,7 @@
 	"golang.org/x/net/context"
 	"google.golang.org/api/iterator"
 	"google.golang.org/api/option"
+	"google.golang.org/grpc/codes"
 
 	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
 )
@@ -52,8 +53,54 @@
 	dbName string
 )
 
+var (
+	singerDBStatements = []string{
+		`CREATE TABLE Singers (
+				SingerId	INT64 NOT NULL,
+				FirstName	STRING(1024),
+				LastName	STRING(1024),
+				SingerInfo	BYTES(MAX)
+			) PRIMARY KEY (SingerId)`,
+		`CREATE INDEX SingerByName ON Singers(FirstName, LastName)`,
+		`CREATE TABLE Accounts (
+				AccountId	INT64 NOT NULL,
+				Nickname	STRING(100),
+				Balance		INT64 NOT NULL,
+			) PRIMARY KEY (AccountId)`,
+		`CREATE INDEX AccountByNickname ON Accounts(Nickname) STORING (Balance)`,
+		`CREATE TABLE Types (
+				RowID		INT64 NOT NULL,
+				String		STRING(MAX),
+				StringArray	ARRAY<STRING(MAX)>,
+				Bytes		BYTES(MAX),
+				BytesArray	ARRAY<BYTES(MAX)>,
+				Int64a		INT64,
+				Int64Array	ARRAY<INT64>,
+				Bool		BOOL,
+				BoolArray	ARRAY<BOOL>,
+				Float64		FLOAT64,
+				Float64Array	ARRAY<FLOAT64>,
+				Date		DATE,
+				DateArray	ARRAY<DATE>,
+				Timestamp	TIMESTAMP,
+				TimestampArray	ARRAY<TIMESTAMP>,
+			) PRIMARY KEY (RowID)`,
+	}
+
+	readDBStatements = []string{
+		`CREATE TABLE TestTable (
+                    Key          STRING(MAX) NOT NULL,
+                    StringValue  STRING(MAX)
+            ) PRIMARY KEY (Key)`,
+		`CREATE INDEX TestTableByValue ON TestTable(StringValue)`,
+		`CREATE INDEX TestTableByValueDesc ON TestTable(StringValue DESC)`,
+	}
+)
+
+type testTableRow struct{ Key, StringValue string }
+
 // prepare initializes Cloud Spanner testing DB and clients.
-func prepare(ctx context.Context, t *testing.T) error {
+func prepare(ctx context.Context, t *testing.T, statements []string) error {
 	if testing.Short() {
 		t.Skip("Integration tests skipped in short mode")
 	}
@@ -79,38 +126,7 @@
 	op, err := admin.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{
 		Parent:          fmt.Sprintf("projects/%v/instances/%v", testProjectID, testInstanceID),
 		CreateStatement: "CREATE DATABASE " + dbName,
-		ExtraStatements: []string{
-			`CREATE TABLE Singers (
-				SingerId	INT64 NOT NULL,
-				FirstName	STRING(1024),
-				LastName	STRING(1024),
-				SingerInfo	BYTES(MAX)
-			) PRIMARY KEY (SingerId)`,
-			`CREATE INDEX SingerByName ON Singers(FirstName, LastName)`,
-			`CREATE TABLE Accounts (
-				AccountId	INT64 NOT NULL,
-				Nickname	STRING(100),
-				Balance		INT64 NOT NULL,
-			) PRIMARY KEY (AccountId)`,
-			`CREATE INDEX AccountByNickname ON Accounts(Nickname) STORING (Balance)`,
-			`CREATE TABLE Types (
-				RowID		INT64 NOT NULL,
-				String		STRING(MAX),
-				StringArray	ARRAY<STRING(MAX)>,
-				Bytes		BYTES(MAX),
-				BytesArray	ARRAY<BYTES(MAX)>,
-				Int64a		INT64,
-				Int64Array	ARRAY<INT64>,
-				Bool		BOOL,
-				BoolArray	ARRAY<BOOL>,
-				Float64		FLOAT64,
-				Float64Array	ARRAY<FLOAT64>,
-				Date		DATE,
-				DateArray	ARRAY<DATE>,
-				Timestamp	TIMESTAMP,
-				TimestampArray	ARRAY<TIMESTAMP>,
-			) PRIMARY KEY (RowID)`,
-		},
+		ExtraStatements: statements,
 	})
 	if err != nil {
 		t.Errorf("cannot create testing DB %v: %v", db, err)
@@ -153,7 +169,7 @@
 	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
 	defer cancel()
 	// Set up testing environment.
-	if err := prepare(ctx, t); err != nil {
+	if err := prepare(ctx, t, singerDBStatements); err != nil {
 		// If prepare() fails, tear down whatever that's already up.
 		tearDown(ctx, t)
 		t.Fatalf("cannot set up testing environment: %v", err)
@@ -355,7 +371,7 @@
 	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
 	defer cancel()
 	// Set up testing environment.
-	if err := prepare(ctx, t); err != nil {
+	if err := prepare(ctx, t, singerDBStatements); err != nil {
 		// If prepare() fails, tear down whatever that's already up.
 		tearDown(ctx, t)
 		t.Fatalf("cannot set up testing environment: %v", err)
@@ -543,7 +559,7 @@
 	// Give a longer deadline because of transaction backoffs.
 	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
 	defer cancel()
-	if err := prepare(ctx, t); err != nil {
+	if err := prepare(ctx, t, singerDBStatements); err != nil {
 		tearDown(ctx, t)
 		t.Fatalf("cannot set up testing environment: %v", err)
 	}
@@ -632,11 +648,182 @@
 	}
 }
 
+const (
+	testTable      = "TestTable"
+	testTableIndex = "TestTableByValue"
+)
+
+var testTableColumns = []string{"Key", "StringValue"}
+
+func TestReads(t *testing.T) {
+	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
+	defer cancel()
+	// Set up testing environment.
+	if err := prepare(ctx, t, readDBStatements); err != nil {
+		// If prepare() fails, tear down whatever that's already up.
+		tearDown(ctx, t)
+		t.Fatalf("cannot set up testing environment: %v", err)
+	}
+	// After all tests, tear down testing environment.
+	defer tearDown(ctx, t)
+
+	// Includes k0..k14. Strings sort lexically, eg "k1" < "k10" < "k2".
+	var ms []*Mutation
+	for i := 0; i < 15; i++ {
+		ms = append(ms, InsertOrUpdate(testTable,
+			testTableColumns,
+			[]interface{}{fmt.Sprintf("k%d", i), fmt.Sprintf("v%d", i)}))
+	}
+	if _, err := client.Apply(ctx, ms, ApplyAtLeastOnce()); err != nil {
+		t.Fatal(err)
+	}
+
+	// Empty read.
+	rows, err := readAllTestTable(client.Single().Read(ctx, testTable,
+		Range(KeyRange{Start: Key{"k99"}, End: Key{"z"}}), testTableColumns))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := len(rows), 0; got != want {
+		t.Errorf("got %d, want %d", got, want)
+	}
+
+	// Index empty read.
+	rows, err = readAllTestTable(client.Single().ReadUsingIndex(ctx, testTable, testTableIndex,
+		Range(KeyRange{Start: Key{"v99"}, End: Key{"z"}}), testTableColumns))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := len(rows), 0; got != want {
+		t.Errorf("got %d, want %d", got, want)
+	}
+
+	// Point read.
+	row, err := client.Single().ReadRow(ctx, testTable, Key{"k1"}, testTableColumns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var got testTableRow
+	if err := row.ToStruct(&got); err != nil {
+		t.Fatal(err)
+	}
+	if want := (testTableRow{"k1", "v1"}); got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	// Point read not found.
+	_, err = client.Single().ReadRow(ctx, testTable, Key{"k999"}, testTableColumns)
+	if ErrCode(err) != codes.NotFound {
+		t.Fatalf("got %v, want NotFound", err)
+	}
+
+	// No index point read not found, because Go does not have ReadRowUsingIndex.
+
+	rangeReads(ctx, t)
+	indexRangeReads(ctx, t)
+}
+
+func rangeReads(ctx context.Context, t *testing.T) {
+	checkRange := func(ks KeySet, wantNums ...int) {
+		if msg, ok := compareRows(client.Single().Read(ctx, testTable, ks, testTableColumns), wantNums); !ok {
+			t.Errorf("key set %+v: %s", ks, msg)
+		}
+	}
+
+	checkRange(Keys(Key{"k1"}), 1)
+	checkRange(keyRange(ClosedOpen, Key{"k3"}, Key{"k5"}), 3, 4)
+	checkRange(keyRange(ClosedClosed, Key{"k3"}, Key{"k5"}), 3, 4, 5)
+	checkRange(keyRange(OpenClosed, Key{"k3"}, Key{"k5"}), 4, 5)
+	checkRange(keyRange(OpenOpen, Key{"k3"}, Key{"k5"}), 4)
+
+	// Partial key specification.
+	checkRange(keyRange(ClosedClosed, Key{"k7"}, Key{}), 7, 8, 9)
+	checkRange(keyRange(OpenClosed, Key{"k7"}, Key{}), 8, 9)
+	checkRange(keyRange(ClosedOpen, Key{}, Key{"k11"}), 0, 1, 10)
+	checkRange(keyRange(ClosedClosed, Key{}, Key{"k11"}), 0, 1, 10, 11)
+
+	// The following produce empty ranges.
+	// TODO(jba): Consider a multi-part key to illustrate partial key behavior.
+	// checkRange(keyRange(ClosedOpen, Key{"k7"}, Key{}))
+	// checkRange(keyRange(OpenOpen, Key{"k7"}, Key{}))
+	// checkRange(keyRange(OpenOpen, Key{}, Key{"k11"}))
+	// checkRange(keyRange(OpenClosed, Key{}, Key{"k11"}))
+
+	// Prefix is component-wise, not string prefix.
+	checkRange(PrefixRange(Key{"k1"}), 1)
+	checkRange(keyRange(ClosedOpen, Key{"k1"}, Key{"k2"}), 1, 10, 11, 12, 13, 14)
+
+	checkRange(AllKeys(), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
+}
+
+func indexRangeReads(ctx context.Context, t *testing.T) {
+	checkRange := func(ks KeySet, wantNums ...int) {
+		if msg, ok := compareRows(client.Single().ReadUsingIndex(ctx, testTable, testTableIndex, ks, testTableColumns),
+			wantNums); !ok {
+			t.Errorf("key set %+v: %s", ks, msg)
+		}
+	}
+
+	checkRange(Keys(Key{"v1"}), 1)
+	checkRange(keyRange(ClosedOpen, Key{"v3"}, Key{"v5"}), 3, 4)
+	checkRange(keyRange(ClosedClosed, Key{"v3"}, Key{"v5"}), 3, 4, 5)
+	checkRange(keyRange(OpenClosed, Key{"v3"}, Key{"v5"}), 4, 5)
+	checkRange(keyRange(OpenOpen, Key{"v3"}, Key{"v5"}), 4)
+
+	// // Partial key specification.
+	checkRange(keyRange(ClosedClosed, Key{"v7"}, Key{}), 7, 8, 9)
+	checkRange(keyRange(OpenClosed, Key{"v7"}, Key{}), 8, 9)
+	checkRange(keyRange(ClosedOpen, Key{}, Key{"v11"}), 0, 1, 10)
+	checkRange(keyRange(ClosedClosed, Key{}, Key{"v11"}), 0, 1, 10, 11)
+
+	// // The following produce empty ranges.
+	// TODO(jba): uncomment when the spanner Internal error is fixed.
+	// checkRange(keyRange(ClosedOpen, Key{"v7"}, Key{}))
+	// checkRange(keyRange(OpenOpen, Key{"v7"}, Key{}))
+	// checkRange(keyRange(OpenOpen, Key{}, Key{"v11"}))
+	// checkRange(keyRange(OpenClosed, Key{}, Key{"v11"}))
+
+	// // Prefix is component-wise, not string prefix.
+	checkRange(PrefixRange(Key{"v1"}), 1)
+	checkRange(keyRange(ClosedOpen, Key{"v1"}, Key{"v2"}), 1, 10, 11, 12, 13, 14)
+	checkRange(AllKeys(), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
+
+	// Read from an index with DESC ordering.
+	wantNums := []int{14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
+	if msg, ok := compareRows(client.Single().ReadUsingIndex(ctx, testTable, "TestTableByValueDesc", AllKeys(), testTableColumns),
+		wantNums); !ok {
+		t.Errorf("desc: %s", msg)
+	}
+}
+
+func compareRows(iter *RowIterator, wantNums []int) (string, bool) {
+	rows, err := readAllTestTable(iter)
+	if err != nil {
+		return err.Error(), false
+	}
+	want := map[string]string{}
+	for _, n := range wantNums {
+		want[fmt.Sprintf("k%d", n)] = fmt.Sprintf("v%d", n)
+	}
+	got := map[string]string{}
+	for _, r := range rows {
+		got[r.Key] = r.StringValue
+	}
+	if !reflect.DeepEqual(got, want) {
+		return fmt.Sprintf("got %v, want %v", got, want), false
+	}
+	return "", true
+}
+
+func keyRange(kind KeyRangeKind, start, end Key) KeySet {
+	return Range(KeyRange{Start: start, End: end, Kind: kind})
+}
+
 // Test client recovery on database recreation.
 func TestDbRemovalRecovery(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
 	defer cancel()
-	if err := prepare(ctx, t); err != nil {
+	if err := prepare(ctx, t, singerDBStatements); err != nil {
 		tearDown(ctx, t)
 		t.Fatalf("cannot set up testing environment: %v", err)
 	}
@@ -650,8 +837,7 @@
 	// Now, send the query.
 	iter := client.Single().Query(ctx, Statement{SQL: "SELECT SingerId FROM Singers"})
 	defer iter.Stop()
-	_, err := iter.Next()
-	if err == nil {
+	if _, err := iter.Next(); err == nil {
 		t.Errorf("client sends query to removed database successfully, want it to fail")
 	}
 
@@ -685,7 +871,7 @@
 func TestBasicTypes(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 	defer cancel()
-	if err := prepare(ctx, t); err != nil {
+	if err := prepare(ctx, t, singerDBStatements); err != nil {
 		tearDown(ctx, t)
 		t.Fatalf("cannot set up testing environment: %v", err)
 	}
@@ -835,7 +1021,7 @@
 func TestStructTypes(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
 	defer cancel()
-	if err := prepare(ctx, t); err != nil {
+	if err := prepare(ctx, t, singerDBStatements); err != nil {
 		tearDown(ctx, t)
 		t.Fatalf("cannot set up testing environment: %v", err)
 	}
@@ -953,3 +1139,22 @@
 		vals = append(vals, v)
 	}
 }
+
+func readAllTestTable(iter *RowIterator) ([]testTableRow, error) {
+	defer iter.Stop()
+	var vals []testTableRow
+	for {
+		row, err := iter.Next()
+		if err == iterator.Done {
+			return vals, nil
+		}
+		if err != nil {
+			return nil, err
+		}
+		var ttr testTableRow
+		if err := row.ToStruct(&ttr); err != nil {
+			return nil, err
+		}
+		vals = append(vals, ttr)
+	}
+}