Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(storage): add support for MatchGlob #8097

Merged
merged 4 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions storage/grpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,11 @@ func (c *grpcStorageClient) ListObjects(ctx context.Context, bucket string, q *Q
}
gitr := c.raw.ListObjects(it.ctx, req, s.gax...)
fetch := func(pageSize int, pageToken string) (token string, err error) {
// MatchGlob not yet supported for gRPC.
// TODO: add support when b/287306063 resolved.
if q != nil && q.MatchGlob != "" {
return "", status.Errorf(codes.Unimplemented, "MatchGlob is not supported for gRPC")
}
var objects []*storagepb.Object
err = run(it.ctx, func() error {
objects, token, err = gitr.InternalFetch(pageSize, pageToken)
Expand Down
1 change: 1 addition & 0 deletions storage/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ func (c *httpStorageClient) ListObjects(ctx context.Context, bucket string, q *Q
req.EndOffset(it.query.EndOffset)
req.Versions(it.query.Versions)
req.IncludeTrailingDelimiter(it.query.IncludeTrailingDelimiter)
req.MatchGlob(it.query.MatchGlob)
if selection := it.query.toFieldSelection(); selection != "" {
req.Fields("nextPageToken", googleapi.Field(selection))
}
Expand Down
62 changes: 62 additions & 0 deletions storage/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,68 @@ func TestIntegration_ObjectIteration(t *testing.T) {
})
}

func TestIntegration_ObjectIterationMatchGlob(t *testing.T) {
// This is a separate test from the Object Iteration test above because
// MatchGlob is not yet implemented for gRPC.
ctx := skipGRPC("https://github.com/googleapis/google-cloud-go/issues/7727")
multiTransportTest(skipJSONReads(ctx, "no reads in test"), t, func(t *testing.T, ctx context.Context, _ string, prefix string, client *Client) {
// Reset testTime, 'cause object last modification time should be within 5 min
// from test (test iteration if -count passed) start time.
testTime = time.Now().UTC()
newBucketName := prefix + uidSpace.New()
h := testHelper{t}
bkt := client.Bucket(newBucketName).Retryer(WithPolicy(RetryAlways))

h.mustCreate(bkt, testutil.ProjID(), nil)
defer func() {
if err := killBucket(ctx, client, newBucketName); err != nil {
log.Printf("deleting %q: %v", newBucketName, err)
}
}()
const defaultType = "text/plain"

// Populate object names and make a map for their contents.
objects := []string{
"obj1",
"obj2",
"obj/with/slashes",
"obj/",
"other/obj1",
}
contents := make(map[string][]byte)

// Test Writer.
for _, obj := range objects {
c := randomContents()
if err := writeObject(ctx, bkt.Object(obj), defaultType, c); err != nil {
t.Errorf("Write for %v failed with %v", obj, err)
}
contents[obj] = c
}
query := &Query{MatchGlob: "**obj1"}

var gotNames []string
it := bkt.Objects(context.Background(), query)
for {
attrs, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
t.Fatalf("iterator.Next: %v", err)
}
if attrs.Name != "" {
gotNames = append(gotNames, attrs.Name)
}
}

sortedNames := []string{"obj1", "other/obj1"}
if !cmp.Equal(sortedNames, gotNames) {
t.Errorf("names = %v, want %v", gotNames, sortedNames)
}
})
}

func TestIntegration_ObjectUpdate(t *testing.T) {
ctx := skipJSONReads(context.Background(), "no reads in test")
multiTransportTest(ctx, t, func(t *testing.T, ctx context.Context, bucket string, _ string, client *Client) {
Expand Down
12 changes: 10 additions & 2 deletions storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,8 @@ type Query struct {
// aside from the prefix, contain delimiter will have their name,
// truncated after the delimiter, returned in prefixes.
// Duplicate prefixes are omitted.
// Must be set to / when used with the MatchGlob parameter to filter results
// in a directory-like mode.
// Optional.
Delimiter string

Expand All @@ -1499,9 +1501,9 @@ type Query struct {
Versions bool

// attrSelection is used to select only specific fields to be returned by
// the query. It is set by the user calling calling SetAttrSelection. These
// the query. It is set by the user calling SetAttrSelection. These
// are used by toFieldMask and toFieldSelection for gRPC and HTTP/JSON
// clients repsectively.
// clients respectively.
attrSelection []string

// StartOffset is used to filter results to objects whose names are
Expand All @@ -1527,6 +1529,12 @@ type Query struct {
// true, they will also be included as objects and their metadata will be
// populated in the returned ObjectAttrs.
IncludeTrailingDelimiter bool

// MatchGlob is a glob pattern used to filter results (for example, foo*bar). See
// https://cloud.google.com/storage/docs/json_api/v1/objects/list#list-object-glob
// for syntax details. When Delimiter is set in conjunction with MatchGlob,
// it must be set to /.
MatchGlob string
}

// attrToFieldMap maps the field names of ObjectAttrs to the underlying field
Expand Down