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

GODRIVER-2698 Make Comment fields any-type on all options structs and setters #1454

Merged
merged 18 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
6 changes: 1 addition & 5 deletions internal/integration/unified/client_operation_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,7 @@ func executeCreateChangeStream(ctx context.Context, operation *operation) (*oper
}
opts.SetCollation(*collation)
case "comment":
commentString, err := createCommentString(val)
if err != nil {
return nil, fmt.Errorf("error creating comment: %v", err)
}
opts.SetComment(commentString)
opts.SetComment(val)
case "fullDocument":
switch fd := val.StringValue(); fd {
case "default":
Expand Down
24 changes: 3 additions & 21 deletions internal/integration/unified/collection_operation_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,7 @@ func executeAggregate(ctx context.Context, operation *operation) (*operationResu
}
opts.SetCollation(collation)
case "comment":
// TODO(GODRIVER-2386): when document support for comments is added, we can replace this switch condition
// TODO with `opts.SetComment(val)`
commentString, err := createCommentString(val)
if err != nil {
return nil, fmt.Errorf("error creating comment: %v", err)
}
opts.SetComment(commentString)
opts.SetComment(val)
case "hint":
hint, err := createHint(val)
if err != nil {
Expand Down Expand Up @@ -188,13 +182,7 @@ func executeCountDocuments(ctx context.Context, operation *operation) (*operatio
}
opts.SetCollation(collation)
case "comment":
// TODO(GODRIVER-2386): when document support for comments is added, we can replace this switch condition
// TODO with `opts.SetComment(val)`
commentString, err := createCommentString(val)
if err != nil {
return nil, fmt.Errorf("error creating comment: %v", err)
}
opts.SetComment(commentString)
opts.SetComment(val)
case "filter":
filter = val.Document()
case "hint":
Expand Down Expand Up @@ -1393,13 +1381,7 @@ func createFindCursor(ctx context.Context, operation *operation) (*cursorResult,
}
opts.SetCollation(collation)
case "comment":
// TODO(GODRIVER-2386): when document support for comments is added, we can replace this switch condition
// TODO with `opts.SetComment(val)`
commentString, err := createCommentString(val)
if err != nil {
return nil, fmt.Errorf("error creating comment: %v", err)
}
opts.SetComment(commentString)
opts.SetComment(val)
case "filter":
filter = val.Document()
case "hint":
Expand Down
11 changes: 0 additions & 11 deletions internal/integration/unified/crud_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,3 @@ func createHint(val bson.RawValue) (interface{}, error) {
}
return hint, nil
}

func createCommentString(val bson.RawValue) (string, error) {
switch val.Type {
case bsontype.String:
return val.StringValue(), nil
case bsontype.EmbeddedDocument:
return val.String(), nil
default:
return "", fmt.Errorf("unrecognized 'comment' value type: %T", val)
}
}
43 changes: 0 additions & 43 deletions internal/integration/unified/matches.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,6 @@ func verifyValuesMatchInner(ctx context.Context, expected, actual bson.RawValue)
return newMatchingError(fullKeyPath, "key not found in actual document")
}

// Check to see if the keypath requires us to convert actual/expected to make a true comparison. If the
// comparison is not supported for the keypath, continue with the recursive strategy.
//
// TODO(GODRIVER-2386): this branch of logic will be removed once we add document support for comments
mixedTypeEvaluated, err := evaluateMixedTypeComparison(expectedKey, expectedValue, actualValue)
if err != nil {
return newMatchingError(fullKeyPath, "error doing mixed-type matching assertion: %v", err)
}
if mixedTypeEvaluated {
continue
}

// Nested documents cannot have extra keys, so we unconditionally pass false for extraKeysAllowed.
comparisonCtx := makeMatchContext(ctx, fullKeyPath, false)
if err := verifyValuesMatchInner(comparisonCtx, expectedValue, actualValue); err != nil {
Expand Down Expand Up @@ -185,37 +173,6 @@ func verifyValuesMatchInner(ctx context.Context, expected, actual bson.RawValue)
return nil
}

// compareDocumentToString will compare an expected document to an actual string by converting the document into a
// string.
func compareDocumentToString(expected, actual bson.RawValue) error {
expectedDocument, ok := expected.DocumentOK()
if !ok {
return fmt.Errorf("expected value to be a document but got a %s", expected.Type)
}

actualString, ok := actual.StringValueOK()
if !ok {
return fmt.Errorf("expected value to be a string but got a %s", actual.Type)
}

if actualString != expectedDocument.String() {
return fmt.Errorf("expected value %s, got %s", expectedDocument.String(), actualString)
}
return nil
}

// evaluateMixedTypeComparison compares an expected document with an actual string. If this comparison occurs, then
// the function will return `true` along with any resulting error.
func evaluateMixedTypeComparison(expectedKey string, expected, actual bson.RawValue) (bool, error) {
switch expectedKey {
case "comment":
if expected.Type == bsontype.EmbeddedDocument && actual.Type == bsontype.String {
return true, compareDocumentToString(expected, actual)
}
}
return false, nil
}

func evaluateSpecialComparison(ctx context.Context, assertionDoc bson.Raw, actual bson.RawValue) error {
assertionElem := assertionDoc.Index(0)
assertion := assertionElem.Key()
Expand Down
10 changes: 5 additions & 5 deletions mongo/change_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,14 @@ func newChangeStream(ctx context.Context, config changeStreamConfig, pipeline in
if cs.options.Collation != nil {
cs.aggregate.Collation(bsoncore.Document(cs.options.Collation.ToDocument()))
}
if comment := cs.options.Comment; comment != nil {
cs.aggregate.Comment(*comment)

commentVal, err := marshalValue(comment, cs.bsonOpts, cs.registry)
if cs.options.Comment != nil {
comment, err := marshalValue(cs.options.Comment, cs.bsonOpts, cs.registry)
if err != nil {
return nil, err
}
cs.cursorOptions.Comment = commentVal

cs.aggregate.Comment(comment)
cs.cursorOptions.Comment = comment
}
if cs.options.BatchSize != nil {
cs.aggregate.BatchSize(*cs.options.BatchSize)
Expand Down
67 changes: 39 additions & 28 deletions mongo/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -1046,13 +1046,13 @@ func aggregate(a aggregateParams) (cur *Cursor, err error) {
cursorOpts.MaxTimeMS = int64(*ao.MaxAwaitTime / time.Millisecond)
}
if ao.Comment != nil {
op.Comment(*ao.Comment)

commentVal, err := marshalValue(ao.Comment, a.bsonOpts, a.registry)
comment, err := marshalValue(ao.Comment, a.bsonOpts, a.registry)
if err != nil {
return nil, err
}
cursorOpts.Comment = commentVal

op.Comment(comment)
cursorOpts.Comment = comment
}
if ao.Hint != nil {
if isUnorderedMap(ao.Hint) {
Expand Down Expand Up @@ -1176,7 +1176,12 @@ func (coll *Collection) CountDocuments(ctx context.Context, filter interface{},
op.Collation(bsoncore.Document(countOpts.Collation.ToDocument()))
}
if countOpts.Comment != nil {
op.Comment(*countOpts.Comment)
comment, err := marshalValue(countOpts.Comment, coll.bsonOpts, coll.registry)
if err != nil {
return 0, err
}

op.Comment(comment)
}
if countOpts.Hint != nil {
if isUnorderedMap(countOpts.Hint) {
Expand Down Expand Up @@ -1531,13 +1536,13 @@ func (coll *Collection) Find(ctx context.Context, filter interface{},
op.Collation(bsoncore.Document(fo.Collation.ToDocument()))
}
if fo.Comment != nil {
op.Comment(*fo.Comment)

commentVal, err := marshalValue(fo.Comment, coll.bsonOpts, coll.registry)
comment, err := marshalValue(fo.Comment, coll.bsonOpts, coll.registry)
if err != nil {
return nil, err
}
cursorOpts.Comment = commentVal

op.Comment(comment)
cursorOpts.Comment = comment
}
if fo.CursorType != nil {
switch *fo.CursorType {
Expand Down Expand Up @@ -1637,27 +1642,13 @@ func (coll *Collection) Find(ctx context.Context, filter interface{},
return newCursorWithSession(bc, coll.bsonOpts, coll.registry, sess)
}

// FindOne executes a find command and returns a SingleResult for one document in the collection.
//
// The filter parameter must be a document containing query operators and can be used to select the document to be
// returned. It cannot be nil. If the filter does not match any documents, a SingleResult with an error set to
// ErrNoDocuments will be returned. If the filter matches multiple documents, one will be selected from the matched set.
//
// The opts parameter can be used to specify options for this operation (see the options.FindOneOptions documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/find/.
func (coll *Collection) FindOne(ctx context.Context, filter interface{},
opts ...*options.FindOneOptions) *SingleResult {

if ctx == nil {
ctx = context.Background()
}

func newFindOptionsFromFindOneOptions(opts ...*options.FindOneOptions) []*options.FindOptions {
findOpts := make([]*options.FindOptions, 0, len(opts))
for _, opt := range opts {
if opt == nil {
continue
}

findOpts = append(findOpts, &options.FindOptions{
AllowPartialResults: opt.AllowPartialResults,
Collation: opt.Collation,
Expand All @@ -1673,11 +1664,31 @@ func (coll *Collection) FindOne(ctx context.Context, filter interface{},
Sort: opt.Sort,
})
}
// Unconditionally send a limit to make sure only one document is returned and the cursor is not kept open
// by the server.

// Unconditionally send a limit to make sure only one document is returned and
// the cursor is not kept open by the server.
findOpts = append(findOpts, options.Find().SetLimit(-1))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: The explicit indexing is somewhat confusing at first and seems brittle. Is there a reason to do that vs using append?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The append method causes the following linting error:

mongo/collection.go:1667:14: append to slice `findOpts` with non-zero initialized length (makezero)
                findOpts = append(findOpts, &options.FindOptions{
                           ^
mongo/collection.go:1685:13: append to slice `findOpts` with non-zero initialized length (makezero)
        findOpts = append(findOpts, options.Find().SetLimit(-1))

Copy link
Collaborator Author

@prestonvasquez prestonvasquez Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matthewdale Looking at this more closely, I believe that his solution has a pretty insidious bug. Namely if a user passes options like this to the FindOne function: [op1, nil, op2], you would get a runtime fatal. I've updated the PR with a test for this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find!


cursor, err := coll.Find(ctx, filter, findOpts...)
return findOpts
}

// FindOne executes a find command and returns a SingleResult for one document in the collection.
//
// The filter parameter must be a document containing query operators and can be used to select the document to be
// returned. It cannot be nil. If the filter does not match any documents, a SingleResult with an error set to
// ErrNoDocuments will be returned. If the filter matches multiple documents, one will be selected from the matched set.
//
// The opts parameter can be used to specify options for this operation (see the options.FindOneOptions documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/find/.
func (coll *Collection) FindOne(ctx context.Context, filter interface{},
opts ...*options.FindOneOptions) *SingleResult {

if ctx == nil {
ctx = context.Background()
}

cursor, err := coll.Find(ctx, filter, newFindOptionsFromFindOneOptions(opts...)...)
return &SingleResult{
ctx: ctx,
cur: cursor,
Expand Down
97 changes: 97 additions & 0 deletions mongo/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,100 @@ func TestCollection(t *testing.T) {
assert.Equal(t, aggErr, err, "expected error %v, got %v", aggErr, err)
})
}

func TestNewFindOptionsFromFindOneOptions(t *testing.T) {
t.Parallel()

tests := []struct {
name string
opts []*options.FindOneOptions
want []*options.FindOptions
}{
{
name: "nil",
opts: nil,
want: []*options.FindOptions{
options.Find().SetLimit(-1),
},
},
{
name: "empty",
opts: []*options.FindOneOptions{},
want: []*options.FindOptions{
options.Find().SetLimit(-1),
},
},
{
name: "singleton",
opts: []*options.FindOneOptions{
options.FindOne().SetSkip(1),
},
want: []*options.FindOptions{
options.Find().SetSkip(1),
options.Find().SetLimit(-1),
},
},
{
name: "multiplicity",
opts: []*options.FindOneOptions{
options.FindOne().SetSkip(1),
options.FindOne().SetSkip(2),
},
want: []*options.FindOptions{
options.Find().SetSkip(1),
options.Find().SetSkip(2),
options.Find().SetLimit(-1),
},
},
{
name: "interior null",
opts: []*options.FindOneOptions{
options.FindOne().SetSkip(1),
nil,
options.FindOne().SetSkip(2),
},
want: []*options.FindOptions{
options.Find().SetSkip(1),
options.Find().SetSkip(2),
options.Find().SetLimit(-1),
},
},
{
name: "start null",
opts: []*options.FindOneOptions{
nil,
options.FindOne().SetSkip(1),
options.FindOne().SetSkip(2),
},
want: []*options.FindOptions{
options.Find().SetSkip(1),
options.Find().SetSkip(2),
options.Find().SetLimit(-1),
},
},
{
name: "end null",
opts: []*options.FindOneOptions{
options.FindOne().SetSkip(1),
options.FindOne().SetSkip(2),
nil,
},
want: []*options.FindOptions{
options.Find().SetSkip(1),
options.Find().SetSkip(2),
options.Find().SetLimit(-1),
},
},
}

for _, test := range tests {
test := test // Capture the range variable

t.Run(test.name, func(t *testing.T) {
t.Parallel()

got := newFindOptionsFromFindOneOptions(test.opts...)
assert.Equal(t, test.want, got)
})
}
}
11 changes: 6 additions & 5 deletions mongo/options/aggregateoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ type AggregateOptions struct {
// This option is only valid for MongoDB versions >= 3.2 and is ignored for previous server versions.
MaxAwaitTime *time.Duration

// A string that will be included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default is nil, which means that no comment will be included in the logs.
Comment *string
// A string or document that will be included in server logs, profiling logs,
// and currentOp queries to help trace the operation. The default is nil,
// which means that no comment will be included in the logs.
Comment interface{}

// The index to use for the aggregation. This should either be the index name as a string or the index specification
// as a document. The hint does not apply to $lookup and $graphLookup aggregation stages. The driver will return an
Expand Down Expand Up @@ -111,8 +112,8 @@ func (ao *AggregateOptions) SetMaxAwaitTime(d time.Duration) *AggregateOptions {
}

// SetComment sets the value for the Comment field.
func (ao *AggregateOptions) SetComment(s string) *AggregateOptions {
ao.Comment = &s
func (ao *AggregateOptions) SetComment(comment interface{}) *AggregateOptions {
ao.Comment = comment
return ao
}

Expand Down
Loading
Loading