Skip to content

Commit 769dfca

Browse files
bbrksclaude
andauthored
CBG-5522: Implement ScanResultIterator.Err() in gocb range scan wrapper (#8403)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ba35f1e commit 769dfca

4 files changed

Lines changed: 46 additions & 9 deletions

File tree

base/collection_rangescan.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,28 @@ func (it *gocbScanResultIterator) Next(_ context.Context) *sgbucket.ScanResultIt
8484
return result
8585
}
8686

87+
func (it *gocbScanResultIterator) Err() error {
88+
if it.err != nil {
89+
return it.err
90+
}
91+
return it.result.Err()
92+
}
93+
8794
func (it *gocbScanResultIterator) Close(_ context.Context) error {
88-
closeErr := it.result.Close()
89-
if it.err == nil {
95+
if it.err != nil {
96+
// A real error is already recorded (e.g. a content-decode failure);
97+
// release the stream but keep the first error.
98+
_ = it.result.Close()
99+
return it.err
100+
}
101+
// gocb's Close returns the first recorded stream error, or nil after
102+
// cancelling a clean scan.
103+
if closeErr := it.result.Close(); closeErr != nil {
90104
it.err = closeErr
105+
return it.err
91106
}
92-
return it.err
107+
// Clean end-of-stream: record ErrScanCancelled so a later Err reports it
108+
// (matching Rosmar) rather than leaking gocb's internal ErrRequestCanceled.
109+
it.err = sgbucket.ErrScanCancelled
110+
return nil
93111
}

base/collection_rangescan_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ func collectScanIDs(t testing.TB, ctx context.Context, rss sgbucket.RangeScanSto
3333
for item := iter.Next(ctx); item != nil; item = iter.Next(ctx) {
3434
ids = append(ids, item.ID)
3535
}
36+
// A clean drain must leave no error, inspectable without closing the iterator.
37+
assert.NoError(t, iter.Err())
3638
sort.Strings(ids)
3739
return ids
3840
}
@@ -82,6 +84,7 @@ func runRangeScanSubtests(t *testing.T, ctx context.Context, writeDS sgbucket.Da
8284
assert.NotNil(t, item.Body, "Expected body for key %s", item.ID)
8385
assert.NotZero(t, item.Cas, "Expected non-zero CAS for key %s", item.ID)
8486
}
87+
assert.NoError(t, iter.Err())
8588
sort.Strings(ids)
8689
require.Equal(t, allDocIDs, ids)
8790
})
@@ -105,6 +108,7 @@ func runRangeScanSubtests(t *testing.T, ctx context.Context, writeDS sgbucket.Da
105108
ids = append(ids, item.ID)
106109
assert.Nil(t, item.Body, "Expected nil body for IDsOnly scan, key %s", item.ID)
107110
}
111+
assert.NoError(t, iter.Err())
108112
sort.Strings(ids)
109113
require.Equal(t, allDocIDs, ids)
110114
})
@@ -114,6 +118,21 @@ func runRangeScanSubtests(t *testing.T, ctx context.Context, writeDS sgbucket.Da
114118
assert.Empty(t, ids)
115119
})
116120

121+
t.Run("CloseCancellation", func(t *testing.T) {
122+
iter, err := scanStore.Scan(ctx, sgbucket.NewRangeScanForPrefix("doc_"), sgbucket.ScanOptions{IDsOnly: true})
123+
require.NoError(t, err)
124+
for item := iter.Next(ctx); item != nil; item = iter.Next(ctx) {
125+
require.NotEmpty(t, item.ID)
126+
}
127+
// Clean end-of-stream: no error is reported until Close is called.
128+
require.NoError(t, iter.Err())
129+
// A clean Close returns nil...
130+
require.NoError(t, iter.Close(ctx))
131+
// ...but records a cancellation, surfaced by a later Err (gocb parity),
132+
// identically on the Rosmar and gocb-backed implementations.
133+
require.ErrorIs(t, iter.Err(), sgbucket.ErrScanCancelled)
134+
})
135+
117136
t.Run("PrefixScan", func(t *testing.T) {
118137
ids := collectScanIDs(t, ctx, scanStore, sgbucket.NewRangeScanForPrefix("doc_c"), sgbucket.ScanOptions{})
119138
require.Equal(t, []string{"doc_c"}, ids)

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ require (
1313
github.com/couchbase/gocb/v2 v2.12.4
1414
github.com/couchbase/gocbcore/v10 v10.9.3
1515
github.com/couchbase/gomemcached v0.2.1
16-
github.com/couchbase/sg-bucket v0.0.0-20260625135331-d9dd0c058146
16+
github.com/couchbase/sg-bucket v0.0.0-20260706130600-fc995d3ac4be
1717
github.com/couchbasedeps/fast-skiplist v0.0.0-20250722125747-e0dd031fe2ac
1818
github.com/couchbaselabs/go-fleecedelta v0.0.0-20220909152808-6d09efa7a338
1919
github.com/couchbaselabs/gocbconnstr v1.0.5
20-
github.com/couchbaselabs/rosmar v0.0.0-20260625145110-3e0b3eaeb1b1
20+
github.com/couchbaselabs/rosmar v0.0.0-20260706131717-11d9680fd14d
2121
github.com/elastic/gosigar v0.14.4
2222
github.com/felixge/fgprof v0.9.5
2323
github.com/go-jose/go-jose/v4 v4.1.4

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ github.com/couchbase/goprotostellar v1.0.6-0.20260407143512-d7af25156dcc h1:wQfv
4646
github.com/couchbase/goprotostellar v1.0.6-0.20260407143512-d7af25156dcc/go.mod h1:X58ot5FRqlBTBkwG/oI4klunpu4MApjGktheqeRWQw0=
4747
github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9BCs=
4848
github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
49-
github.com/couchbase/sg-bucket v0.0.0-20260625135331-d9dd0c058146 h1:CNwOVIG5a4efMAbAiwUpN+10cslVUcGQzxHgXHAKt08=
50-
github.com/couchbase/sg-bucket v0.0.0-20260625135331-d9dd0c058146/go.mod h1:njSdhrtd7aq+m6raxcu0JNfpwa6CnB58uhwvnrqzjjM=
49+
github.com/couchbase/sg-bucket v0.0.0-20260706130600-fc995d3ac4be h1:x6hodQB/wfuF8cXDlKl9JFb+jDvMVxrWCHqeEevn4/Y=
50+
github.com/couchbase/sg-bucket v0.0.0-20260706130600-fc995d3ac4be/go.mod h1:njSdhrtd7aq+m6raxcu0JNfpwa6CnB58uhwvnrqzjjM=
5151
github.com/couchbase/tools-common/cloud/v8 v8.1.3 h1:+fH2+3E8KV8xyXXzbEJNhosMqh08ahauZWlu0m5qtJA=
5252
github.com/couchbase/tools-common/cloud/v8 v8.1.3/go.mod h1:5wEMtM4rX92Zl9ylQKEJFgmYNUyFVoWzXCvo31YNO+U=
5353
github.com/couchbase/tools-common/fs v1.0.3 h1:KXhisN+hmp5yicOWkUBNjJcd/WsblHA2SVShjL2eGiY=
@@ -68,8 +68,8 @@ github.com/couchbaselabs/gocbconnstr v1.0.5 h1:e0JokB5qbcz7rfnxEhNRTKz8q1svoRvDo
6868
github.com/couchbaselabs/gocbconnstr v1.0.5/go.mod h1:KV3fnIKMi8/AzX0O9zOrO9rofEqrRF1d2rG7qqjxC7o=
6969
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0 h1:HU9DlAYYWR69jQnLN6cpg0fh0hxW/8d5hnglCXXjW78=
7070
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E=
71-
github.com/couchbaselabs/rosmar v0.0.0-20260625145110-3e0b3eaeb1b1 h1:d0YibxqBXLRzlxiiIrdsH5KpWmzb+5eb0nYmPuvqxzQ=
72-
github.com/couchbaselabs/rosmar v0.0.0-20260625145110-3e0b3eaeb1b1/go.mod h1:Opac5JFBaP0y2HKWtha2TV9ciBNnFUJ44SiGW11FuwQ=
71+
github.com/couchbaselabs/rosmar v0.0.0-20260706131717-11d9680fd14d h1:aO+wy8QnEj2XsLI5jk01zXvxSjVbOMbpWeLzTtWdkms=
72+
github.com/couchbaselabs/rosmar v0.0.0-20260706131717-11d9680fd14d/go.mod h1:9tmSd+tF1TDFlrlbUB8Q1XKxOoZyJu97wIQ5pTN6B20=
7373
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7474
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7575
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=

0 commit comments

Comments
 (0)