Skip to content

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

Merged

Conversation

HanaPearlman
Copy link
Contributor

@HanaPearlman HanaPearlman commented Oct 19, 2020

Description

What changed?
Previously, each operation in collection.ts needed to do the necessary logic to figure out which BSONSerializationOptions values to include in the options. Some operations omitted this logic completely, while others did it incorrectly. This change stores the BSONSerializationOptions as a new inheritable property on MongoClient, Db, Collection, and OperationBase, and makes it accessible via a new bsonOptions getter for each class.

NODE-1561

@HanaPearlman HanaPearlman force-pushed the NODE-1561/master/unify-ignoreUndefined branch from a3d7338 to 2c79f70 Compare October 19, 2020 16:54
promoteValues: options.promoteValues ?? collection.s.promoteValues ?? true,
promoteBuffers: options.promoteBuffers ?? collection.s.promoteBuffers ?? false,
ignoreUndefined: options.ignoreUndefined ?? collection.s.ignoreUndefined ?? false
};
}
Copy link
Contributor Author

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.

@HanaPearlman HanaPearlman requested review from mbroadst, nbbeeken, emadum and reggi and removed request for mbroadst and nbbeeken October 19, 2020 19:35
@HanaPearlman HanaPearlman marked this pull request as ready for review October 19, 2020 19:36
@HanaPearlman HanaPearlman force-pushed the NODE-1561/master/unify-ignoreUndefined branch 2 times, most recently from cedf80c to 38efad2 Compare October 19, 2020 20:48
@HanaPearlman HanaPearlman force-pushed the NODE-1561/master/unify-ignoreUndefined branch from 38efad2 to 59a0db7 Compare October 20, 2020 13:21
@HanaPearlman HanaPearlman force-pushed the NODE-1561/master/unify-ignoreUndefined branch from 59a0db7 to 72f6794 Compare October 20, 2020 13:35
Copy link
Contributor

@nbbeeken nbbeeken left a 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(
Copy link
Contributor

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.

Comment on lines 106 to 107
const base = Object.assign({}, parent?.s.options, parent?.s);
Object.assign(this, inheritOrDefaultBSONSerializableOptions(options, base));
Copy link
Contributor

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}));

Comment on lines 103 to 105
// 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
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@nbbeeken nbbeeken Oct 20, 2020

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.

const options = this.options;
const options = Object.assign({}, this.options, this.bsonOptions);
Copy link
Contributor

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

Comment on lines 1906 to 1907
// 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
Copy link
Contributor

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

@HanaPearlman HanaPearlman force-pushed the NODE-1561/master/unify-ignoreUndefined branch from 7ce8201 to 8a75dc8 Compare October 20, 2020 18:58
@@ -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));
Copy link
Contributor

@reggi reggi Oct 20, 2020

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;

Copy link
Contributor Author

@HanaPearlman HanaPearlman Oct 20, 2020

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.

Copy link
Contributor

@emadum emadum left a 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;
Copy link
Contributor

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
});

Copy link
Contributor

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) {
Copy link
Contributor

@emadum emadum Oct 21, 2020

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Member

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);
Copy link
Contributor

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);

@HanaPearlman HanaPearlman requested a review from reggi October 21, 2020 17:50
Copy link
Contributor

@emadum emadum left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@@ -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));
Copy link
Contributor

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.

Copy link
Contributor

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)

@HanaPearlman HanaPearlman requested a review from reggi October 22, 2020 17:59
@HanaPearlman HanaPearlman force-pushed the NODE-1561/master/unify-ignoreUndefined branch from 5b360ed to e436fba Compare October 22, 2020 22:35
@HanaPearlman HanaPearlman requested a review from mbroadst October 23, 2020 12:10
Copy link
Member

@mbroadst mbroadst left a 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'] } },
Copy link
Member

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) {
  ...
});

Copy link
Contributor

@reggi reggi left a comment

Choose a reason for hiding this comment

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

LGTM

@HanaPearlman HanaPearlman merged commit b4ec3ed into mongodb:master Oct 27, 2020
@HanaPearlman HanaPearlman deleted the NODE-1561/master/unify-ignoreUndefined branch October 27, 2020 21:49
@HanaPearlman HanaPearlman restored the NODE-1561/master/unify-ignoreUndefined branch October 27, 2020 21:52
@HanaPearlman HanaPearlman deleted the NODE-1561/master/unify-ignoreUndefined branch October 27, 2020 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants