Skip to content

fix(query): cast case and then expressions in $expr $switch branches - #16534

Open
kwy404 wants to merge 2 commits into
Automattic:masterfrom
kwy404:fix-expr-switch-branch-casting
Open

kwy404 wants to merge 2 commits into
Automattic:masterfrom
kwy404:fix-expr-switch-branch-casting

Conversation

@kwy404

@kwy404 kwy404 commented Sep 26, 2026

Copy link
Copy Markdown

Summary

cast$expr() passes each $switch branch object straight to _castExpression():

val.$switch.branches = val.$switch.branches.map(v => _castExpression(v, schema, strictQuery));

A branch is { case: <expression>, then: <expression> }. _castExpression() only acts on operator keys like $eq or $multiply, so on a branch object it finds nothing to do and returns it unchanged. The case and then expressions are never cast, while the if, then and else of $cond are.

This PR casts branch.case and branch.then the same way $cond casts if and then. Missing keys are skipped, so a malformed branch still goes to the server as is.

Examples

const Person = mongoose.model('Person', new Schema({ name: String, age: Number }));
await Person.create([{ name: 'a', age: 30 }, { name: 'b', age: 40 }]);

// before: matches nothing, because the '30' in case stays a string. After: matches 'a'
await Person.find({ $expr: { $switch: { branches: [{ case: { $eq: ['$age', '30'] }, then: true }], default: false } } });

// matches 'a' both before and after, because $cond.if is already cast
await Person.find({ $expr: { $cond: { if: { $eq: ['$age', '30'] }, then: true, else: false } } });

Added casts case and then expressions in $switch branches to test/helpers/query.cast$expr.test.js. It fails on master (the '18' in case and the '2' in then stay strings) and passes with this change. The existing gh-14751 $switch test and the other $expr tests still pass, and eslint is clean.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The new loop unintentionally stops removing undefined fields from branch objects.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Fixes casting within $expr.$switch branch expressions.

Changes:

  • Casts each branch’s case and then expressions.
  • Adds regression coverage for comparison and arithmetic casting.
File Description
lib/​helpers/​query/​cast$expr.js Recursively casts $switch branch expressions.
test/​helpers/​query.cast$expr.test.js Tests casting within case and then.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +100 to +107
for (const branch of val.$switch.branches) {
if (branch?.case != null) {
branch.case = _castExpression(branch.case, schema, strictQuery);
}
if (branch?.then != null) {
branch.then = _castExpression(branch.then, schema, strictQuery);
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, thanks. I confirmed that master drops then: undefined from the branch through the final omitUndefined() pass, while this loop kept it. I added omitUndefined(branch) after casting case and then, so branches get the same cleanup as before, and extended the test with a branch that has then: undefined.

This branch has not been deployed

No deployments
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.

2 participants