// SplitQuery is the stub for TabletServer.SplitQuery RPC // TODO(erez): Remove this method and rename SplitQueryV2 to SplitQuery once // the migration to SplitQuery V2 is done. func (conn *gRPCQueryClient) SplitQuery(ctx context.Context, target *querypb.Target, query querytypes.BoundQuery, splitColumn string, splitCount int64) (queries []querytypes.QuerySplit, err error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { err = tabletconn.ConnClosed return } q, err := querytypes.BoundQueryToProto3(query.Sql, query.BindVariables) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } req := &querypb.SplitQueryRequest{ Target: target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Query: q, SplitColumn: []string{splitColumn}, SplitCount: splitCount, NumRowsPerQueryPart: 0, Algorithm: querypb.SplitQueryRequest_EQUAL_SPLITS, UseSplitQueryV2: false, } sqr, err := conn.c.SplitQuery(ctx, req) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } split, err := querytypes.Proto3ToQuerySplits(sqr.Queries) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } return split, nil }
// SplitQuery is the stub for TabletServer.SplitQuery RPC func (conn *gRPCQueryClient) SplitQuery(ctx context.Context, query tproto.BoundQuery, splitColumn string, splitCount int) (queries []tproto.QuerySplit, err error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { err = tabletconn.ConnClosed return } q, err := tproto.BoundQueryToProto3(query.Sql, query.BindVariables) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } req := &pb.SplitQueryRequest{ Target: conn.target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Query: q, SplitColumn: splitColumn, SplitCount: int64(splitCount), SessionId: conn.sessionID, } sqr, err := conn.c.SplitQuery(ctx, req) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } split, err := tproto.Proto3ToQuerySplits(sqr.Queries) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } return split, nil }
// BeginExecute starts a transaction and runs an Execute. func (conn *gRPCQueryClient) BeginExecute(ctx context.Context, target *querypb.Target, query string, bindVars map[string]interface{}, options *querypb.ExecuteOptions) (result *sqltypes.Result, transactionID int64, err error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return nil, 0, tabletconn.ConnClosed } q, err := querytypes.BoundQueryToProto3(query, bindVars) if err != nil { return nil, 0, err } if *combo { // If combo is enabled, we combine both calls req := &querypb.BeginExecuteRequest{ Target: target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Query: q, Options: options, } reply, err := conn.c.BeginExecute(ctx, req) if err != nil { return nil, 0, tabletconn.TabletErrorFromGRPC(err) } if reply.Error != nil { return nil, reply.TransactionId, tabletconn.TabletErrorFromRPCError(reply.Error) } return sqltypes.Proto3ToResult(reply.Result), reply.TransactionId, nil } // Begin part. breq := &querypb.BeginRequest{ Target: target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), } br, err := conn.c.Begin(ctx, breq) if err != nil { return nil, 0, tabletconn.TabletErrorFromGRPC(err) } transactionID = br.TransactionId // Execute part. ereq := &querypb.ExecuteRequest{ Target: target, EffectiveCallerId: breq.EffectiveCallerId, ImmediateCallerId: breq.ImmediateCallerId, Query: q, TransactionId: transactionID, Options: options, } er, err := conn.c.Execute(ctx, ereq) if err != nil { return nil, transactionID, tabletconn.TabletErrorFromGRPC(err) } return sqltypes.Proto3ToResult(er.Result), transactionID, nil }
// SplitQuery is part of tabletconn.TabletConn func (itc *internalTabletConn) SplitQuery(ctx context.Context, target *querypb.Target, query querytypes.BoundQuery, splitColumn string, splitCount int64) ([]querytypes.QuerySplit, error) { splits, err := itc.tablet.qsc.QueryService().SplitQuery(ctx, target, query.Sql, query.BindVariables, splitColumn, splitCount) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(vterrors.ToGRPCError(err)) } return splits, nil }
// ExecuteBatch sends a batch query to VTTablet. func (conn *gRPCQueryClient) ExecuteBatch(ctx context.Context, target *querypb.Target, queries []querytypes.BoundQuery, asTransaction bool, transactionID int64, options *querypb.ExecuteOptions) ([]sqltypes.Result, error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return nil, tabletconn.ConnClosed } req := &querypb.ExecuteBatchRequest{ Target: target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Queries: make([]*querypb.BoundQuery, len(queries)), AsTransaction: asTransaction, TransactionId: transactionID, Options: options, } for i, q := range queries { qq, err := querytypes.BoundQueryToProto3(q.Sql, q.BindVariables) if err != nil { return nil, err } req.Queries[i] = qq } ebr, err := conn.c.ExecuteBatch(ctx, req) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } return sqltypes.Proto3ToResults(ebr.Results), nil }
// SplitQueryV2 is part of tabletconn.TabletConn // TODO(erez): Rename to SplitQuery once the migration to SplitQuery V2 is done. func (itc *internalTabletConn) SplitQueryV2( ctx context.Context, query querytypes.BoundQuery, splitColumns []string, splitCount int64, numRowsPerQueryPart int64, algorithm querypb.SplitQueryRequest_Algorithm) ([]querytypes.QuerySplit, error) { splits, err := itc.tablet.qsc.QueryService().SplitQueryV2( ctx, &querypb.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, query.Sql, query.BindVariables, splitColumns, splitCount, numRowsPerQueryPart, algorithm) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) } return splits, nil }
// StreamExecute is part of tabletconn.TabletConn // We need to copy the bind variables as tablet server will change them. func (itc *internalTabletConn) StreamExecute(ctx context.Context, target *querypb.Target, query string, bindVars map[string]interface{}, options *querypb.ExecuteOptions) (sqltypes.ResultStream, error) { bv, err := querytypes.BindVariablesToProto3(bindVars) if err != nil { return nil, err } bindVars, err = querytypes.Proto3ToBindVariables(bv) if err != nil { return nil, err } result := make(chan *sqltypes.Result, 10) var finalErr error go func() { finalErr = itc.tablet.qsc.QueryService().StreamExecute(ctx, target, query, bindVars, options, func(reply *sqltypes.Result) error { // We need to deep-copy the reply before returning, // because the underlying buffers are reused. result <- reply.Copy() return nil }) finalErr = tabletconn.TabletErrorFromGRPC(vterrors.ToGRPCError(finalErr)) // the client will only access finalErr after the // channel is closed, and then it's already set. close(result) }() return &streamExecuteAdapter{result, &finalErr}, nil }
// StreamHealth is part of tabletconn.TabletConn func (itc *internalTabletConn) StreamHealth(ctx context.Context) (tabletconn.StreamHealthReader, error) { result := make(chan *querypb.StreamHealthResponse, 10) id, err := itc.tablet.qsc.QueryService().StreamHealthRegister(result) if err != nil { return nil, err } var finalErr error go func() { select { case <-ctx.Done(): } // We populate finalErr before closing the channel. // The consumer first waits on the channel closure, // then read finalErr finalErr = itc.tablet.qsc.QueryService().StreamHealthUnregister(id) finalErr = tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(finalErr)) close(result) }() return &streamHealthReader{ c: result, err: &finalErr, }, nil }
// Execute sends the query to VTTablet. func (conn *gRPCQueryClient) Execute(ctx context.Context, query string, bindVars map[string]interface{}, transactionID int64) (*sqltypes.Result, error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return nil, tabletconn.ConnClosed } q, err := tproto.BoundQueryToProto3(query, bindVars) if err != nil { return nil, err } req := &pb.ExecuteRequest{ Target: conn.target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Query: q, TransactionId: transactionID, SessionId: conn.sessionID, } er, err := conn.c.Execute(ctx, req) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } return sqltypes.Proto3ToResult(er.Result), nil }
// ExecuteBatch is part of tabletconn.TabletConn // We need to copy the bind variables as tablet server will change them. func (itc *internalTabletConn) ExecuteBatch(ctx context.Context, queries []tproto.BoundQuery, asTransaction bool, transactionID int64) (*tproto.QueryResultList, error) { q := make([]tproto.BoundQuery, len(queries)) for i, query := range queries { bv, err := tproto.BindVariablesToProto3(query.BindVariables) if err != nil { return nil, err } bindVars, err := tproto.Proto3ToBindVariables(bv) if err != nil { return nil, err } q[i].Sql = query.Sql q[i].BindVariables = bindVars } reply := &tproto.QueryResultList{} if err := itc.tablet.qsc.QueryService().ExecuteBatch(ctx, &querypb.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, &tproto.QueryList{ Queries: q, AsTransaction: asTransaction, TransactionId: transactionID, }, reply); err != nil { return nil, tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) } return reply, nil }
// StreamExecute is part of tabletconn.TabletConn // We need to copy the bind variables as tablet server will change them. func (itc *internalTabletConn) StreamExecute(ctx context.Context, query string, bindVars map[string]interface{}, transactionID int64) (<-chan *mproto.QueryResult, tabletconn.ErrFunc, error) { bindVars = tproto.Proto3ToBindVariables(tproto.BindVariablesToProto3(bindVars)) result := make(chan *mproto.QueryResult, 10) var finalErr error go func() { finalErr = itc.tablet.qsc.QueryService().StreamExecute(ctx, &pbq.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, &tproto.Query{ Sql: query, BindVariables: bindVars, TransactionId: transactionID, }, func(reply *mproto.QueryResult) error { result <- reply return nil }) // the client will only access finalErr after the // channel is closed, and then it's already set. close(result) }() return result, func() error { return tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(finalErr)) }, nil }
// StreamExecute is part of tabletconn.TabletConn // We need to copy the bind variables as tablet server will change them. func (itc *internalTabletConn) StreamExecute(ctx context.Context, query string, bindVars map[string]interface{}, transactionID int64) (<-chan *sqltypes.Result, tabletconn.ErrFunc, error) { bv, err := querytypes.BindVariablesToProto3(bindVars) if err != nil { return nil, nil, err } bindVars, err = querytypes.Proto3ToBindVariables(bv) if err != nil { return nil, nil, err } result := make(chan *sqltypes.Result, 10) var finalErr error go func() { finalErr = itc.tablet.qsc.QueryService().StreamExecute(ctx, &querypb.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, query, bindVars, 0, func(reply *sqltypes.Result) error { // We need to deep-copy the reply before returning, // because the underlying buffers are reused. result <- reply.Copy() return nil }) // the client will only access finalErr after the // channel is closed, and then it's already set. close(result) }() return result, func() error { return tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(finalErr)) }, nil }
// Begin is part of tabletconn.TabletConn func (itc *internalTabletConn) Begin(ctx context.Context, target *querypb.Target) (int64, error) { transactionID, err := itc.tablet.qsc.QueryService().Begin(ctx, target) if err != nil { return 0, tabletconn.TabletErrorFromGRPC(vterrors.ToGRPCError(err)) } return transactionID, nil }
// ExecuteBatch sends a batch query to VTTablet. func (conn *gRPCQueryClient) ExecuteBatch(ctx context.Context, queries []tproto.BoundQuery, asTransaction bool, transactionID int64) (*tproto.QueryResultList, error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return nil, tabletconn.ConnClosed } req := &pb.ExecuteBatchRequest{ Target: conn.target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Queries: make([]*pb.BoundQuery, len(queries)), AsTransaction: asTransaction, TransactionId: transactionID, SessionId: conn.sessionID, } for i, q := range queries { req.Queries[i] = tproto.BoundQueryToProto3(q.Sql, q.BindVariables) } ebr, err := conn.c.ExecuteBatch(ctx, req) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } return tproto.Proto3ToQueryResultList(ebr.Results), nil }
// Rollback is part of tabletconn.TabletConn func (itc *internalTabletConn) Rollback(ctx context.Context, transactionID int64) error { err := itc.tablet.qsc.QueryService().Rollback(ctx, &querypb.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, 0, transactionID) return tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) }
// StreamExecute starts a streaming query to VTTablet. func (conn *gRPCQueryClient) StreamExecute(ctx context.Context, query string, bindVars map[string]interface{}, transactionID int64) (<-chan *sqltypes.Result, tabletconn.ErrFunc, error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return nil, nil, tabletconn.ConnClosed } q, err := querytypes.BoundQueryToProto3(query, bindVars) if err != nil { return nil, nil, err } req := &querypb.StreamExecuteRequest{ Target: conn.target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Query: q, SessionId: conn.sessionID, } stream, err := conn.c.StreamExecute(ctx, req) if err != nil { return nil, nil, tabletconn.TabletErrorFromGRPC(err) } sr := make(chan *sqltypes.Result, 10) var finalError error go func() { var fields []*querypb.Field for { ser, err := stream.Recv() if err != nil { if err != io.EOF { finalError = tabletconn.TabletErrorFromGRPC(err) } close(sr) return } if fields == nil { fields = ser.Result.Fields } sr <- sqltypes.CustomProto3ToResult(fields, ser.Result) } }() return sr, func() error { return finalError }, nil }
// Commit is part of tabletconn.TabletConn func (itc *internalTabletConn) Commit(ctx context.Context, transactionID int64) error { err := itc.tablet.qsc.QueryService().Commit(ctx, &pbq.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, &tproto.Session{ TransactionId: transactionID, }) return tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) }
// Begin is part of tabletconn.TabletConn func (itc *internalTabletConn) Begin(ctx context.Context) (int64, error) { transactionID, err := itc.tablet.qsc.QueryService().Begin(ctx, &querypb.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, 0) if err != nil { return 0, tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) } return transactionID, nil }
// Begin is part of tabletconn.TabletConn func (itc *internalTabletConn) Begin(ctx context.Context) (transactionID int64, err error) { result := &tproto.TransactionInfo{} if err := itc.tablet.qsc.QueryService().Begin(ctx, &pbq.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, &tproto.Session{}, result); err != nil { return 0, tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) } return result.TransactionId, nil }
func (a *updateStreamAdapter) Recv() (*querypb.StreamEvent, error) { r, err := a.stream.Recv() switch err { case nil: return r.Event, nil case io.EOF: return nil, err default: return nil, tabletconn.TabletErrorFromGRPC(err) } }
func (a *streamExecuteAdapter) Recv() (*sqltypes.Result, error) { ser, err := a.stream.Recv() switch err { case nil: if a.fields == nil { a.fields = ser.Result.Fields } return sqltypes.CustomProto3ToResult(a.fields, ser.Result), nil case io.EOF: return nil, err default: return nil, tabletconn.TabletErrorFromGRPC(err) } }
// SplitQuery is part of tabletconn.TabletConn func (itc *internalTabletConn) SplitQuery(ctx context.Context, query tproto.BoundQuery, splitColumn string, splitCount int) ([]tproto.QuerySplit, error) { reply := &tproto.SplitQueryResult{} if err := itc.tablet.qsc.QueryService().SplitQuery(ctx, &pbq.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, &tproto.SplitQueryRequest{ Query: query, SplitColumn: splitColumn, SplitCount: splitCount, }, reply); err != nil { return nil, tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) } return reply.Queries, nil }
// Execute is part of tabletconn.TabletConn // We need to copy the bind variables as tablet server will change them. func (itc *internalTabletConn) Execute(ctx context.Context, target *querypb.Target, query string, bindVars map[string]interface{}, transactionID int64, options *querypb.ExecuteOptions) (*sqltypes.Result, error) { bv, err := querytypes.BindVariablesToProto3(bindVars) if err != nil { return nil, err } bindVars, err = querytypes.Proto3ToBindVariables(bv) if err != nil { return nil, err } reply, err := itc.tablet.qsc.QueryService().Execute(ctx, target, query, bindVars, transactionID, options) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(vterrors.ToGRPCError(err)) } return reply, nil }
// Execute is part of tabletconn.TabletConn // We need to copy the bind variables as tablet server will change them. func (itc *internalTabletConn) Execute(ctx context.Context, query string, bindVars map[string]interface{}, transactionID int64) (*mproto.QueryResult, error) { bindVars = tproto.Proto3ToBindVariables(tproto.BindVariablesToProto3(bindVars)) reply := &mproto.QueryResult{} if err := itc.tablet.qsc.QueryService().Execute(ctx, &pbq.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, &tproto.Query{ Sql: query, BindVariables: bindVars, TransactionId: transactionID, }, reply); err != nil { return nil, tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) } return reply, nil }
// Begin starts a transaction. func (conn *gRPCQueryClient) Begin(ctx context.Context, target *querypb.Target) (transactionID int64, err error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return 0, tabletconn.ConnClosed } req := &querypb.BeginRequest{ Target: target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), } br, err := conn.c.Begin(ctx, req) if err != nil { return 0, tabletconn.TabletErrorFromGRPC(err) } return br.TransactionId, nil }
// ReadTransaction returns the metadata for the sepcified dtid. func (conn *gRPCQueryClient) ReadTransaction(ctx context.Context, target *querypb.Target, dtid string) (*querypb.TransactionMetadata, error) { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return nil, tabletconn.ConnClosed } req := &querypb.ReadTransactionRequest{ Target: target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Dtid: dtid, } response, err := conn.c.ReadTransaction(ctx, req) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(err) } return response.Metadata, nil }
// Rollback rolls back the ongoing transaction. func (conn *gRPCQueryClient) Rollback(ctx context.Context, target *querypb.Target, transactionID int64) error { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return tabletconn.ConnClosed } req := &querypb.RollbackRequest{ Target: target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), TransactionId: transactionID, } _, err := conn.c.Rollback(ctx, req) if err != nil { return tabletconn.TabletErrorFromGRPC(err) } return nil }
// Execute is part of tabletconn.TabletConn // We need to copy the bind variables as tablet server will change them. func (itc *internalTabletConn) Execute(ctx context.Context, query string, bindVars map[string]interface{}, transactionID int64) (*sqltypes.Result, error) { bv, err := querytypes.BindVariablesToProto3(bindVars) if err != nil { return nil, err } bindVars, err = querytypes.Proto3ToBindVariables(bv) if err != nil { return nil, err } reply, err := itc.tablet.qsc.QueryService().Execute(ctx, &querypb.Target{ Keyspace: itc.tablet.keyspace, Shard: itc.tablet.shard, TabletType: itc.tablet.tabletType, }, query, bindVars, 0, transactionID) if err != nil { return nil, tabletconn.TabletErrorFromGRPC(tabletserver.ToGRPCError(err)) } return reply, nil }
// CommitPrepared commits the prepared transaction. func (conn *gRPCQueryClient) CommitPrepared(ctx context.Context, target *querypb.Target, dtid string) error { conn.mu.RLock() defer conn.mu.RUnlock() if conn.cc == nil { return tabletconn.ConnClosed } req := &querypb.CommitPreparedRequest{ Target: target, EffectiveCallerId: callerid.EffectiveCallerIDFromContext(ctx), ImmediateCallerId: callerid.ImmediateCallerIDFromContext(ctx), Dtid: dtid, } _, err := conn.c.CommitPrepared(ctx, req) if err != nil { return tabletconn.TabletErrorFromGRPC(err) } return nil }
// UpdateStream is part of tabletconn.TabletConn. func (itc *internalTabletConn) UpdateStream(ctx context.Context, target *querypb.Target, position string, timestamp int64) (tabletconn.StreamEventReader, error) { result := make(chan *querypb.StreamEvent, 10) var finalErr error go func() { finalErr = itc.tablet.qsc.QueryService().UpdateStream(ctx, target, position, timestamp, func(reply *querypb.StreamEvent) error { // We need to deep-copy the reply before returning, // because the underlying buffers are reused. result <- proto.Clone(reply).(*querypb.StreamEvent) return nil }) finalErr = tabletconn.TabletErrorFromGRPC(vterrors.ToGRPCError(finalErr)) // the client will only access finalErr after the // channel is closed, and then it's already set. close(result) }() return &updateStreamAdapter{result, &finalErr}, nil }