Skip to content
Permalink
Browse files
feat(spanner): export ToSpannerError (#3133)
* feat: export ToSpannerError

The ToSpannerError method should be exported to allow users to create
Spanner errors to use with unit tests. Currently, that was only possible
by creating a struct directly and filling the deprecated Code field. That
is however no longer recommended, as the code field might be removed in a
future release.

Fixes #3122

* fix: use New instead of Newf
  • Loading branch information
olavloite committed Nov 4, 2020
1 parent 5899bdd commit b951d8bd194b76da0a8bf2ce7cf85b546d2e051c
Showing with 31 additions and 24 deletions.
  1. +2 −2 spanner/client.go
  2. +1 −1 spanner/client_test.go
  3. +9 −2 spanner/errors.go
  4. +1 −1 spanner/errors_test.go
  5. +1 −1 spanner/mutation.go
  6. +3 −3 spanner/pdml.go
  7. +1 −1 spanner/read.go
  8. +3 −3 spanner/session.go
  9. +3 −3 spanner/sessionclient.go
  10. +4 −4 spanner/transaction.go
  11. +2 −2 spanner/transaction_test.go
  12. +1 −1 spanner/value.go
@@ -117,7 +117,7 @@ type ClientConfig struct {

// errDial returns error for dialing to Cloud Spanner.
func errDial(ci int, err error) error {
e := toSpannerError(err).(*Error)
e := ToSpannerError(err).(*Error)
e.decorate(fmt.Sprintf("dialing fails for channel[%v]", ci))
return e
}
@@ -341,7 +341,7 @@ func (c *Client) BatchReadOnlyTransaction(ctx context.Context, tb TimestampBound
},
})
if err != nil {
return nil, toSpannerError(err)
return nil, ToSpannerError(err)
}
tx = res.Id
if res.ReadTimestamp != nil {
@@ -537,7 +537,7 @@ func TestClient_ReadOnlyTransaction_SessionNotFoundOnExecuteStreamingSql(t *test
err := testReadOnlyTransaction(t, map[string]SimulatedExecutionTime{
MethodExecuteStreamingSql: {Errors: []error{newSessionNotFoundError("projects/p/instances/i/databases/d/sessions/s")}},
})
want := toSpannerError(newSessionNotFoundError("projects/p/instances/i/databases/d/sessions/s"))
want := ToSpannerError(newSessionNotFoundError("projects/p/instances/i/databases/d/sessions/s"))
if err == nil {
t.Fatalf("missing expected error\nGot: nil\nWant: %v", want)
}
@@ -115,8 +115,15 @@ func spannerErrorf(code codes.Code, format string, args ...interface{}) error {
}
}

// toSpannerError converts general Go error to *spanner.Error.
func toSpannerError(err error) error {
// ToSpannerError converts a general Go error to *spanner.Error. If the given
// error is already a *spanner.Error, the original error will be returned.
//
// Spanner Errors are normally created by the Spanner client library from the
// returned status of a RPC. This method can also be used to create Spanner
// errors for use in tests. The recommended way to create test errors is
// calling this method with a status error, e.g.
// ToSpannerError(status.New(codes.NotFound, "Table not found").Err())
func ToSpannerError(err error) error {
return toSpannerErrorWithCommitInfo(err, false)
}

@@ -63,7 +63,7 @@ func TestToSpannerError(t *testing.T) {
wrapped: errors.New("wha?"),
msg: "error with wrapped non-gRPC and non-Spanner error"}},
} {
err := toSpannerError(test.err)
err := ToSpannerError(test.err)
errDuringCommit := toSpannerErrorWithCommitInfo(test.err, true)
if got, want := ErrCode(err), test.wantCode; got != want {
t.Errorf("%v: got %s, want %s", test.err, got, want)
@@ -179,7 +179,7 @@ func structToMutationParams(in interface{}) ([]string, []interface{}, error) {
}
fields, err := fieldCache.Fields(t)
if err != nil {
return nil, nil, toSpannerError(err)
return nil, nil, ToSpannerError(err)
}
var cols []string
var vals []interface{}
@@ -51,7 +51,7 @@ func (c *Client) partitionedUpdate(ctx context.Context, statement Statement, opt

sh, err := c.idleSessions.take(ctx)
if err != nil {
return 0, toSpannerError(err)
return 0, ToSpannerError(err)
}
if sh != nil {
defer sh.recycle()
@@ -61,7 +61,7 @@ func (c *Client) partitionedUpdate(ctx context.Context, statement Statement, opt
// The transaction reference will be added by the executePdml method.
params, paramTypes, err := statement.convertParams()
if err != nil {
return 0, toSpannerError(err)
return 0, ToSpannerError(err)
}
req := &sppb.ExecuteSqlRequest{
Session: sh.getID(),
@@ -107,7 +107,7 @@ func executePdml(ctx context.Context, sh *sessionHandle, req *sppb.ExecuteSqlReq
},
})
if err != nil {
return 0, toSpannerError(err)
return 0, ToSpannerError(err)
}
// Add a reference to the PDML transaction on the ExecuteSql request.
req.Transaction = &sppb.TransactionSelector{
@@ -157,7 +157,7 @@ func (r *RowIterator) Next() (*Row, error) {
return row, nil
}
if err := r.streamd.lastErr(); err != nil {
r.err = toSpannerError(err)
r.err = ToSpannerError(err)
} else if !r.rowd.done() {
r.err = errEarlyReadEnd()
} else {
@@ -1033,13 +1033,13 @@ func (p *sessionPool) takeWriteSession(ctx context.Context) (*sessionHandle, err
s.destroy(false)
trace.TracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()},
"Session not found for write")
return nil, toSpannerError(err)
return nil, ToSpannerError(err)
}

s.recycle()
trace.TracePrintf(ctx, map[string]interface{}{"sessionID": s.getID()},
"Error preparing session for write")
return nil, toSpannerError(err)
return nil, ToSpannerError(err)
}
}
p.incNumInUse(ctx)
@@ -1506,7 +1506,7 @@ func (hc *healthChecker) worker(i int) {
// cycle.
// Don't log about permission errors, which may be expected
// (e.g. using read-only auth).
serr := toSpannerError(err).(*Error)
serr := ToSpannerError(err).(*Error)
if serr.Code != codes.PermissionDenied {
logf(hc.pool.sc.logger, "Failed to prepare session, error: %v", serr)
}
@@ -135,7 +135,7 @@ func (sc *sessionClient) createSession(ctx context.Context) (*session, error) {
Session: &sppb.Session{Labels: sc.sessionLabels},
})
if err != nil {
return nil, toSpannerError(err)
return nil, ToSpannerError(err)
}
return &session{valid: true, client: client, id: sid.Name, createTime: time.Now(), md: sc.md, logger: sc.logger}, nil
}
@@ -227,7 +227,7 @@ func (sc *sessionClient) executeBatchCreateSessions(client *vkit.Client, createC
}
if ctx.Err() != nil {
trace.TracePrintf(ctx, nil, "Context error while creating a batch of %d sessions: %v", createCount, ctx.Err())
consumer.sessionCreationFailed(toSpannerError(ctx.Err()), remainingCreateCount)
consumer.sessionCreationFailed(ToSpannerError(ctx.Err()), remainingCreateCount)
break
}
response, err := client.BatchCreateSessions(ctx, &sppb.BatchCreateSessionsRequest{
@@ -237,7 +237,7 @@ func (sc *sessionClient) executeBatchCreateSessions(client *vkit.Client, createC
})
if err != nil {
trace.TracePrintf(ctx, nil, "Error creating a batch of %d sessions: %v", remainingCreateCount, err)
consumer.sessionCreationFailed(toSpannerError(err), remainingCreateCount)
consumer.sessionCreationFailed(ToSpannerError(err), remainingCreateCount)
break
}
actuallyCreated := int32(len(response.Session))
@@ -489,7 +489,7 @@ func (t *ReadOnlyTransaction) begin(ctx context.Context) error {
rts = time.Unix(res.ReadTimestamp.Seconds, int64(res.ReadTimestamp.Nanos))
}
} else {
err = toSpannerError(err)
err = ToSpannerError(err)
}
break
}
@@ -827,7 +827,7 @@ func (t *ReadWriteTransaction) update(ctx context.Context, stmt Statement, opts
}
resultSet, err := sh.getClient().ExecuteSql(contextWithOutgoingMetadata(ctx, sh.getMetadata()), req)
if err != nil {
return 0, toSpannerError(err)
return 0, ToSpannerError(err)
}
if resultSet.Stats == nil {
return 0, spannerErrorf(codes.InvalidArgument, "query passed to Update: %q", stmt.SQL)
@@ -876,7 +876,7 @@ func (t *ReadWriteTransaction) BatchUpdate(ctx context.Context, stmts []Statemen
Seqno: atomic.AddInt64(&t.sequenceNumber, 1),
})
if err != nil {
return nil, toSpannerError(err)
return nil, ToSpannerError(err)
}

var counts []int64
@@ -1209,7 +1209,7 @@ func (t *writeOnlyTransaction) applyAtLeastOnce(ctx context.Context, ms ...*Muta
break
}
}
return ts, toSpannerError(err)
return ts, ToSpannerError(err)
}

// isAbortedErr returns true if the error indicates that an gRPC call is
@@ -78,7 +78,7 @@ func TestReadOnlyTransaction_RecoverFromFailure(t *testing.T) {
})

_, _, e := txn.acquire(ctx)
if wantErr := toSpannerError(errUsr); !testEqual(e, wantErr) {
if wantErr := ToSpannerError(errUsr); !testEqual(e, wantErr) {
t.Fatalf("Acquire for multi use, got %v, want %v.", e, wantErr)
}
_, _, e = txn.acquire(ctx)
@@ -238,7 +238,7 @@ func TestTransaction_SessionNotFound(t *testing.T) {
t.Fatalf("Expect Read to succeed, got %v, want %v.", got.err, wantErr)
}

wantErr = toSpannerError(newSessionNotFoundError("projects/p/instances/i/databases/d/sessions/s"))
wantErr = ToSpannerError(newSessionNotFoundError("projects/p/instances/i/databases/d/sessions/s"))
ms := []*Mutation{
Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(1), "Foo", int64(50)}),
Insert("Accounts", []string{"AccountId", "Nickname", "Balance"}, []interface{}{int64(2), "Bar", int64(1)}),
@@ -2389,7 +2389,7 @@ func decodeStruct(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}) er

fields, err := fieldCache.Fields(t)
if err != nil {
return toSpannerError(err)
return ToSpannerError(err)
}
seen := map[string]bool{}
for i, f := range ty.Fields {

0 comments on commit b951d8b

Please sign in to comment.