Skip to content

Commit

Permalink
spanner: ReadOnlyTransaction().ReadRowUsingIndex
Browse files Browse the repository at this point in the history
Add ReadRowUsingIndex() method that returns a single row from the
database by index

Fixes #1035

Change-Id: If6c40e56ce19a79c9bd59723367b6892ae278f4f
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/48091
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Hengfeng Li <hengfeng@google.com>
  • Loading branch information
AlisskaPie authored and olavloite committed Jan 19, 2020
1 parent 4c1e347 commit 4f99193
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 2 deletions.
85 changes: 83 additions & 2 deletions spanner/integration_test.go
Expand Up @@ -451,6 +451,30 @@ func TestIntegration_SingleUse(t *testing.T) {
if err := test.checkTs(rts); err != nil {
t.Fatalf("%d: SingleUse.ReadUsingIndex doesn't return expected timestamp: %v", i, err)
}
// SingleUse.ReadRowUsingIndex
got = nil
for _, k := range []Key{{"Marc", "Foo"}, {"Alpha", "Beta"}, {"Last", "End"}} {
su = client.Single().WithTimestampBound(test.tb)
r, err := su.ReadRowUsingIndex(ctx, "Singers", "SingerByName", k, []string{"SingerId", "FirstName", "LastName"})
if err != nil {
continue
}
v, err := rowToValues(r)
if err != nil {
continue
}
got = append(got, v)
rts, err = su.Timestamp()
if err != nil {
t.Fatalf("%d: SingleUse.ReadRowUsingIndex(%v) doesn't return a timestamp, error: %v", i, k, err)
}
if err := test.checkTs(rts); err != nil {
t.Fatalf("%d: SingleUse.ReadRowUsingIndex(%v) doesn't return expected timestamp: %v", i, k, err)
}
}
if !testEqual(got, test.want) {
t.Fatalf("%d: got unexpected results from SingleUse.ReadRowUsingIndex: %v, want %v", i, got, test.want)
}
})
}
}
Expand Down Expand Up @@ -723,6 +747,32 @@ func TestIntegration_ReadOnlyTransaction(t *testing.T) {
if roTs != rts {
t.Errorf("%d: got two read timestamps: %v, %v, want ReadOnlyTransaction to return always the same read timestamp", i, roTs, rts)
}
// ReadOnlyTransaction.ReadRowUsingIndex
got = nil
for _, k := range []Key{{"Marc", "Foo"}, {"Alpha", "Beta"}, {"Last", "End"}} {
r, err := ro.ReadRowUsingIndex(ctx, "Singers", "SingerByName", k, []string{"SingerId", "FirstName", "LastName"})
if err != nil {
continue
}
v, err := rowToValues(r)
if err != nil {
continue
}
got = append(got, v)
rts, err = ro.Timestamp()
if err != nil {
t.Errorf("%d: ReadOnlyTransaction.ReadRowUsingIndex(%v) doesn't return a timestamp, error: %v", i, k, err)
}
if err := test.checkTs(rts); err != nil {
t.Errorf("%d: ReadOnlyTransaction.ReadRowUsingIndex(%v) doesn't return expected timestamp: %v", i, k, err)
}
if roTs != rts {
t.Errorf("%d: got two read timestamps: %v, %v, want ReadOnlyTransaction to return always the same read timestamp", i, roTs, rts)
}
}
if !testEqual(got, test.want) {
t.Errorf("%d: got unexpected results from ReadOnlyTransaction.ReadRowUsingIndex: %v, want %v", i, got, test.want)
}
ro.Close()
}
}
Expand Down Expand Up @@ -912,8 +962,23 @@ func TestIntegration_Reads(t *testing.T) {
t.Fatalf("got %v, want NotFound", err)
}

// No index point read not found, because Go does not have ReadRowUsingIndex.

// Index point read.
rowIndex, err := client.Single().ReadRowUsingIndex(ctx, testTable, testTableIndex, Key{"v1"}, testTableColumns)
if err != nil {
t.Fatal(err)
}
var gotIndex testTableRow
if err := rowIndex.ToStruct(&gotIndex); err != nil {
t.Fatal(err)
}
if wantIndex := (testTableRow{"k1", "v1"}); gotIndex != wantIndex {
t.Errorf("got %v, want %v", gotIndex, wantIndex)
}
// Index point read not found.
_, err = client.Single().ReadRowUsingIndex(ctx, testTable, testTableIndex, Key{"v999"}, testTableColumns)
if ErrCode(err) != codes.NotFound {
t.Fatalf("got %v, want NotFound", err)
}
rangeReads(ctx, t, client)
indexRangeReads(ctx, t, client)
}
Expand Down Expand Up @@ -1460,6 +1525,16 @@ func TestIntegration_ReadErrors(t *testing.T) {
client, _, cleanup := prepareIntegrationTest(ctx, t, DefaultSessionPoolConfig, readDBStatements)
defer cleanup()

var ms []*Mutation
for i := 0; i < 2; i++ {
ms = append(ms, InsertOrUpdate(testTable,
testTableColumns,
[]interface{}{fmt.Sprintf("k%d", i), fmt.Sprintf("v")}))
}
if _, err := client.Apply(ctx, ms); err != nil {
t.Fatal(err)
}

// Read over invalid table fails
_, err := client.Single().ReadRow(ctx, "badTable", Key{1}, []string{"StringValue"})
if msg, ok := matchError(err, codes.NotFound, "badTable"); !ok {
Expand Down Expand Up @@ -1494,6 +1569,12 @@ func TestIntegration_ReadErrors(t *testing.T) {
if msg, ok := matchError(err, codes.DeadlineExceeded, ""); !ok {
t.Error(msg)
}
// Read should fail if there are multiple rows returned.
_, err = client.Single().ReadRowUsingIndex(ctx, testTable, testTableIndex, Key{"v"}, testTableColumns)
wantMsgPart := fmt.Sprintf("more than one row found by index(Table: %v, IndexKey: %v, Index: %v)", testTable, Key{"v"}, testTableIndex)
if msg, ok := matchError(err, codes.FailedPrecondition, wantMsgPart); !ok {
t.Error(msg)
}
}

// Test TransactionRunner. Test that transactions are aborted and retried as
Expand Down
40 changes: 40 additions & 0 deletions spanner/transaction.go
Expand Up @@ -145,6 +145,16 @@ func errRowNotFound(table string, key Key) error {
return spannerErrorf(codes.NotFound, "row not found(Table: %v, PrimaryKey: %v)", table, key)
}

// errRowNotFoundByIndex returns error for not being able to read the row by index.
func errRowNotFoundByIndex(table string, key Key, index string) error {
return spannerErrorf(codes.NotFound, "row not found(Table: %v, IndexKey: %v, Index: %v)", table, key, index)
}

// errMultipleRowsFound returns error for receiving more than one row when reading a single row using an index.
func errMultipleRowsFound(table string, key Key, index string) error {
return spannerErrorf(codes.FailedPrecondition, "more than one row found by index(Table: %v, IndexKey: %v, Index: %v)", table, key, index)
}

// ReadRow reads a single row from the database.
//
// If no row is present with the given key, then ReadRow returns an error where
Expand All @@ -163,6 +173,36 @@ func (t *txReadOnly) ReadRow(ctx context.Context, table string, key Key, columns
}
}

// ReadRowUsingIndex reads a single row from the database using an index.
//
// If no row is present with the given index, then ReadRowUsingIndex returns an
// error where spanner.ErrCode(err) is codes.NotFound.
//
// If more than one row received with the given index, then ReadRowUsingIndex
// returns an error where spanner.ErrCode(err) is codes.FailedPrecondition.
func (t *txReadOnly) ReadRowUsingIndex(ctx context.Context, table string, index string, key Key, columns []string) (*Row, error) {
iter := t.ReadUsingIndex(ctx, table, index, key, columns)
defer iter.Stop()
row, err := iter.Next()
switch err {
case iterator.Done:
return nil, errRowNotFoundByIndex(table, key, index)
case nil:
// If more than one row found, return an error.
_, err := iter.Next()
switch err {
case iterator.Done:
return row, nil
case nil:
return nil, errMultipleRowsFound(table, key, index)
default:
return nil, err
}
default:
return nil, err
}
}

// Query executes a query against the database. It returns a RowIterator for
// retrieving the resulting rows.
//
Expand Down

0 comments on commit 4f99193

Please sign in to comment.