some progress

This commit is contained in:
Jonas_Jones 2023-03-30 20:40:42 +02:00
parent aea93a5527
commit e3c15bd288
1388 changed files with 306946 additions and 68323 deletions

View file

@ -0,0 +1,77 @@
'use strict';
const hasIncludedChildren = require('./hasIncludedChildren');
const isExclusive = require('./isExclusive');
const isInclusive = require('./isInclusive');
const isPOJO = require('../../utils').isPOJO;
module.exports = function applyProjection(doc, projection, _hasIncludedChildren) {
if (projection == null) {
return doc;
}
if (doc == null) {
return doc;
}
let exclude = null;
if (isInclusive(projection)) {
exclude = false;
} else if (isExclusive(projection)) {
exclude = true;
}
if (exclude == null) {
return doc;
} else if (exclude) {
_hasIncludedChildren = _hasIncludedChildren || hasIncludedChildren(projection);
return applyExclusiveProjection(doc, projection, _hasIncludedChildren);
} else {
_hasIncludedChildren = _hasIncludedChildren || hasIncludedChildren(projection);
return applyInclusiveProjection(doc, projection, _hasIncludedChildren);
}
};
function applyExclusiveProjection(doc, projection, hasIncludedChildren, projectionLimb, prefix) {
if (doc == null || typeof doc !== 'object') {
return doc;
}
const ret = { ...doc };
projectionLimb = prefix ? (projectionLimb || {}) : projection;
for (const key of Object.keys(ret)) {
const fullPath = prefix ? prefix + '.' + key : key;
if (projection.hasOwnProperty(fullPath) || projectionLimb.hasOwnProperty(key)) {
if (isPOJO(projection[fullPath]) || isPOJO(projectionLimb[key])) {
ret[key] = applyExclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
} else {
delete ret[key];
}
} else if (hasIncludedChildren[fullPath]) {
ret[key] = applyExclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
}
}
return ret;
}
function applyInclusiveProjection(doc, projection, hasIncludedChildren, projectionLimb, prefix) {
if (doc == null || typeof doc !== 'object') {
return doc;
}
const ret = { ...doc };
projectionLimb = prefix ? (projectionLimb || {}) : projection;
for (const key of Object.keys(ret)) {
const fullPath = prefix ? prefix + '.' + key : key;
if (projection.hasOwnProperty(fullPath) || projectionLimb.hasOwnProperty(key)) {
if (isPOJO(projection[fullPath]) || isPOJO(projectionLimb[key])) {
ret[key] = applyInclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
}
continue;
} else if (hasIncludedChildren[fullPath]) {
ret[key] = applyInclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
} else {
delete ret[key];
}
}
return ret;
}

View file

@ -0,0 +1,40 @@
'use strict';
/**
* Creates an object that precomputes whether a given path has child fields in
* the projection.
*
* #### Example:
*
* const res = hasIncludedChildren({ 'a.b.c': 0 });
* res.a; // 1
* res['a.b']; // 1
* res['a.b.c']; // 1
* res['a.c']; // undefined
*
* @param {Object} fields
* @api private
*/
module.exports = function hasIncludedChildren(fields) {
const hasIncludedChildren = {};
const keys = Object.keys(fields);
for (const key of keys) {
if (key.indexOf('.') === -1) {
hasIncludedChildren[key] = 1;
continue;
}
const parts = key.split('.');
let c = parts[0];
for (let i = 0; i < parts.length; ++i) {
hasIncludedChildren[c] = 1;
if (i + 1 < parts.length) {
c = c + '.' + parts[i + 1];
}
}
}
return hasIncludedChildren;
};

View file

@ -0,0 +1,18 @@
'use strict';
/*!
* ignore
*/
module.exports = function isDefiningProjection(val) {
if (val == null) {
// `undefined` or `null` become exclusive projections
return true;
}
if (typeof val === 'object') {
// Only cases where a value does **not** define whether the whole projection
// is inclusive or exclusive are `$meta` and `$slice`.
return !('$meta' in val) && !('$slice' in val);
}
return true;
};

View file

@ -0,0 +1,35 @@
'use strict';
const isDefiningProjection = require('./isDefiningProjection');
/*!
* ignore
*/
module.exports = function isExclusive(projection) {
if (projection == null) {
return null;
}
const keys = Object.keys(projection);
let ki = keys.length;
let exclude = null;
if (ki === 1 && keys[0] === '_id') {
exclude = !projection._id;
} else {
while (ki--) {
// Does this projection explicitly define inclusion/exclusion?
// Explicitly avoid `$meta` and `$slice`
const key = keys[ki];
if (key !== '_id' && isDefiningProjection(projection[key])) {
exclude = (projection[key] != null && typeof projection[key] === 'object') ?
isExclusive(projection[key]) :
!projection[key];
break;
}
}
}
return exclude;
};

View file

@ -0,0 +1,38 @@
'use strict';
const isDefiningProjection = require('./isDefiningProjection');
/*!
* ignore
*/
module.exports = function isInclusive(projection) {
if (projection == null) {
return false;
}
const props = Object.keys(projection);
const numProps = props.length;
if (numProps === 0) {
return false;
}
for (let i = 0; i < numProps; ++i) {
const prop = props[i];
// Plus paths can't define the projection (see gh-7050)
if (prop.startsWith('+')) {
continue;
}
// If field is truthy (1, true, etc.) and not an object, then this
// projection must be inclusive. If object, assume its $meta, $slice, etc.
if (isDefiningProjection(projection[prop]) && !!projection[prop]) {
if (projection[prop] != null && typeof projection[prop] === 'object') {
return isInclusive(projection[prop]);
} else {
return !!projection[prop];
}
}
}
return false;
};

View file

@ -0,0 +1,40 @@
'use strict';
const isDefiningProjection = require('./isDefiningProjection');
/**
* Determines if `path` is excluded by `projection`
*
* @param {Object} projection
* @param {String} path
* @return {Boolean}
* @api private
*/
module.exports = function isPathExcluded(projection, path) {
if (projection == null) {
return false;
}
if (path === '_id') {
return projection._id === 0;
}
const paths = Object.keys(projection);
let type = null;
for (const _path of paths) {
if (isDefiningProjection(projection[_path])) {
type = projection[path] === 1 ? 'inclusive' : 'exclusive';
break;
}
}
if (type === 'inclusive') {
return projection[path] !== 1;
}
if (type === 'exclusive') {
return projection[path] === 0;
}
return false;
};

View file

@ -0,0 +1,28 @@
'use strict';
/*!
* ignore
*/
module.exports = function isPathSelectedInclusive(fields, path) {
const chunks = path.split('.');
let cur = '';
let j;
let keys;
let numKeys;
for (let i = 0; i < chunks.length; ++i) {
cur += cur.length ? '.' : '' + chunks[i];
if (fields[cur]) {
keys = Object.keys(fields);
numKeys = keys.length;
for (j = 0; j < numKeys; ++j) {
if (keys[i].indexOf(cur + '.') === 0 && keys[i].indexOf(path) !== 0) {
continue;
}
}
return true;
}
}
return false;
};

View file

@ -0,0 +1,14 @@
'use strict';
/**
* Determines if `path2` is a subpath of or equal to `path1`
*
* @param {string} path1
* @param {string} path2
* @return {Boolean}
* @api private
*/
module.exports = function isSubpath(path1, path2) {
return path1 === path2 || path2.startsWith(path1 + '.');
};

View file

@ -0,0 +1,33 @@
'use strict';
/**
* Convert a string or array into a projection object, retaining all
* `-` and `+` paths.
*/
module.exports = function parseProjection(v, retainMinusPaths) {
const type = typeof v;
if (type === 'string') {
v = v.split(/\s+/);
}
if (!Array.isArray(v) && Object.prototype.toString.call(v) !== '[object Arguments]') {
return v;
}
const len = v.length;
const ret = {};
for (let i = 0; i < len; ++i) {
let field = v[i];
if (!field) {
continue;
}
const include = '-' == field[0] ? 0 : 1;
if (!retainMinusPaths && include === 0) {
field = field.substring(1);
}
ret[field] = include;
}
return ret;
};