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: always use optimized findOrCreate #738

Merged
merged 1 commit into from
Sep 17, 2024
Merged
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
179 changes: 90 additions & 89 deletions lib/mongodb.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,9 @@
this.settings.enableOptimizedfindOrCreate === true ||
this.settings.enableOptimizedFindOrCreate === true
) {
MongoDB.prototype.findOrCreate = optimizedFindOrCreate;
debug('Optimized findOrCreate is enabled by default, and the enableOptimizedFindOrCreate setting is ignored since v7.0.0.')

Check failure on line 200 in lib/mongodb.js

View workflow job for this annotation

GitHub Actions / Code Lint

This line has a length of 129. Maximum allowed is 120

Check failure on line 200 in lib/mongodb.js

View workflow job for this annotation

GitHub Actions / Code Lint

Expected indentation of 4 spaces but found 6

Check failure on line 200 in lib/mongodb.js

View workflow job for this annotation

GitHub Actions / Code Lint

Missing semicolon
}

if (this.settings.enableGeoIndexing === true) {
MongoDB.prototype.buildNearFilter = buildNearFilter;
} else {
Expand Down Expand Up @@ -1166,9 +1167,9 @@
keys = keys.split(',');
}
for (let index = 0, len = keys.length; index < len; index++) {
const m = keys[index].match(/\s+(A|DE)SC$/);

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on
library input
may run slow on strings with many repetitions of ' '.
This
regular expression
that depends on
library input
may run slow on strings with many repetitions of ' '.
This
regular expression
that depends on
library input
may run slow on strings with many repetitions of ' '.
let key = keys[index];
key = key.replace(/\s+(A|DE)SC$/, '').trim();

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on
library input
may run slow on strings with many repetitions of ' '.
This
regular expression
that depends on
library input
may run slow on strings with many repetitions of ' '.
This
regular expression
that depends on
library input
may run slow on strings with many repetitions of ' '.
if (key === idName) {
key = '_id';
} else {
Expand Down Expand Up @@ -1509,6 +1510,94 @@
}
};

/**
* Find a matching model instances by the filter or create a new instance
*
* Only supported on mongodb 2.6+
*
* @param {String} modelName The model name
* @param {Object} data The model instance data
* @param {Object} filter The filter
* @param {Function} [callback] The callback function
*/
MongoDB.prototype.findOrCreate = function findOrCreate(modelName, filter, data, options, callback) {
const self = this;
if (self.debug) {
debug('findOrCreate', modelName, filter, data);
}

if (!callback) callback = options;

const idValue = self.getIdValue(modelName, data);
const idName = self.idName(modelName);

if (idValue == null) {
delete data[idName]; // Allow MongoDB to generate the id
} else {
const oid = self.coerceId(modelName, idValue, options); // Is it an Object ID?
data._id = oid; // Set it to _id
if (idName !== '_id') {
delete data[idName];
}
}

filter = filter || {};
let query = {};
if (filter.where) {
if (filter.where[idName]) {
let id = filter.where[idName];
delete filter.where[idName];
id = self.coerceId(modelName, id, options);
filter.where._id = id;
}
query = self.buildWhere(modelName, filter.where, options);
}

const sort = self.buildSort(modelName, filter.order, options);

const projection = fieldsArrayToObj(filter.fields);

this.collection(modelName).findOneAndUpdate(
query,
{$setOnInsert: data},
{projection: projection, sort: sort, upsert: true},
function(err, result) {
if (self.debug) {
debug('findOrCreate.callback', modelName, filter, err, result);
}
if (err) {
return callback(err);
}

let value = result.value;
const created = !!result.lastErrorObject.upserted;

if (created && (value == null || Object.keys(value).length === 0)) {
value = data;
self.setIdValue(modelName, value, result.lastErrorObject.upserted);
} else {
value = self.fromDatabase(modelName, value);
self.setIdValue(modelName, value, value._id);
}

if (value && idName !== '_id') {
delete value._id;
}

if (filter && filter.include) {
self._models[modelName].model.include([value], filter.include, function(
err,
data,
) {
callback(err, data[0], created);
});
} else {
callback(null, value, created);
}
},
);
}

Check failure on line 1599 in lib/mongodb.js

View workflow job for this annotation

GitHub Actions / Code Lint

Missing semicolon

/**
* Transform db data to model entity
*
Expand Down Expand Up @@ -2265,94 +2354,6 @@

exports.sanitizeFilter = sanitizeFilter;

/**
* Find a matching model instances by the filter or create a new instance
*
* Only supported on mongodb 2.6+
*
* @param {String} modelName The model name
* @param {Object} data The model instance data
* @param {Object} filter The filter
* @param {Function} [callback] The callback function
*/
function optimizedFindOrCreate(modelName, filter, data, options, callback) {
const self = this;
if (self.debug) {
debug('findOrCreate', modelName, filter, data);
}

if (!callback) callback = options;

const idValue = self.getIdValue(modelName, data);
const idName = self.idName(modelName);

if (idValue == null) {
delete data[idName]; // Allow MongoDB to generate the id
} else {
const oid = self.coerceId(modelName, idValue, options); // Is it an Object ID?
data._id = oid; // Set it to _id
if (idName !== '_id') {
delete data[idName];
}
}

filter = filter || {};
let query = {};
if (filter.where) {
if (filter.where[idName]) {
let id = filter.where[idName];
delete filter.where[idName];
id = self.coerceId(modelName, id, options);
filter.where._id = id;
}
query = self.buildWhere(modelName, filter.where, options);
}

const sort = self.buildSort(modelName, filter.order, options);

const projection = fieldsArrayToObj(filter.fields);

this.collection(modelName).findOneAndUpdate(
query,
{$setOnInsert: data},
{projection: projection, sort: sort, upsert: true},
function(err, result) {
if (self.debug) {
debug('findOrCreate.callback', modelName, filter, err, result);
}
if (err) {
return callback(err);
}

let value = result.value;
const created = !!result.lastErrorObject.upserted;

if (created && (value == null || Object.keys(value).length === 0)) {
value = data;
self.setIdValue(modelName, value, result.lastErrorObject.upserted);
} else {
value = self.fromDatabase(modelName, value);
self.setIdValue(modelName, value, value._id);
}

if (value && idName !== '_id') {
delete value._id;
}

if (filter && filter.include) {
self._models[modelName].model.include([value], filter.include, function(
err,
data,
) {
callback(err, data[0], created);
});
} else {
callback(null, value, created);
}
},
);
}

/**
* @param {*} data Plain Data Object for the matching property definition(s)
* @param {*} modelCtor Model constructor
Expand Down
Loading