popurelate
Version:
Easily populate and filter documents using data from related collections.
217 lines (216 loc) • 8.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.haveCommonPath = exports.recomposePipelines = exports.canBeMerged = void 0;
const errors_1 = require("../errors");
const path_1 = require("../path");
const deps_1 = require("./deps");
const is_1 = require("./is");
function canBeMerged(pipeline1, pipeline2, finder, actuallyMerge) {
var _a, _b, _c, _d;
const nah = actuallyMerge ? null : false;
const type1 = is_1.getPipelineType(pipeline1.pipeline);
const type2 = is_1.getPipelineType(pipeline2.pipeline);
if (pipeline1.removable || pipeline2.removable)
return nah;
if (type1 === null || type2 === null)
return nah;
if (!pipeline1.useOptimizer || !pipeline2.useOptimizer)
return nah;
if (type1 !== type2)
return nah;
if (pipeline1.IAmChangingFields &&
pipeline2.IAmChangingFields &&
pipeline1.IAmChangingFields.isChangingEveryField !==
pipeline2.IAmChangingFields.isChangingEveryField) {
return nah;
}
if (type1 === "query") {
if (haveCommonElement(pipeline1.IAmDependedOnFields, pipeline2.IAmDependedOnFields) ||
!canQueriesBeMerged(pipeline1.pipeline, pipeline2.pipeline)) {
return nah;
}
if (!actuallyMerge)
return true;
return mergeWrapped(pipeline1, pipeline2, finder, mergeQueryPipelines(pipeline1.pipeline, pipeline2.pipeline));
}
if (type1 === "addFields") {
if (((_a = pipeline1.IAmChangingFields) === null || _a === void 0 ? void 0 : _a.isChangingEveryField) || ((_b = pipeline2.IAmChangingFields) === null || _b === void 0 ? void 0 : _b.isChangingEveryField)) {
return nah;
}
if (exports.haveCommonPath(((_c = pipeline1.IAmChangingFields) === null || _c === void 0 ? void 0 : _c.fields) || new Set(), ((_d = pipeline2.IAmChangingFields) === null || _d === void 0 ? void 0 : _d.fields) || new Set())) {
return nah;
}
// strict check
if (!actuallyMerge)
return true;
return mergeWrapped(pipeline1, pipeline2, finder, mergeAddFieldsPipelines(pipeline1.pipeline, pipeline2.pipeline));
}
if (type1 === "populate") {
const pip1 = pipeline1.pipeline;
const pip2 = pipeline2.pipeline;
if (pip1.field === pip2.field && canPopulatesBeMErged(pip1, pip2)) {
if (!actuallyMerge)
return true;
return mergeWrapped(pipeline1, pipeline2, finder, mergePopulatePipelines(pipeline1.pipeline, pipeline2.pipeline));
}
return nah;
}
return nah;
}
exports.canBeMerged = canBeMerged;
const canQueriesBeMerged = (p1, p2) => {
return !haveCommonElement(Object.keys(p1.query), new Set(Object.keys(p2.query)));
};
const canPopulatesBeMErged = (p1, p2) => {
if (!arePopulationsSameWithoucChildren(p1, p2)) {
return false;
}
const children1 = p1.populate.children;
const children2 = p2.populate.children;
if (children1 && children2) {
const keys = new Set(Object.keys(children1).concat(Object.keys(children2)));
for (const key of keys) {
const child1 = children1[key];
const child2 = children2[key];
if (!child1 || !child2)
continue;
if (!canPopulatesBeMErged(child1, child2))
return false;
}
}
return true;
};
const arePopulationsSameWithoucChildren = (p1, p2) => {
if (!haveSameValues(p1, p2, ["field", "parentIdField", "myIdField"])) {
return false;
}
if (!haveSameValues(p1.populate, p2.populate, [
"modelName",
"localField",
"foreignField",
"globalPathPrefix",
"localPathPrefix",
"matchesMany",
"required",
"through",
])) {
return false;
}
return true;
};
const haveSameValues = (obj1, obj2, keys) => {
for (const key of keys) {
if (obj1[key] !== obj2[key])
return false;
}
return true;
};
const recomposePipelines = (pipelines, finder) => {
let firstIndex = -1;
let secondIndex = -1;
for (let i = 0; i < pipelines.length; ++i) {
if (pipelines[i].removable)
continue;
if (secondIndex === -1) {
secondIndex = i;
continue;
}
if (secondIndex !== i)
firstIndex = secondIndex;
secondIndex = i;
const p1 = pipelines[firstIndex];
const p2 = pipelines[secondIndex];
const mergable = canBeMerged(p1, p2, finder, true);
if (!mergable)
continue;
pipelines.splice(secondIndex, 1, mergable);
pipelines.splice(firstIndex, 1);
i--;
}
return pipelines;
};
exports.recomposePipelines = recomposePipelines;
const haveCommonElement = (set1, set2) => {
for (const el of set1) {
if (set2.has(el))
return true;
}
return false;
};
const haveCommonPath = (set1, set2) => {
for (const field1 of set1) {
for (const field2 of set2) {
if (path_1.isSubPathOf(field1, field2) || path_1.isSubPathOf(field2, field1)) {
return true;
}
}
}
return false;
};
exports.haveCommonPath = haveCommonPath;
const mergeQueryPipelines = (p1, p2) => {
return Object.assign(Object.assign({}, p1), { query: Object.assign(Object.assign({}, p1.query), p2.query) });
};
const mergeAddFieldsPipelines = (p1, p2) => {
return Object.assign(Object.assign({}, p1), { addFields: Object.assign(Object.assign({}, p1.addFields), p2.addFields) });
};
const mergePopulatePipelines = (p1, p2) => {
const newObj = Object.assign(Object.assign({}, p1), { populate: Object.assign(Object.assign({}, p1.populate), { alreadyPopulated: !!p1.populate.alreadyPopulated && !!p2.populate.alreadyPopulated, children: {} }) });
if (p1.populate.children) {
for (const key in p1.populate.children) {
const child1 = p1.populate.children[key];
const child2 = p2.populate.children && p2.populate.children[key];
newObj.populate.children[key] = !child2
? child1
: mergePopulatePipelines(child1, child2);
}
}
if (p2.populate.children) {
for (const key in p2.populate.children) {
if (p1.populate.children && p1.populate.children[key])
continue;
const child = p2.populate.children[key];
newObj.populate.children[key] = child;
}
}
if (Object.keys(newObj.populate.children).length === 0) {
delete newObj.populate.children;
}
return newObj;
};
const mergeWrapped = (w1, w2, finder, mergedPipeline) => {
const newObj = Object.assign(Object.assign({}, w1), { IAmDependedOnFields: union(w1.IAmDependedOnFields, w2.IAmDependedOnFields), IAmChangingFields: unionChangingFields(w1.IAmChangingFields, w2.IAmChangingFields), pipeline: mergedPipeline, IAmDependedOnPipelineIds: new Set(w1.IAmDependedOnPipelineIds), pipelineIdsDepenedOnMe: new Set(w1.pipelineIdsDepenedOnMe) });
finder[newObj.id] = newObj;
deps_1.deps.transferDependences(w2.id, newObj.id, finder);
return newObj;
};
const union = (set1, set2) => {
const newSet = new Set(set1);
for (const elem of set2)
newSet.add(elem);
return newSet;
};
const unionChangingFields = (f1, f2) => {
if (!f1)
return f2;
if (!f2)
return f1;
if (f1.isChangingEveryField) {
if (!f2.isChangingEveryField) {
throw new errors_1.QueryBuilderError("Merging changing isChangingEveryField=true with isChangingEveryField=false is not supproted");
}
return {
isChangingEveryField: true,
except: union(f1.except, f2.except),
};
}
else {
if (f2.isChangingEveryField) {
throw new errors_1.QueryBuilderError("Merging changing isChangingEveryField=false with isChangingEveryField=true is not supproted");
}
return {
isChangingEveryField: false,
fields: union(f1.fields, f2.fields),
};
}
};