Skip to content

Commit a34503b

Browse files
gaultritone
andauthored
feat(storage): allow specifying includeTrailingDelimiter (#5617)
References fsouza/fake-gcs-server#676. Co-authored-by: Chris Cotter <[email protected]>
1 parent 96c9d7e commit a34503b

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

storage/bucket.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,6 +1543,7 @@ func (it *ObjectIterator) fetch(pageSize int, pageToken string) (string, error)
15431543
req.StartOffset(it.query.StartOffset)
15441544
req.EndOffset(it.query.EndOffset)
15451545
req.Versions(it.query.Versions)
1546+
req.IncludeTrailingDelimiter(it.query.IncludeTrailingDelimiter)
15461547
if len(it.query.fieldSelection) > 0 {
15471548
req.Fields("nextPageToken", googleapi.Field(it.query.fieldSelection))
15481549
}

storage/integration_test.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,7 @@ func TestIntegration_Objects(t *testing.T) {
13411341
"obj1",
13421342
"obj2",
13431343
"obj/with/slashes",
1344+
"obj/",
13441345
}
13451346
contents := make(map[string][]byte)
13461347

@@ -1395,6 +1396,43 @@ func TestIntegration_Objects(t *testing.T) {
13951396
t.Errorf("prefixes = %v, want %v", gotPrefixes, sortedPrefixes)
13961397
}
13971398
})
1399+
t.Run("testObjectsIterateSelectedAttrsDelimiterIncludeTrailingDelimiter", func(t *testing.T) {
1400+
query := &Query{Prefix: "", Delimiter: "/", IncludeTrailingDelimiter: true}
1401+
if err := query.SetAttrSelection([]string{"Name"}); err != nil {
1402+
t.Fatalf("selecting query attrs: %v", err)
1403+
}
1404+
1405+
var gotNames []string
1406+
var gotPrefixes []string
1407+
it := bkt.Objects(context.Background(), query)
1408+
for {
1409+
attrs, err := it.Next()
1410+
if err == iterator.Done {
1411+
break
1412+
}
1413+
if err != nil {
1414+
t.Fatalf("iterator.Next: %v", err)
1415+
}
1416+
if attrs.Name != "" {
1417+
gotNames = append(gotNames, attrs.Name)
1418+
} else if attrs.Prefix != "" {
1419+
gotPrefixes = append(gotPrefixes, attrs.Prefix)
1420+
}
1421+
1422+
if attrs.Bucket != "" {
1423+
t.Errorf("Bucket field not selected, want empty, got = %v", attrs.Bucket)
1424+
}
1425+
}
1426+
1427+
sortedNames := []string{"obj/", "obj1", "obj2"}
1428+
if !cmp.Equal(sortedNames, gotNames) {
1429+
t.Errorf("names = %v, want %v", gotNames, sortedNames)
1430+
}
1431+
sortedPrefixes := []string{"obj/"}
1432+
if !cmp.Equal(sortedPrefixes, gotPrefixes) {
1433+
t.Errorf("prefixes = %v, want %v", gotPrefixes, sortedPrefixes)
1434+
}
1435+
})
13981436

13991437
// Test Reader.
14001438
for _, obj := range objects {
@@ -1872,7 +1910,7 @@ func testObjectsIterateAllSelectedAttrs(t *testing.T, bkt *BucketHandle, objects
18721910
// verifying the returned results).
18731911
query := &Query{
18741912
Prefix: "",
1875-
StartOffset: "obj/with/slashes",
1913+
StartOffset: "obj/",
18761914
EndOffset: "obj2",
18771915
}
18781916
var selectedAttrs []string

storage/storage.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,6 +1588,14 @@ type Query struct {
15881588
// which returns all properties. Passing ProjectionNoACL will omit Owner and ACL,
15891589
// which may improve performance when listing many objects.
15901590
Projection Projection
1591+
1592+
// IncludeTrailingDelimiter controls how objects which end in a single
1593+
// instance of Delimiter (for example, if Query.Delimiter = "/" and the
1594+
// object name is "foo/bar/") are included in the results. By default, these
1595+
// objects only show up as prefixes. If IncludeTrailingDelimiter is set to
1596+
// true, they will also be included as objects and their metadata will be
1597+
// populated in the returned ObjectAttrs.
1598+
IncludeTrailingDelimiter bool
15911599
}
15921600

15931601
// attrToFieldMap maps the field names of ObjectAttrs to the underlying field

0 commit comments

Comments
 (0)