Files
mongo/jstests/aggregation/sources/project/project_coalescing.js
Zac 591928c619 SERVER-108478 JS formatted by prettier and remove clang-format (#39656)
GitOrigin-RevId: 6c8f6aded47f260aa4f7c231b17dae3302cb1e04
2025-08-21 17:27:09 +00:00

92 lines
3.7 KiB
JavaScript

/**
* Test that coalesced $projects return expected fields.
*
* @tags: []
*/
import {fc} from "jstests/third_party/fast_check/fc-3.1.0.js";
const coll = db.project_coalescing;
coll.drop();
// $project coalescing only operates on top-level fields.
assert.commandWorked(coll.insert({_id: 1, a: 1, b: 1, c: 1, d: 1}));
// Now we model what our pipelines will look like.
// 'arb' is short for arbitrary. This is a model of an arbitrary field.
const fieldArb = fc.constantFrom("_id", "a", "b", "c", "d");
// {$project: {_id: 0/1, a: 0/1, ...}}
const projectFieldsArb = fc.uniqueArray(fieldArb, {minLength: 1, maxLength: 5});
// To make a valid $project, we need a list of fields, a boolean for whether the _id field is
// included, and a boolean for whether the non-_id fields are included (if the projection inclusive)
const projectArb = fc.tuple(projectFieldsArb, fc.boolean(), fc.boolean()).map(([fields, idIncluded, isInclusive]) => {
const projectList = {};
for (const field of fields) {
projectList[field] = field === "_id" ? idIncluded : isInclusive;
}
return {$project: projectList};
});
// A pipeline is [$project, $project, ...]
const pipelineModel = fc.array(projectArb, {minLength: 1, maxLength: 5});
// You can get some examples of what pipelineModel looks like using fc.sample
// jsTestLog(fc.sample(pipelineModel))
// We can provide a list of example cases for fast-check to test before generating random cases. In
// this case we provide an example that used to be a bug, from SERVER-91405. The bug repros with the
// pipeline [{$project: {a: 0}}, {$project: {a: 0}}]
const examples = [[[{$project: {a: 0}}, {$project: {a: 0}}]]];
// Get a full specification of what fields are included and excluded by this $project, since we know
// the schema. This is used to calculate the correct answer.
function getFullSpec(projSpec) {
const projKeys = Object.keys(projSpec);
projKeys.sort();
// If only _id is specified, it indicates the full inclusive/exclusiveness.
if (projKeys.length === 1 && projKeys[0] === "_id") {
if (projSpec._id) {
return {_id: 1, a: 0, b: 0, c: 0, d: 0};
} else {
return {_id: 0, a: 1, b: 1, c: 1, d: 1};
}
}
// All non-id fields must have the same inclusivity/exclusivity to be a valid $project. _id will
// always be at the front since we sorted the keys, so let's just take the last one.
const nonIdField = projKeys[projKeys.length - 1];
const isInclusive = projSpec[nonIdField];
const fullSpec = isInclusive ? {_id: 1, a: 0, b: 0, c: 0, d: 0} : {_id: 1, a: 1, b: 1, c: 1, d: 1};
for (const key of projKeys) {
fullSpec[key] = projSpec[key];
}
return fullSpec;
}
fc.assert(
fc.property(pipelineModel, (pipeline) => {
// By default, all fields will be included. Keep track of what fields we should see at each
// stage of the pipeline, and then we'll know what to expect in the result.
const expectedFields = {_id: 1, a: 1, b: 1, c: 1, d: 1};
for (const stage of pipeline) {
const fullSpec = getFullSpec(stage["$project"]);
for (const key of Object.keys(fullSpec)) {
expectedFields[key] &= fullSpec[key];
}
}
const results = coll.aggregate(pipeline).toArray();
assert.eq(results.length, 1);
// Check the result against what we expect.
const resultKeys = Object.keys(results[0]);
for (const key of Object.keys(expectedFields)) {
if (expectedFields[key]) {
assert(resultKeys.includes(key));
} else {
assert(!resultKeys.includes(key));
}
}
}),
{seed: 5, numRuns: 500, examples},
);