// Apply applies a Mutation to a specific row. func (t *Table) Apply(ctx context.Context, row string, m *Mutation, opts ...ApplyOption) error { ctx = mergeMetadata(ctx, t.md) after := func(res proto.Message) { for _, o := range opts { o.after(res) } } var callOptions []gax.CallOption if m.cond == nil { req := &btpb.MutateRowRequest{ TableName: t.c.fullTableName(t.table), RowKey: []byte(row), Mutations: m.ops, } if mutationsAreRetryable(m.ops) { callOptions = retryOptions } var res *btpb.MutateRowResponse err := gax.Invoke(ctx, func(ctx context.Context) error { var err error res, err = t.c.client.MutateRow(ctx, req) return err }, callOptions...) if err == nil { after(res) } return err } req := &btpb.CheckAndMutateRowRequest{ TableName: t.c.fullTableName(t.table), RowKey: []byte(row), PredicateFilter: m.cond.proto(), } if m.mtrue != nil { req.TrueMutations = m.mtrue.ops } if m.mfalse != nil { req.FalseMutations = m.mfalse.ops } if mutationsAreRetryable(req.TrueMutations) && mutationsAreRetryable(req.FalseMutations) { callOptions = retryOptions } var cmRes *btpb.CheckAndMutateRowResponse err := gax.Invoke(ctx, func(ctx context.Context) error { var err error cmRes, err = t.c.client.CheckAndMutateRow(ctx, req) return err }, callOptions...) if err == nil { after(cmRes) } return err }
// ApplyBulk applies multiple Mutations. // Each mutation is individually applied atomically, // but the set of mutations may be applied in any order. // // Two types of failures may occur. If the entire process // fails, (nil, err) will be returned. If specific mutations // fail to apply, ([]err, nil) will be returned, and the errors // will correspond to the relevant rowKeys/muts arguments. // // Conditional mutations cannot be applied in bulk and providing one will result in an error. func (t *Table) ApplyBulk(ctx context.Context, rowKeys []string, muts []*Mutation, opts ...ApplyOption) ([]error, error) { ctx = mergeMetadata(ctx, t.md) if len(rowKeys) != len(muts) { return nil, fmt.Errorf("mismatched rowKeys and mutation array lengths: %d, %d", len(rowKeys), len(muts)) } origEntries := make([]*entryErr, len(rowKeys)) for i, key := range rowKeys { mut := muts[i] if mut.cond != nil { return nil, errors.New("conditional mutations cannot be applied in bulk") } origEntries[i] = &entryErr{Entry: &btpb.MutateRowsRequest_Entry{RowKey: []byte(key), Mutations: mut.ops}} } // entries will be reduced after each invocation to just what needs to be retried. entries := make([]*entryErr, len(rowKeys)) copy(entries, origEntries) err := gax.Invoke(ctx, func(ctx context.Context) error { err := t.doApplyBulk(ctx, entries, opts...) if err != nil { // We want to retry the entire request with the current entries return err } entries = t.getApplyBulkRetries(entries) if len(entries) > 0 && len(idempotentRetryCodes) > 0 { // We have at least one mutation that needs to be retried. // Return an arbitrary error that is retryable according to callOptions. return grpc.Errorf(idempotentRetryCodes[0], "Synthetic error: partial failure of ApplyBulk") } return nil }, retryOptions...) if err != nil { return nil, err } // Accumulate all of the errors into an array to return, interspersed with nils for successful // entries. The absence of any errors means we should return nil. var errs []error var foundErr bool for _, entry := range origEntries { if entry.Err != nil { foundErr = true } errs = append(errs, entry.Err) } if foundErr { return errs, nil } return nil, nil }
// ReadRows reads rows from a table. f is called for each row. // If f returns false, the stream is shut down and ReadRows returns. // f owns its argument, and f is called serially in order by row key. // // By default, the yielded rows will contain all values in all cells. // Use RowFilter to limit the cells returned. func (t *Table) ReadRows(ctx context.Context, arg RowSet, f func(Row) bool, opts ...ReadOption) error { ctx = mergeMetadata(ctx, t.md) var prevRowKey string err := gax.Invoke(ctx, func(ctx context.Context) error { req := &btpb.ReadRowsRequest{ TableName: t.c.fullTableName(t.table), Rows: arg.proto(), } for _, opt := range opts { opt.set(req) } ctx, cancel := context.WithCancel(ctx) // for aborting the stream defer cancel() stream, err := t.c.client.ReadRows(ctx, req) if err != nil { return err } cr := newChunkReader() for { res, err := stream.Recv() if err == io.EOF { break } if err != nil { // Reset arg for next Invoke call. arg = arg.retainRowsAfter(prevRowKey) return err } for _, cc := range res.Chunks { row, err := cr.Process(cc) if err != nil { // No need to prepare for a retry, this is an unretryable error. return err } if row == nil { continue } prevRowKey = row.Key() if !f(row) { // Cancel and drain stream. cancel() for { if _, err := stream.Recv(); err != nil { // The stream has ended. We don't return an error // because the caller has intentionally interrupted the scan. return nil } } } } if err := cr.Close(); err != nil { // No need to prepare for a retry, this is an unretryable error. return err } } return err }, retryOptions...) return err }