-
Notifications
You must be signed in to change notification settings - Fork 1.8k
refactor: unify means of applying ignoreUndefined
#2589
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
refactor: unify means of applying ignoreUndefined
#2589
Conversation
a3d7338
to
2c79f70
Compare
promoteValues: options.promoteValues ?? collection.s.promoteValues ?? true, | ||
promoteBuffers: options.promoteBuffers ?? collection.s.promoteBuffers ?? false, | ||
ignoreUndefined: options.ignoreUndefined ?? collection.s.ignoreUndefined ?? false | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic now occurs in CommandOperation
. Nothing else needs to change in this file; the execute
method here already includes this.bsonOptions
in its options.
cedf80c
to
38efad2
Compare
38efad2
to
59a0db7
Compare
59a0db7
to
72f6794
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a few nits and 🧅 s, nice work!
src/bson.ts
Outdated
|
||
// Merge the given BSONSerializeOptions, preferring options over parentOptions, and substituting | ||
// defaults for values not set. | ||
export function inheritOrDefaultBSONSerializableOptions( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comments above are useful, nice work you can go ahead and make it a docstyle comment
/**
* ...
* @internal
*/
Then it will be readable by just hovering it anywhere in the codebase, also you can make it internal.
Also fine with it as is but may I suggest the name: inheritBSONOptions
I think its fine to just state they will be defaulted in the docs.
src/operations/command.ts
Outdated
const base = Object.assign({}, parent?.s.options, parent?.s); | ||
Object.assign(this, inheritOrDefaultBSONSerializableOptions(options, base)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can inline the base here like so:
Object.assign(this, inheritOrDefaultBSONSerializableOptions(options, {...parent.s.options, ...parent?.s}));
src/operations/command.ts
Outdated
// Assign BSON serialize options to OperationBase, preferring options over parent options. | ||
// base accounts for the fact that Collection stores bson options in s, while other parents, | ||
// like Db, stores bson options in s.options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this is cause to change that? I wonder how much it would effect to make collection keep its bsonOptions in options, or have db spread them on to s
.
That could make this into a lot of work, so total throwaway comment if so, but curious if you have insight on what that would entail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it would have a strong effect at all to move the Collection bsonOptions
to s.options
-- as far as I can tell, very few places were using the Collection bsonOptions
on s
, and I deleted most of them in this PR. Would be happy to make this change.
As for spreading other parent bsonOptions
onto s
, Db
was just one example. I think we would also have to change cursor
too, and possibly others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would be nice for consistency (the first proposal)
I'm thinking now the original reason that collection might pick off its own copy of these options is to make sure that it doesn't edit its parent's options, I took a quick look around and it seems like collection does get its own separate options object but that could be a bug you run into if you give this a go, the options prop that collections uses shouldn't be the same reference to Db's options object.
src/operations/delete.ts
Outdated
const options = this.options; | ||
const options = Object.assign({}, this.options, this.bsonOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't comment on every instance to spare you the emails, but I think we should prefer the spread operator to assign: const options = {...this.options, ...this.bsonOptions}
. But not a strong opinion, and spread doesn't work in every case, like when assigning onto 'this
test/functional/insert.test.js
Outdated
// Add a tag that our runner can trigger on | ||
// in this case we are setting that node needs to be higher than 0.10.X to run |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment looks left over? there's nothing about 0.10.x here
7ce8201
to
8a75dc8
Compare
src/operations/bulk_write.ts
Outdated
@@ -24,18 +25,15 @@ export class BulkWriteOperation extends OperationBase<BulkWriteOptions, BulkWrit | |||
|
|||
this.collection = collection; | |||
this.operations = operations; | |||
|
|||
// Assign BSON serialize options to OperationBase, preferring options over collection options | |||
Object.assign(this, inheritBSONOptions(options, collection.s)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Total opinons here:
I'd love some consistency if possible. It seems that collection has it on .s
but bulk_write is on the class itself.
Also using Object.assign(this...
is super convenient but usually shy away from it to ensure types not being violated.
Here I'd prefer:
this.bsonOptions = inheritBSONOptions(options, collection.s));
or
const bsonOptions = inheritBSONOptions(options, collection.s));
this.raw = bsonOptions.raw;
this.promoteLongs = bsonOptions.promoteLongs;
this.promoteValues = bsonOptions.promoteValues;
this.promoteBuffers = bsonOptions.promoteBuffers;
this.ignoreUndefined = bsonOptions.ignoreUndefined;
this.serializeFunctions = bsonOptions.serializeFunctions;
this.fieldsAsRaw = bsonOptions.fieldsAsRaw;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your point about consistency is great, I definitely struggled with that for these changes.
It seems to me that for the Collection/Db/Client, the bson options must be somewhere in .s
, but classes extending OperationBase
don't (currently) have a .s
, so unless that changes there can't be perfect consistency...
Here is how I was thinking about it: Collection
, Db
, and MongoClient
could all store the bson options in s.options
, while anything extending OperationBase
has the bson options on the class itself. As for having a this.bsonOptions
property for OperationBase
instead of having all of the values directly on the class, I would be Ok with this! The rational for some of this seemed to come out of @nbbeeken's work-- wondering if he has any thoughts on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a few minor comments about cleaning up the new tests to our modern style, but this LGTM other than that, nice work!
metadata: { requires: { topology: ['single'] } }, | ||
|
||
test: function (done) { | ||
var configuration = this.configuration; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: while we don't go through and update old tests systematically, we generally try to change the old var
style of declaration to the modern const
/let
when adding new tests.
poolSize: 1, | ||
ignoreUndefined: false | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point you have a client, so you could use the withClient
helper here and avoid needing to connect or close the client, e.g. return withClient.call(this, client, (client, done) => { /* test */ });
|
||
it( | ||
'Should correctly inherit ignore undefined field from collection during insert', | ||
withClient(function (client, done) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the other tests in this file only run on single
topology via the metadata
; is there a reason this test and the subsequent ones don't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, there is no reason! I guess I didn't see exactly why that was needed, so I didn't include it. Will make this change in case there is something I am missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in some cases we limit very basic tests that don't depend on replica sets or sharded clusters to just run on the single
topology, because we have extra tests that we only run on replica/sharded, so those tasks take longer to complete in Evergreen. Limiting tests to single when possible is a way we can cut down on the wait time (and cost!) of running our tests. @mbroadst might be able to provide more background about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I just left a comment about this below. I think the rule of thumb should actually be that we run tests everywhere when possible, so we maximize our coverage. Let's tackle the runtime of the tests at a later date, we can likely make a number of improvements to speed things up while maintaining as much coverage as possible across multiple cluster types
|
||
// Locate the doument | ||
collection.findOne((err, item) => { | ||
test.equal(1, item.a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to using const
/let
for new tests, we also try to use the chai
style of expecations. Here it would be e.g. expect(item).to.have.property('a', 1)
;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
src/operations/bulk_write.ts
Outdated
@@ -24,18 +25,15 @@ export class BulkWriteOperation extends OperationBase<BulkWriteOptions, BulkWrit | |||
|
|||
this.collection = collection; | |||
this.operations = operations; | |||
|
|||
// Assign BSON serialize options to OperationBase, preferring options over collection options | |||
Object.assign(this, inheritBSONOptions(options, collection.s.options, true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should use Object.assign
on this
it does not give any information just by looking at this how this
is being manipulated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternative examples in #2589 (comment)
5b360ed
to
e436fba
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! I left one minor nit about test structure. 👏 great job on this work
@@ -148,4 +149,132 @@ describe('Ignore Undefined', function () { | |||
}); | |||
} | |||
}); | |||
|
|||
it('Should correctly inherit ignore undefined field from db during insert', { | |||
metadata: { requires: { topology: ['single'] } }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to only test against a standalone? If you want to test on any topology type/server version then you can omit the metadata
completely:
if('should correctly inherit ignoreUndefined from db during insert', function(done) {
...
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Description
What changed?
Previously, each operation in
collection.ts
needed to do the necessary logic to figure out whichBSONSerializationOptions
values to include in the options. Some operations omitted this logic completely, while others did it incorrectly. This change stores theBSONSerializationOptions
as a new inheritable property onMongoClient
,Db
,Collection
, andOperationBase
, and makes it accessible via a newbsonOptions
getter for each class.NODE-1561