Skip to content

Commit 2911a66

Browse files
committed
Ensure Count in GET response is consistent with key filtering by revision
Signed-off-by: Cristian Ferretti <[email protected]>
1 parent 37cbd6c commit 2911a66

File tree

4 files changed

+58
-14
lines changed

4 files changed

+58
-14
lines changed

etcdctl/ctlv3/ctl.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ func init() {
5252
rootCmd.PersistentFlags().StringSliceVar(&globalFlags.Endpoints, "endpoints", []string{"127.0.0.1:2379"}, "gRPC endpoints")
5353
rootCmd.PersistentFlags().BoolVar(&globalFlags.Debug, "debug", false, "enable client-side debug logging")
5454

55-
rootCmd.PersistentFlags().StringVarP(&globalFlags.OutputFormat, "write-out", "w", "simple", "set the output format (fields, json, protobuf, simple, table)")
55+
rootCmd.PersistentFlags().StringVarP(&globalFlags.OutputFormat, "write-out", "w", "simple",
56+
"set the output format (fields, json, protobuf, simple, table); note json encodes kvs as base64")
5657
rootCmd.PersistentFlags().BoolVar(&globalFlags.IsHex, "hex", false, "print byte strings as hex encoded strings")
5758
rootCmd.RegisterFlagCompletionFunc("write-out", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
5859
return []string{"fields", "json", "protobuf", "simple", "table"}, cobra.ShellCompDirectiveDefault

etcdutl/ctl.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ var (
3535
)
3636

3737
func init() {
38-
rootCmd.PersistentFlags().StringVarP(&etcdutl.OutputFormat, "write-out", "w", "simple", "set the output format (fields, json, protobuf, simple, table)")
38+
rootCmd.PersistentFlags().StringVarP(&etcdutl.OutputFormat, "write-out", "w", "simple",
39+
"set the output format (fields, json, protobuf, simple, table); note json encodes kvs as base64")
3940
rootCmd.RegisterFlagCompletionFunc("write-out", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
4041
return []string{"fields", "json", "protobuf", "simple", "table"}, cobra.ShellCompDirectiveDefault
4142
})

server/etcdserver/txn/txn.go

+35-9
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,6 @@ func Range(ctx context.Context, lg *zap.Logger, kv mvcc.KV, r *pb.RangeRequest)
152152
func executeRange(ctx context.Context, lg *zap.Logger, txnRead mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error) {
153153
trace := traceutil.Get(ctx)
154154

155-
resp := &pb.RangeResponse{}
156-
resp.Header = &pb.ResponseHeader{}
157-
158155
limit := r.Limit
159156
if r.SortOrder != pb.RangeRequest_NONE ||
160157
r.MinModRevision != 0 || r.MaxModRevision != 0 ||
@@ -167,17 +164,41 @@ func executeRange(ctx context.Context, lg *zap.Logger, txnRead mvcc.TxnRead, r *
167164
limit = limit + 1
168165
}
169166

167+
// Combining r.CountOnly and any of { r.MaxModRevision, r.MinModRevision, r.MaxCreateRevision, r.MinCreateRevision }
168+
// means "give me the count for the result of the filtering I asked for".
169+
filtering := r.MaxModRevision != 0 || r.MinModRevision != 0 || r.MaxCreateRevision != 0 || r.MinCreateRevision != 0
170+
var noKvsToProcess bool
171+
if filtering {
172+
noKvsToProcess = false
173+
} else {
174+
noKvsToProcess = r.CountOnly
175+
}
176+
170177
ro := mvcc.RangeOptions{
171178
Limit: limit,
172179
Rev: r.Revision,
173-
Count: r.CountOnly,
180+
Count: noKvsToProcess,
174181
}
175182

176183
rr, err := txnRead.Range(ctx, r.Key, mkGteRange(r.RangeEnd), ro)
177184
if err != nil {
178185
return nil, err
179186
}
187+
resp := &pb.RangeResponse{Header: &pb.ResponseHeader{Revision: rr.Rev}}
188+
189+
if noKvsToProcess {
190+
resp.Count = int64(rr.Count)
191+
resp.Kvs = make([]*mvccpb.KeyValue, 0)
192+
} else {
193+
processKvsInRange(r, rr, resp, trace, lg)
194+
}
195+
196+
trace.Step("assemble the response")
197+
return resp, nil
198+
}
180199

200+
func processKvsInRange(r *pb.RangeRequest, rr *mvcc.RangeResult, resp *pb.RangeResponse, trace *traceutil.Trace, lg *zap.Logger) {
201+
trace.Step("filter and sort the key-value pairs")
181202
if r.MaxModRevision != 0 {
182203
f := func(kv *mvccpb.KeyValue) bool { return kv.ModRevision > r.MaxModRevision }
183204
pruneKVs(rr, f)
@@ -195,6 +216,16 @@ func executeRange(ctx context.Context, lg *zap.Logger, txnRead mvcc.TxnRead, r *
195216
pruneKVs(rr, f)
196217
}
197218

219+
// No more prunning after this point; we can count now.
220+
resp.Count = int64(len(rr.KVs))
221+
// If r.CountOnly was specified:
222+
// * r.SortOrder is useless and ignored.
223+
// * r.Limit is useless and ignored.
224+
if r.CountOnly {
225+
resp.Kvs = make([]*mvccpb.KeyValue, 0)
226+
return
227+
}
228+
198229
sortOrder := r.SortOrder
199230
if r.SortTarget != pb.RangeRequest_KEY && sortOrder == pb.RangeRequest_NONE {
200231
// Since current mvcc.Range implementation returns results
@@ -235,18 +266,13 @@ func executeRange(ctx context.Context, lg *zap.Logger, txnRead mvcc.TxnRead, r *
235266
rr.KVs = rr.KVs[:r.Limit]
236267
resp.More = true
237268
}
238-
trace.Step("filter and sort the key-value pairs")
239-
resp.Header.Revision = rr.Rev
240-
resp.Count = int64(rr.Count)
241269
resp.Kvs = make([]*mvccpb.KeyValue, len(rr.KVs))
242270
for i := range rr.KVs {
243271
if r.KeysOnly {
244272
rr.KVs[i].Value = nil
245273
}
246274
resp.Kvs[i] = &rr.KVs[i]
247275
}
248-
trace.Step("assemble the response")
249-
return resp, nil
250276
}
251277

252278
func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWithSharedBuffer bool, kv mvcc.KV, lessor lease.Lessor) (*pb.TxnResponse, *traceutil.Trace, error) {

tests/e2e/ctl_v3_kv_test.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func getMinMaxCreateModRevTest(cx ctlCtx) {
245245

246246
for i, tt := range tests {
247247
if err := ctlV3Get(cx, tt.args, tt.wkv...); err != nil {
248-
cx.t.Errorf("getMinModRevTest #%d: ctlV3Get error (%v)", i, err)
248+
cx.t.Errorf("getMinMaxCreateModRevTest #%d: ctlV3Get error (%v)", i, err)
249249
}
250250
}
251251
}
@@ -387,15 +387,31 @@ type kv struct {
387387
key, val string
388388
}
389389

390+
// returns -1 if searchArg is not found, or the index when found.
391+
func findArg(cx ctlCtx, searchArg int) int {
392+
for i, arg := range cx.PrefixArgs() {
393+
if arg == searchArg {
394+
return i
395+
}
396+
}
397+
return -1
398+
}
399+
390400
func ctlV3Get(cx ctlCtx, args []string, kvs ...kv) error {
391-
cmdArgs := append(cx.PrefixArgs(), "get")
401+
cmdArgs := append(cx.PrefixArgs(), "get", "--write-out=fields")
392402
cmdArgs = append(cmdArgs, args...)
393403
if !cx.quorum {
394404
cmdArgs = append(cmdArgs, "--consistency", "s")
395405
}
396406
var lines []expect.ExpectedResponse
397407
for _, elem := range kvs {
398-
lines = append(lines, expect.ExpectedResponse{Value: elem.key}, expect.ExpectedResponse{Value: elem.val})
408+
lines = append(lines,
409+
expect.ExpectedResponse{Value: fmt.Sprintf("\"Key\" : \"%s\"", elem.key)},
410+
expect.ExpectedResponse{Value: fmt.Sprintf("\"Value\" : \"%s\"", elem.val)},
411+
)
412+
}
413+
if findArg(cx, "--limit") == -1 {
414+
lines = append(lines, expect.ExpectedResponse{Value: fmt.Sprintf("\"Count\" : %d", len(kvs))})
399415
}
400416
return e2e.SpawnWithExpects(cmdArgs, cx.envMap, lines...)
401417
}

0 commit comments

Comments
 (0)