satellite/metabase: fix listing prefixes with cursor set
We were not skipping the initial prefix from the cursor. Change-Id: I2bb472e960b92cae77fd1226de0b26fac79c429b
This commit is contained in:
parent
8f4505f532
commit
2e9d3a737c
@ -28,10 +28,9 @@ type objectsIterator struct {
|
||||
|
||||
curIndex int
|
||||
curRows tagsql.Rows
|
||||
cursor iterateCursor
|
||||
inclusiveCursor bool
|
||||
cursor iterateCursor // not relative to prefix
|
||||
|
||||
skipPrefix ObjectKey
|
||||
skipPrefix ObjectKey // relative to prefix
|
||||
doNextQuery func(context.Context, *objectsIterator) (_ tagsql.Rows, err error)
|
||||
|
||||
// failErr is set when either scan or next query fails during iteration.
|
||||
@ -42,6 +41,7 @@ type iterateCursor struct {
|
||||
Key ObjectKey
|
||||
Version Version
|
||||
StreamID uuid.UUID
|
||||
Inclusive bool
|
||||
}
|
||||
|
||||
func iterateAllVersions(ctx context.Context, db *DB, opts IterateObjects, fn func(context.Context, ObjectsIterator) error) (err error) {
|
||||
@ -56,13 +56,9 @@ func iterateAllVersions(ctx context.Context, db *DB, opts IterateObjects, fn fun
|
||||
prefixLimit: prefixLimit(opts.Prefix),
|
||||
batchSize: opts.BatchSize,
|
||||
recursive: true,
|
||||
inclusiveCursor: false,
|
||||
|
||||
curIndex: 0,
|
||||
cursor: iterateCursor{
|
||||
Key: opts.Cursor.Key,
|
||||
Version: opts.Cursor.Version,
|
||||
},
|
||||
cursor: firstIterateCursor(true, opts.Cursor, opts.Prefix),
|
||||
doNextQuery: doNextQueryAllVersionsWithoutStatus,
|
||||
}
|
||||
|
||||
@ -70,7 +66,7 @@ func iterateAllVersions(ctx context.Context, db *DB, opts IterateObjects, fn fun
|
||||
if lessKey(it.cursor.Key, opts.Prefix) {
|
||||
it.cursor.Key = opts.Prefix
|
||||
it.cursor.Version = -1
|
||||
it.inclusiveCursor = true
|
||||
it.cursor.Inclusive = true
|
||||
}
|
||||
|
||||
return iterate(ctx, it, fn)
|
||||
@ -91,10 +87,8 @@ func iterateAllVersionsWithStatus(ctx context.Context, db *DB, opts IterateObjec
|
||||
recursive: opts.Recursive,
|
||||
|
||||
curIndex: 0,
|
||||
cursor: iterateCursor{
|
||||
Key: opts.Cursor.Key,
|
||||
Version: opts.Cursor.Version,
|
||||
},
|
||||
cursor: firstIterateCursor(opts.Recursive, opts.Cursor, opts.Prefix),
|
||||
|
||||
doNextQuery: doNextQueryAllVersionsWithStatus,
|
||||
}
|
||||
|
||||
@ -102,7 +96,7 @@ func iterateAllVersionsWithStatus(ctx context.Context, db *DB, opts IterateObjec
|
||||
if lessKey(it.cursor.Key, opts.Prefix) {
|
||||
it.cursor.Key = opts.Prefix
|
||||
it.cursor.Version = -1
|
||||
it.inclusiveCursor = true
|
||||
it.cursor.Inclusive = true
|
||||
}
|
||||
|
||||
return iterate(ctx, it, fn)
|
||||
@ -150,7 +144,7 @@ func iterate(ctx context.Context, it *objectsIterator, fn func(context.Context,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it.inclusiveCursor = false
|
||||
it.cursor.Inclusive = false
|
||||
|
||||
defer func() {
|
||||
if rowsErr := it.curRows.Err(); rowsErr != nil {
|
||||
@ -256,7 +250,7 @@ func doNextQueryAllVersionsWithoutStatus(ctx context.Context, it *objectsIterato
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
cursorCompare := ">"
|
||||
if it.inclusiveCursor {
|
||||
if it.cursor.Inclusive {
|
||||
cursorCompare = ">="
|
||||
}
|
||||
|
||||
@ -311,7 +305,7 @@ func doNextQueryAllVersionsWithStatus(ctx context.Context, it *objectsIterator)
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
cursorCompare := ">"
|
||||
if it.inclusiveCursor {
|
||||
if it.cursor.Inclusive {
|
||||
cursorCompare = ">="
|
||||
}
|
||||
|
||||
@ -433,3 +427,46 @@ func prefixLimit(a ObjectKey) ObjectKey {
|
||||
func lessKey(a, b ObjectKey) bool {
|
||||
return bytes.Compare([]byte(a), []byte(b)) < 0
|
||||
}
|
||||
|
||||
// firstIterateCursor adjust the cursor for a non-recursive iteration.
|
||||
// The cursor is non-inclusive and we need to adjust to handle prefix as cursor properly.
|
||||
// We return the next possible key from the prefix.
|
||||
func firstIterateCursor(recursive bool, cursor IterateCursor, prefix ObjectKey) iterateCursor {
|
||||
if recursive {
|
||||
return iterateCursor{
|
||||
Key: cursor.Key,
|
||||
Version: cursor.Version,
|
||||
}
|
||||
}
|
||||
|
||||
// when the cursor does not match the prefix, we'll return the original cursor.
|
||||
if !strings.HasPrefix(string(cursor.Key), string(prefix)) {
|
||||
return iterateCursor{
|
||||
Key: cursor.Key,
|
||||
Version: cursor.Version,
|
||||
}
|
||||
}
|
||||
|
||||
// handle case where:
|
||||
// prefix: x/y/
|
||||
// cursor: x/y/z/w
|
||||
// In this case, we want the skip prefix to be `x/y/z` + string('/' + 1).
|
||||
|
||||
cursorWithoutPrefix := cursor.Key[len(prefix):]
|
||||
p := strings.IndexByte(string(cursorWithoutPrefix), Delimiter)
|
||||
if p < 0 {
|
||||
// The cursor is not a prefix, but instead a path inside the prefix,
|
||||
// so we can use it directly.
|
||||
return iterateCursor{
|
||||
Key: cursor.Key,
|
||||
Version: cursor.Version,
|
||||
}
|
||||
}
|
||||
|
||||
// return the next prefix given a scoped path
|
||||
return iterateCursor{
|
||||
Key: cursor.Key[:len(prefix)+p] + ObjectKey(Delimiter+1),
|
||||
Version: -1,
|
||||
Inclusive: true,
|
||||
}
|
||||
}
|
||||
|
@ -256,6 +256,45 @@ func TestIterateObjects(t *testing.T) {
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjects{
|
||||
Opts: metabase.IterateObjects{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
|
||||
Cursor: metabase.IterateCursor{Key: "a", Version: 0},
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
objects["a"],
|
||||
objects["b/1"],
|
||||
objects["b/2"],
|
||||
objects["b/3"],
|
||||
objects["c"],
|
||||
objects["c/"],
|
||||
objects["c//"],
|
||||
objects["c/1"],
|
||||
objects["g"],
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjects{
|
||||
Opts: metabase.IterateObjects{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
|
||||
Cursor: metabase.IterateCursor{Key: "a", Version: 1},
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
objects["b/1"],
|
||||
objects["b/2"],
|
||||
objects["b/3"],
|
||||
objects["c"],
|
||||
objects["c/"],
|
||||
objects["c//"],
|
||||
objects["c/1"],
|
||||
objects["g"],
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjects{
|
||||
Opts: metabase.IterateObjects{
|
||||
ProjectID: projectID,
|
||||
@ -628,12 +667,16 @@ func TestIterateObjectsWithStatus(t *testing.T) {
|
||||
t.Run("objects in one bucket in project with 2 buckets", func(t *testing.T) {
|
||||
defer metabasetest.DeleteAll{}.Check(ctx, t, db)
|
||||
numberOfObjectsPerBucket := 5
|
||||
|
||||
expected := make([]metabase.ObjectEntry, numberOfObjectsPerBucket)
|
||||
|
||||
objectsBucketA := createObjects(ctx, t, db, numberOfObjectsPerBucket, uuid.UUID{1}, "bucket-a")
|
||||
objectsBucketB := createObjects(ctx, t, db, numberOfObjectsPerBucket, uuid.UUID{1}, "bucket-b")
|
||||
|
||||
for i, obj := range objectsBucketA {
|
||||
expected[i] = objectEntryFromRaw(obj)
|
||||
}
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: uuid.UUID{1},
|
||||
@ -643,18 +686,24 @@ func TestIterateObjectsWithStatus(t *testing.T) {
|
||||
},
|
||||
Result: expected,
|
||||
}.Check(ctx, t, db)
|
||||
metabasetest.Verify{Objects: append(objectsBucketA, objectsBucketB...)}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.Verify{
|
||||
Objects: append(objectsBucketA, objectsBucketB...),
|
||||
}.Check(ctx, t, db)
|
||||
})
|
||||
|
||||
t.Run("objects in one bucket with same bucketName in another project", func(t *testing.T) {
|
||||
defer metabasetest.DeleteAll{}.Check(ctx, t, db)
|
||||
numberOfObjectsPerBucket := 5
|
||||
|
||||
expected := make([]metabase.ObjectEntry, numberOfObjectsPerBucket)
|
||||
|
||||
objectsProject1 := createObjects(ctx, t, db, numberOfObjectsPerBucket, uuid.UUID{1}, "mybucket")
|
||||
objectsProject2 := createObjects(ctx, t, db, numberOfObjectsPerBucket, uuid.UUID{2}, "mybucket")
|
||||
for i, obj := range objectsProject1 {
|
||||
expected[i] = objectEntryFromRaw(obj)
|
||||
}
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: uuid.UUID{1},
|
||||
@ -664,7 +713,10 @@ func TestIterateObjectsWithStatus(t *testing.T) {
|
||||
},
|
||||
Result: expected,
|
||||
}.Check(ctx, t, db)
|
||||
metabasetest.Verify{Objects: append(objectsProject1, objectsProject2...)}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.Verify{
|
||||
Objects: append(objectsProject1, objectsProject2...),
|
||||
}.Check(ctx, t, db)
|
||||
})
|
||||
|
||||
t.Run("recursive", func(t *testing.T) {
|
||||
@ -1052,6 +1104,228 @@ func TestIterateObjectsWithStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIterateObjectsSkipCursor(t *testing.T) {
|
||||
metabasetest.Run(t, func(ctx *testcontext.Context, t *testing.T, db *metabase.DB) {
|
||||
projectID, bucketName := uuid.UUID{1}, "bucky"
|
||||
|
||||
t.Run("no prefix", func(t *testing.T) {
|
||||
defer metabasetest.DeleteAll{}.Check(ctx, t, db)
|
||||
|
||||
createObjectsWithKeys(ctx, t, db, projectID, bucketName, []metabase.ObjectKey{
|
||||
"08/test",
|
||||
"09/test",
|
||||
"10/test",
|
||||
})
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
Prefix: "",
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("08/"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
Prefix: "",
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("08"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
prefixEntry(metabase.ObjectKey("08/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
Prefix: "",
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("08/a/x"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
})
|
||||
|
||||
t.Run("prefix", func(t *testing.T) {
|
||||
defer metabasetest.DeleteAll{}.Check(ctx, t, db)
|
||||
|
||||
createObjectsWithKeys(ctx, t, db, projectID, bucketName, []metabase.ObjectKey{
|
||||
"2017/05/08/test",
|
||||
"2017/05/09/test",
|
||||
"2017/05/10/test",
|
||||
})
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
Prefix: metabase.ObjectKey("2017/05/"),
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("2017/05/08"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
prefixEntry(metabase.ObjectKey("08/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
Prefix: metabase.ObjectKey("2017/05/"),
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("2017/05/08/"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
Prefix: metabase.ObjectKey("2017/05/"),
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("2017/05/08/a/x"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
})
|
||||
|
||||
t.Run("batch-size", func(t *testing.T) {
|
||||
defer metabasetest.DeleteAll{}.Check(ctx, t, db)
|
||||
|
||||
afterDelimiter := metabase.ObjectKey(metabase.Delimiter + 1)
|
||||
|
||||
objects := createObjectsWithKeys(ctx, t, db, projectID, bucketName, []metabase.ObjectKey{
|
||||
"2017/05/08",
|
||||
"2017/05/08/a",
|
||||
"2017/05/08/b",
|
||||
"2017/05/08/c",
|
||||
"2017/05/08/d",
|
||||
"2017/05/08/e",
|
||||
"2017/05/08" + afterDelimiter,
|
||||
"2017/05/09/a",
|
||||
"2017/05/09/b",
|
||||
"2017/05/09/c",
|
||||
"2017/05/09/d",
|
||||
"2017/05/09/e",
|
||||
"2017/05/10/a",
|
||||
"2017/05/10/b",
|
||||
"2017/05/10/c",
|
||||
"2017/05/10/d",
|
||||
"2017/05/10/e",
|
||||
})
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
BatchSize: 3,
|
||||
Prefix: metabase.ObjectKey("2017/05/"),
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("2017/05/08"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
prefixEntry(metabase.ObjectKey("08/"), metabase.Committed),
|
||||
withoutPrefix1("2017/05/", objects["2017/05/08"+afterDelimiter]),
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
BatchSize: 3,
|
||||
Prefix: metabase.ObjectKey("2017/05/"),
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("2017/05/08/"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
withoutPrefix1("2017/05/", objects["2017/05/08"+afterDelimiter]),
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
|
||||
metabasetest.IterateObjectsWithStatus{
|
||||
Opts: metabase.IterateObjectsWithStatus{
|
||||
ProjectID: projectID,
|
||||
BucketName: bucketName,
|
||||
Recursive: false,
|
||||
Prefix: metabase.ObjectKey("2017/05/"),
|
||||
Cursor: metabase.IterateCursor{
|
||||
Key: metabase.ObjectKey("2017/05/08/a/x"),
|
||||
Version: 1,
|
||||
},
|
||||
Status: metabase.Committed,
|
||||
},
|
||||
Result: []metabase.ObjectEntry{
|
||||
withoutPrefix1("2017/05/", objects["2017/05/08"+afterDelimiter]),
|
||||
prefixEntry(metabase.ObjectKey("09/"), metabase.Committed),
|
||||
prefixEntry(metabase.ObjectKey("10/"), metabase.Committed),
|
||||
},
|
||||
}.Check(ctx, t, db)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIteratePendingObjectsWithObjectKey(t *testing.T) {
|
||||
metabasetest.Run(t, func(ctx *testcontext.Context, t *testing.T, db *metabase.DB) {
|
||||
obj := metabasetest.RandObjectStream()
|
||||
@ -1299,6 +1573,11 @@ func withoutPrefix(prefix metabase.ObjectKey, entries ...metabase.ObjectEntry) [
|
||||
return xs
|
||||
}
|
||||
|
||||
func withoutPrefix1(prefix metabase.ObjectKey, entry metabase.ObjectEntry) metabase.ObjectEntry {
|
||||
entry.ObjectKey = entry.ObjectKey[len(prefix):]
|
||||
return entry
|
||||
}
|
||||
|
||||
func prefixEntry(key metabase.ObjectKey, status metabase.ObjectStatus) metabase.ObjectEntry {
|
||||
return metabase.ObjectEntry{
|
||||
IsPrefix: true,
|
||||
|
@ -6,6 +6,7 @@ package metabase
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -27,3 +28,31 @@ func TestPrefixLimit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstIterateCursor(t *testing.T) {
|
||||
afterDelimiter := ObjectKey('/' + 1)
|
||||
|
||||
assert.Equal(t,
|
||||
iterateCursor{Key: "a"},
|
||||
firstIterateCursor(false, IterateCursor{Key: "a"}, ""))
|
||||
|
||||
assert.Equal(t,
|
||||
iterateCursor{Key: "a" + afterDelimiter, Version: -1, Inclusive: true},
|
||||
firstIterateCursor(false, IterateCursor{Key: "a/"}, ""))
|
||||
|
||||
assert.Equal(t,
|
||||
iterateCursor{Key: "a" + afterDelimiter, Version: -1, Inclusive: true},
|
||||
firstIterateCursor(false, IterateCursor{Key: "a/x/y"}, ""))
|
||||
|
||||
assert.Equal(t,
|
||||
iterateCursor{Key: "a/x/y"},
|
||||
firstIterateCursor(false, IterateCursor{Key: "a/x/y"}, "a/x/"))
|
||||
|
||||
assert.Equal(t,
|
||||
iterateCursor{Key: "2017/05/08" + afterDelimiter, Version: -1, Inclusive: true},
|
||||
firstIterateCursor(false, IterateCursor{Key: "2017/05/08/"}, "2017/05/"))
|
||||
|
||||
assert.Equal(t,
|
||||
iterateCursor{Key: "2017/05/08" + afterDelimiter, Version: -1, Inclusive: true},
|
||||
firstIterateCursor(false, IterateCursor{Key: "2017/05/08/x/y"}, "2017/05/"))
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ type ObjectsIterator interface {
|
||||
}
|
||||
|
||||
// IterateCursor is a cursor used during iteration through objects.
|
||||
//
|
||||
// The cursor is exclusive.
|
||||
type IterateCursor struct {
|
||||
Key ObjectKey
|
||||
Version Version
|
||||
|
Loading…
Reference in New Issue
Block a user