Files
mongo/jstests/core/query/update/update_undefined.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

266 lines
9.9 KiB
JavaScript

/*
* This jstest demonstrates some simple ways to remove undefined values from user data.
* @tags: [
* requires_multi_updates,
* requires_non_retryable_writes
* ]
*/
import {documentEq} from "jstests/aggregation/extras/utils.js";
const coll = db[jsTestName()];
const docs = [
// No "a.b" path
{_id: 0, a: 1},
{_id: 1, a: undefined},
{_id: 2, a: {}},
{_id: 3, a: []},
{_id: 4, a: [undefined, 1, [undefined, 1]]},
{_id: 5, b: undefined},
// No arrays at a or a.b
{_id: 10, a: {b: 1}},
{_id: 11, a: {b: {}}},
{_id: 12, a: {b: {c: 1}}},
{_id: 13, a: {b: undefined, c: 1}},
// a is an array, a.b non-array
{_id: 20, a: [{b: undefined, c: 1}, {c: 1}, 1, [{b: undefined, c: 1}]]},
// a.b is array
{_id: 30, a: {b: []}},
{_id: 31, a: {b: [1, {c: 1}]}},
{_id: 32, a: {b: [undefined, 1, [undefined]], c: 1}},
// a and a.b arrays
{_id: 33, a: [{b: [undefined]}, {b: 1}, {b: [1, undefined]}, {c: 1}]},
];
function runUpdate(update, expected, documents = docs) {
coll.drop();
assert.commandWorked(coll.insert(documents));
const command = Object.assign({update: coll.getName()}, update);
assert.commandWorked(db.runCommand(command));
const collContents = coll.find().sort({_id: 1}).toArray();
assert.eq(collContents.length, expected.length, "Expected result and actual result do not have the same length.");
for (let i = 0; i < collContents.length; i++) {
assert(
documentEq(collContents[i], expected[i]),
"Collection contents after update are not what was expected. Did you sort your expected results by _id?\n" +
"actualDoc=" +
tojson(collContents[i]) +
"\nexpectedDoc=" +
tojson(expected[i]) +
"\ncollContents=" +
tojson(collContents) +
"\nexpected=" +
tojson(expected) +
"\nupdate=" +
tojson(command),
);
}
}
// If you know the name of the field containing the undefined values, you can target it specifically
// by matching on undefined with $expr $type and using $set to null.
// Note that something like coll.update({a: {$type: "undefined"}}, {$set: {a: null}}, {multi: true})
// may not work if the dataset has arrays. Match expression $type does implicit array traversal
// after the last element, so this will replace [undefined, 1] with null. We need to use $expr here.
runUpdate(
{
updates: [{q: {$expr: {$eq: [{$type: "$a"}, "undefined"]}}, u: {$set: {"a": null}}, multi: true}],
},
[
{_id: 0, a: 1},
{_id: 1, a: null}, // This is the only document that changed.
{_id: 2, a: {}},
{_id: 3, a: []},
{_id: 4, a: [undefined, 1, [undefined, 1]]}, // Note that this document does not change.
{_id: 5, b: undefined},
{_id: 10, a: {b: 1}},
{_id: 11, a: {b: {}}},
{_id: 12, a: {b: {c: 1}}},
{_id: 13, a: {b: undefined, c: 1}},
{_id: 20, a: [{b: undefined, c: 1}, {c: 1}, 1, [{b: undefined, c: 1}]]},
{_id: 30, a: {b: []}},
{_id: 31, a: {b: [1, {c: 1}]}},
{_id: 32, a: {b: [undefined, 1, [undefined]], c: 1}},
{_id: 33, a: [{b: [undefined]}, {b: 1}, {b: [1, undefined]}, {c: 1}]},
],
);
// We can do the same thing with dotted paths. As above, this does not do implicit array traversal.
runUpdate(
{
updates: [{q: {$expr: {$eq: [{$type: "$a.b"}, "undefined"]}}, u: {$set: {"a.b": null}}, multi: true}],
},
[
{_id: 0, a: 1},
{_id: 1, a: undefined},
{_id: 2, a: {}},
{_id: 3, a: []},
{_id: 4, a: [undefined, 1, [undefined, 1]]},
{_id: 5, b: undefined},
{_id: 10, a: {b: 1}},
{_id: 11, a: {b: {}}},
{_id: 12, a: {b: {c: 1}}},
{_id: 13, a: {b: null, c: 1}}, // This is the only document that changed.
{
_id: 20,
a: [{b: undefined, c: 1}, {c: 1}, 1, [{b: undefined, c: 1}]],
}, // This one does not.
{_id: 30, a: {b: []}},
{_id: 31, a: {b: [1, {c: 1}]}},
{_id: 32, a: {b: [undefined, 1, [undefined]], c: 1}},
{_id: 33, a: [{b: [undefined]}, {b: 1}, {b: [1, undefined]}, {c: 1}]},
],
);
// If you don't know the name of the fields where the undefined values are, you can still remove
// or replace undefined values in top-level fields. This example doesn't handle dotted paths or
// array traversals.
runUpdate(
{
updates: [
{
q: {},
u: [
{
$replaceWith: {
$arrayToObject: {
$filter: {
input: {$objectToArray: "$$ROOT"},
// This condition removes undefined values. See example below for replacing
// these values with null, instead.
cond: {$not: {$eq: [{$type: "$$this.v"}, "undefined"]}},
},
},
},
},
],
multi: true,
},
],
},
[
{_id: 0, a: 1},
{_id: 1}, // We removed field "a" here.
{_id: 2, a: {}},
{_id: 3, a: []},
{_id: 4, a: [undefined, 1, [undefined, 1]]},
{_id: 5}, // We removed field "b" here.
{_id: 10, a: {b: 1}},
{_id: 11, a: {b: {}}},
{_id: 12, a: {b: {c: 1}}},
{_id: 13, a: {b: undefined, c: 1}},
{_id: 20, a: [{b: undefined, c: 1}, {c: 1}, 1, [{b: undefined, c: 1}]]},
{_id: 30, a: {b: []}},
{_id: 31, a: {b: [1, {c: 1}]}},
{_id: 32, a: {b: [undefined, 1, [undefined]], c: 1}},
{_id: 33, a: [{b: [undefined]}, {b: 1}, {b: [1, undefined]}, {c: 1}]},
],
);
// Here's a similar example, but instead of removing the undefined values, we replace them with
// null. This example focuses on top-level field "a". Unlike above, it will replace undefined under
// dotted paths and arrays (but only one level).
runUpdate(
{
updates: [
{
q: {},
u: [
{
$set: {
"a": {
$cond: {
// When "a" is an array, we convert {a: [undefined, 1]} --> {a: [null, 1]}
if: {$eq: [{$type: "$a"}, "array"]},
then: {
$map: {
input: "$a",
in: {
$cond: {
if: {$eq: [{$type: "$$this"}, "undefined"]},
then: null,
else: "$$this",
},
},
},
},
else: {
$cond: {
// When "a" is an object, we convert {a: {b: undefined, c: 1}} -->
// {a: {b: null, c: 1}}
if: {$eq: [{$type: "$a"}, "object"]},
then: {
$arrayToObject: {
$map: {
input: {$objectToArray: "$a"},
in: {
$cond: {
if: {$eq: [{$type: "$$this.v"}, "undefined"]},
then: {k: "$$this.k", v: null},
else: "$$this",
},
},
},
},
},
// When "a" is a scalar, we convert undefined -> null.
else: {
$cond: {
if: {$eq: [{$type: "$a"}, "undefined"]},
then: null,
else: "$a",
},
},
},
},
},
},
},
},
],
multi: true,
},
],
},
[
{_id: 0, a: 1},
{_id: 1, a: null}, // Changed
{_id: 2, a: {}},
{_id: 3, a: []},
{_id: 4, a: [null, 1, [undefined, 1]]}, // Changed
{_id: 5, b: undefined},
{_id: 10, a: {b: 1}},
{_id: 11, a: {b: {}}},
{_id: 12, a: {b: {c: 1}}},
{_id: 13, a: {b: null, c: 1}}, // Changed
{_id: 20, a: [{b: undefined, c: 1}, {c: 1}, 1, [{b: undefined, c: 1}]]},
{_id: 30, a: {b: []}},
{_id: 31, a: {b: [1, {c: 1}]}},
{_id: 32, a: {b: [undefined, 1, [undefined]], c: 1}},
{_id: 33, a: [{b: [undefined]}, {b: 1}, {b: [1, undefined]}, {c: 1}]},
],
);