Add methods for test spanner db

Change-Id: I53ebab5697a64612fd7098b777202c3d7b5f41a6
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/984676
Auto-Submit: Prakhar Asthana <pasthana@google.com>
Commit-Queue: Wenbin Zhang <wenbinzhang@google.com>
Reviewed-by: Wenbin Zhang <wenbinzhang@google.com>
diff --git a/golden/go/sql/sqltest/BUILD.bazel b/golden/go/sql/sqltest/BUILD.bazel
index 92bab84..0b94b7c 100644
--- a/golden/go/sql/sqltest/BUILD.bazel
+++ b/golden/go/sql/sqltest/BUILD.bazel
@@ -10,10 +10,13 @@
         "//bazel/external/cockroachdb",
         "//go/emulators",
         "//go/emulators/cockroachdb_instance",
+        "//go/emulators/gcp_emulator",
+        "//go/emulators/pgadapter",
         "//go/skerr",
         "//go/sql/sqlutil",
         "//go/util",
         "//golden/go/sql/schema",
+        "//golden/go/sql/schema/spanner",
         "@com_github_jackc_pgx_v4//pgxpool",
         "@com_github_stretchr_testify//require",
     ],
diff --git a/golden/go/sql/sqltest/sqltest.go b/golden/go/sql/sqltest/sqltest.go
index 0d8a6ad..10c231d 100644
--- a/golden/go/sql/sqltest/sqltest.go
+++ b/golden/go/sql/sqltest/sqltest.go
@@ -18,10 +18,13 @@
 	"go.skia.org/infra/bazel/external/cockroachdb"
 	"go.skia.org/infra/go/emulators"
 	"go.skia.org/infra/go/emulators/cockroachdb_instance"
+	"go.skia.org/infra/go/emulators/gcp_emulator"
+	"go.skia.org/infra/go/emulators/pgadapter"
 	"go.skia.org/infra/go/skerr"
 	"go.skia.org/infra/go/sql/sqlutil"
 	"go.skia.org/infra/go/util"
 	"go.skia.org/infra/golden/go/sql/schema"
+	"go.skia.org/infra/golden/go/sql/schema/spanner"
 )
 
 // NewCockroachDBForTests creates a randomly named database on a test CockroachDB instance (aka the
@@ -70,6 +73,35 @@
 	return db
 }
 
+func NewSpannerDBForTests(ctx context.Context, t testing.TB) *pgxpool.Pool {
+	// Ensure that both spanner emulator and pgadapter are running first.
+	gcp_emulator.RequireSpanner(t)
+	pgadapter.Require(t)
+
+	n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
+	require.NoError(t, err)
+	databaseName := "for_tests" + n.String()
+
+	if len(databaseName) > 30 {
+		databaseName = databaseName[:30]
+	}
+	host := emulators.GetEmulatorHostEnvVar(emulators.PGAdapter)
+	connectionString := fmt.Sprintf("postgresql://root@%s/%s?sslmode=disable", host, databaseName)
+
+	conn, err := pgxpool.Connect(ctx, connectionString)
+	require.NoError(t, err)
+	return conn
+}
+
+// NewSpannerDBForTestsWithProductionSchema returns a SQL database with the production
+// schema. It will be aimed at a randomly named database.
+func NewSpannerDBForTestsWithProductionSchema(ctx context.Context, t testing.TB) *pgxpool.Pool {
+	db := NewSpannerDBForTests(ctx, t)
+	_, err := db.Exec(ctx, spanner.Schema)
+	require.NoError(t, err)
+	return db
+}
+
 // SQLExporter is an abstraction around a type that can be written as a single row in a SQL table.
 type SQLExporter interface {
 	// ToSQLRow returns the column names and the column data that should be written for this row.
@@ -158,6 +190,11 @@
 	return skerr.Wrapf(err, "Inserting %d rows into table %s", numRows, name)
 }
 
+// ----------------------- WARNING ------------------------
+//
+//	INCOMPATIBLE WITH SPANNER EMULATOR
+//
+// --------------------------------------------------------
 // GetAllRows returns all rows for a given table. The passed in row param must be a pointer type
 // that implements the sqltest.Scanner interface. The passed in row may optionally implement the
 // sqltest.RowsOrder interface to specify an ordering to return the rows in (this can make for
@@ -217,6 +254,7 @@
 //     prior to the update, and that the slice with new rows contains the affected row after the
 //     update.
 func GetRowChanges[T any](ctx context.Context, t *testing.T, db *pgxpool.Pool, table string, t0 time.Time) (missingRows, newRows []T) {
+
 	getRows := func(statement string) []T {
 		rows, err := db.Query(ctx, statement)
 		require.NoError(t, err)
diff --git a/golden/go/sql/sqltest/sqltest_test.go b/golden/go/sql/sqltest/sqltest_test.go
index 24342bc..6635568 100644
--- a/golden/go/sql/sqltest/sqltest_test.go
+++ b/golden/go/sql/sqltest/sqltest_test.go
@@ -14,11 +14,11 @@
 	"go.skia.org/infra/golden/go/sql/sqltest"
 )
 
-func TestBulkInsertDataTables_ValidData_Success(t *testing.T) {
+func TestBulkInsertDataTables_ValidData_Success_cdb(t *testing.T) {
 
 	ctx := context.Background()
 	db := sqltest.NewCockroachDBForTests(ctx, t)
-	_, err := db.Exec(ctx, testSchema)
+	_, err := db.Exec(ctx, testSchema_cdb)
 	require.NoError(t, err)
 
 	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
@@ -74,11 +74,11 @@
 	}}, actualRows2)
 }
 
-func TestBulkInsertDataTables_InvalidForeignKeys_ReturnsError(t *testing.T) {
+func TestBulkInsertDataTables_InvalidForeignKeys_ReturnsError_cdb(t *testing.T) {
 
 	ctx := context.Background()
 	db := sqltest.NewCockroachDBForTests(ctx, t)
-	_, err := db.Exec(ctx, testSchema)
+	_, err := db.Exec(ctx, testSchema_cdb)
 	require.NoError(t, err)
 
 	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
@@ -92,11 +92,11 @@
 	require.Error(t, err)
 }
 
-func TestGetAllRows_RowsOrderByDefined_ReturnsInOrder(t *testing.T) {
+func TestGetAllRows_RowsOrderByDefined_ReturnsInOrder_cdb(t *testing.T) {
 
 	ctx := context.Background()
 	db := sqltest.NewCockroachDBForTests(ctx, t)
-	_, err := db.Exec(ctx, testSchema)
+	_, err := db.Exec(ctx, testSchema_cdb)
 	require.NoError(t, err)
 
 	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
@@ -118,11 +118,11 @@
 	}, actualRows)
 }
 
-func TestGetAllRows_RowsOrderNotDefined_ReturnsInAnyOrder(t *testing.T) {
+func TestGetAllRows_RowsOrderNotDefined_ReturnsInAnyOrder_cdb(t *testing.T) {
 
 	ctx := context.Background()
 	db := sqltest.NewCockroachDBForTests(ctx, t)
-	_, err := db.Exec(ctx, testSchema)
+	_, err := db.Exec(ctx, testSchema_cdb)
 	require.NoError(t, err)
 
 	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
@@ -142,11 +142,11 @@
 	}, actualRows)
 }
 
-func TestGetRowChanges_RowsOrderDefined_ReturnsInOrder(t *testing.T) {
+func TestGetRowChanges_RowsOrderDefined_ReturnsInOrder_cdb(t *testing.T) {
 
 	ctx := context.Background()
 	db := sqltest.NewCockroachDBForTests(ctx, t)
-	_, err := db.Exec(ctx, testSchema)
+	_, err := db.Exec(ctx, testSchema_cdb)
 	require.NoError(t, err)
 
 	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
@@ -177,11 +177,11 @@
 	}, newRows)
 }
 
-func TestGetRowChanges_RowsOrderNotDefined_ReturnsInAnyOrder(t *testing.T) {
+func TestGetRowChanges_RowsOrderNotDefined_ReturnsInAnyOrder_cdb(t *testing.T) {
 
 	ctx := context.Background()
 	db := sqltest.NewCockroachDBForTests(ctx, t)
-	_, err := db.Exec(ctx, testSchema)
+	_, err := db.Exec(ctx, testSchema_cdb)
 	require.NoError(t, err)
 
 	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
@@ -212,6 +212,134 @@
 	}, newRows)
 }
 
+func TestBulkInsertDataTables_ValidData_Success(t *testing.T) {
+
+	ctx := context.Background()
+	db := sqltest.NewSpannerDBForTests(ctx, t)
+	_, err := db.Exec(ctx, testSchema)
+	require.NoError(t, err)
+
+	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
+		TableOne: []tableOneRow{
+			{ColumnOne: "apple", ColumnTwo: "banana"},
+			{ColumnOne: "cherry", ColumnTwo: "durian"},
+			{ColumnOne: "elderberry", ColumnTwo: "fig"},
+		},
+		TableTwo: []tableTwoRow{
+			// The ComputedColumn is intentionally omitted here to make sure the appropriate
+			// value is inserted at the SQL level.
+			{SpecialColumnOne: []byte("arugula"), ForeignColumnTwo: "apple"},
+			{SpecialColumnOne: []byte("beet"), ForeignColumnTwo: "elderberry"},
+		},
+	})
+	require.NoError(t, err)
+
+	// Spotcheck data
+	rows, err := db.Query(ctx, `SELECT * FROM TableOne`)
+	require.NoError(t, err)
+	defer rows.Close()
+	var actualRows []tableOneRow
+	for rows.Next() {
+		var r tableOneRow
+		assert.NoError(t, rows.Scan(&r.ColumnOne, &r.ColumnTwo))
+		actualRows = append(actualRows, r)
+	}
+	assert.ElementsMatch(t, []tableOneRow{{
+		ColumnOne: "apple", ColumnTwo: "banana",
+	}, {
+		ColumnOne: "cherry", ColumnTwo: "durian",
+	}, {
+		ColumnOne: "elderberry", ColumnTwo: "fig",
+	}}, actualRows)
+
+	rows, err = db.Query(ctx, `SELECT * FROM TableTwo`)
+	require.NoError(t, err)
+	defer rows.Close()
+	var actualRows2 []tableTwoRow
+	for rows.Next() {
+		var r tableTwoRow
+		assert.NoError(t, rows.Scan(&r.SpecialColumnOne, &r.ForeignColumnTwo, &r.ComputedColumnThree))
+		actualRows2 = append(actualRows2, r)
+	}
+	assert.ElementsMatch(t, []tableTwoRow{{
+		SpecialColumnOne:    []byte("arugula"),
+		ForeignColumnTwo:    "apple",
+		ComputedColumnThree: 5,
+	}, {
+		SpecialColumnOne:    []byte("beet"),
+		ForeignColumnTwo:    "elderberry",
+		ComputedColumnThree: 10,
+	}}, actualRows2)
+}
+
+func TestBulkInsertDataTables_InvalidForeignKeys_ReturnsError(t *testing.T) {
+
+	ctx := context.Background()
+	db := sqltest.NewSpannerDBForTests(ctx, t)
+	_, err := db.Exec(ctx, testSchema)
+	require.NoError(t, err)
+
+	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
+		TableOne: []tableOneRow{
+			{ColumnOne: "apple", ColumnTwo: "banana"},
+		},
+		TableTwo: []tableTwoRow{
+			{SpecialColumnOne: []byte("beet"), ForeignColumnTwo: "elderberry"},
+		},
+	})
+	require.Error(t, err)
+}
+
+func TestGetAllRows_RowsOrderByDefined_ReturnsInOrder(t *testing.T) {
+
+	ctx := context.Background()
+	db := sqltest.NewSpannerDBForTests(ctx, t)
+	_, err := db.Exec(ctx, testSchema)
+	require.NoError(t, err)
+
+	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
+		TableThree: []tableThreeRow{
+			{ColumnOne: "apricots", ColumnBool: schema.NBNull, ColumnTS: ts("2021-02-01T00:00:00Z")},
+			{ColumnOne: "chorizo", ColumnBool: schema.NBFalse, ColumnTS: ts("2021-04-01T00:00:00Z")},
+			{ColumnOne: "extra cheese", ColumnBool: schema.NBTrue, ColumnTS: ts("2021-03-01T00:00:00Z")},
+		},
+	})
+	require.NoError(t, err)
+
+	actualRows := sqltest.GetAllRows(ctx, t, db, "TableThree", &tableThreeRow{}).([]tableThreeRow)
+	// The order matters because tableThreeRow has RowsOrderBy defined, which should return
+	// the rows in descending order by column.
+	assert.Equal(t, []tableThreeRow{
+		{ColumnOne: "chorizo", ColumnBool: schema.NBFalse, ColumnTS: ts("2021-04-01T00:00:00Z")},
+		{ColumnOne: "extra cheese", ColumnBool: schema.NBTrue, ColumnTS: ts("2021-03-01T00:00:00Z")},
+		{ColumnOne: "apricots", ColumnBool: schema.NBNull, ColumnTS: ts("2021-02-01T00:00:00Z")},
+	}, actualRows)
+}
+
+func TestGetAllRows_RowsOrderNotDefined_ReturnsInAnyOrder(t *testing.T) {
+
+	ctx := context.Background()
+	db := sqltest.NewSpannerDBForTests(ctx, t)
+	_, err := db.Exec(ctx, testSchema)
+	require.NoError(t, err)
+
+	err = sqltest.BulkInsertDataTables(ctx, db, testTables{
+		TableOne: []tableOneRow{
+			{ColumnOne: "apple", ColumnTwo: "banana"},
+			{ColumnOne: "cherry", ColumnTwo: "durian"},
+			{ColumnOne: "elderberry", ColumnTwo: "fig"},
+		},
+	})
+	require.NoError(t, err)
+
+	actualRows := sqltest.GetAllRows(ctx, t, db, "TableOne", &tableOneRow{}).([]tableOneRow)
+	assert.ElementsMatch(t, []tableOneRow{
+		{ColumnOne: "apple", ColumnTwo: "banana"},
+		{ColumnOne: "cherry", ColumnTwo: "durian"},
+		{ColumnOne: "elderberry", ColumnTwo: "fig"},
+	}, actualRows)
+}
+
 func ts(s string) time.Time {
 	ct, err := time.Parse(time.RFC3339, s)
 	if err != nil {
@@ -220,14 +348,14 @@
 	return ct.UTC()
 }
 
-const testSchema = `CREATE TABLE IF NOT EXISTS TableOne (
+const testSchema_cdb = `CREATE TABLE IF NOT EXISTS TableOne (
   column_one STRING PRIMARY KEY,
   column_two STRING NOT NULL
 );
 CREATE TABLE IF NOT EXISTS TableTwo (
   special_column_one BYTES PRIMARY KEY,
   foreign_column_two STRING REFERENCES TableOne (column_one),
-  computed_column_three INT8 AS (char_length(foreign_column_two)) STORED NOT NULL
+  computed_column_three INT8 NOT NULL
 );
 CREATE TABLE IF NOT EXISTS TableThree (
   column_one STRING PRIMARY KEY,
@@ -236,6 +364,25 @@
 );
 `
 
+const testSchema = `CREATE TABLE TableOne (
+  column_one TEXT PRIMARY KEY,
+  column_two TEXT NOT NULL
+);
+
+CREATE TABLE TableTwo (
+  special_column_one BYTEA PRIMARY KEY,
+  foreign_column_two TEXT,
+  computed_column_three INT8 NOT NULL,
+  CONSTRAINT FK_TableTwo_TableOne FOREIGN KEY (foreign_column_two) REFERENCES TableOne (column_one)
+);
+
+CREATE TABLE TableThree (
+  column_one TEXT PRIMARY KEY,
+  column_bool BOOL,
+  column_ts TIMESTAMP WITH TIME ZONE NOT NULL
+);
+  `
+
 type testTables struct {
 	TableOne   []tableOneRow
 	TableTwo   []tableTwoRow
@@ -268,8 +415,9 @@
 }
 
 func (r tableTwoRow) ToSQLRow() (colNames []string, colData []interface{}) {
-	return []string{"special_column_one", "foreign_column_two"}, []interface{}{
-		r.SpecialColumnOne, r.ForeignColumnTwo,
+	computedLength := len(r.ForeignColumnTwo)
+	return []string{"special_column_one", "foreign_column_two", "computed_column_three"}, []interface{}{
+		r.SpecialColumnOne, r.ForeignColumnTwo, computedLength,
 	}
 }