spanner/spannertest: implement InsertOrUpdate

Unsurprisingly, this is very similar to Insert and Update.

Updates #1181.

Change-Id: I31c634929ab7a58a426eea875de0b603e0a5f0e7
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/43230
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Knut Olav Løite <koloite@gmail.com>
diff --git a/spanner/spannertest/db.go b/spanner/spannertest/db.go
index ea29247..fdc434e 100644
--- a/spanner/spannertest/db.go
+++ b/spanner/spannertest/db.go
@@ -255,7 +255,27 @@
 	})
 }
 
-// TODO: InsertOrUpdate, Replace
+func (d *database) InsertOrUpdate(tbl string, cols []string, values []*structpb.ListValue) error {
+	return d.writeValues(tbl, cols, values, func(t *table, colIndexes, pkIndexes []int, r row) error {
+		var pk []interface{}
+		for _, i := range pkIndexes {
+			pk = append(pk, r[i])
+		}
+		rowNum := t.rowForPK(pk)
+		if rowNum < 0 {
+			// New row; do an insert.
+			t.rows = append(t.rows, r)
+		} else {
+			// Existing row; do an update.
+			for _, i := range colIndexes {
+				t.rows[rowNum][i] = r[i]
+			}
+		}
+		return nil
+	})
+}
+
+// TODO: Replace
 
 func (d *database) Delete(table string, keys []*structpb.ListValue, all bool) error {
 	t, err := d.table(table)
diff --git a/spanner/spannertest/inmem.go b/spanner/spannertest/inmem.go
index fde8f5a..5da939f 100644
--- a/spanner/spannertest/inmem.go
+++ b/spanner/spannertest/inmem.go
@@ -566,6 +566,12 @@
 			if err != nil {
 				return nil, err
 			}
+		case *spannerpb.Mutation_InsertOrUpdate:
+			iou := op.InsertOrUpdate
+			err := s.db.InsertOrUpdate(iou.Table, iou.Columns, iou.Values)
+			if err != nil {
+				return nil, err
+			}
 		case *spannerpb.Mutation_Delete_:
 			del := op.Delete
 			ks := del.KeySet