I have some MongoDB database records that have a nested Array, like this:
[
{
rootName: "AAA",
outerItem: {
innerItems: [
{
name: "A"
},
{
name: null
},
{
name: "C"
}
]
}
}
]
And I have a query that will modify the records using operators like $concatArrays
and $slice
, which works fine in the MongoDB Playground:
db.collection.update({},
[
{
"$set": {
"outerItem.innerItems": {
$concatArrays: [
{
$slice: [
"$outerItem.innerItems",
0,
1
]
}
]
}
}
}
],
{
"multi": false,
"upsert": false
})
It gives this expected result, where only a part of the original Array is used:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"outerItem": {
"innerItems": [
{
"name": "A"
}
]
},
"rootName": "AAA"
}
]
MongoDB Playground link: https://mongoplayground.net/p/DhAjlLnN1xs
I tried to use the same code in a TypeScript Node.js project, using the TypeORM libraries:
import { MigrationInterface } from 'typeorm';
import { MongoQueryRunner } from 'typeorm/driver/mongodb/MongoQueryRunner';
export class MigrateTheDatabase implements MigrationInterface {
public async up(queryRunner: MongoQueryRunner): Promise<void> {
const result = await queryRunner.updateMany('records', {},
[
{
$set: {
'outerItem.innerItems': { $concatArrays: [{ $slice: ['$outerItem.innerItems', 0, 1] }] },
}
}
]);
console.log(`Updated ${result.modifiedCount} records`);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public async down(queryRunner: MongoQueryRunner): Promise<void> {
// nothing
}
}
But when this database migration runs, it just prints an error at runtime, and the query fails to update any records:
Closing migrations connection...
Error while connecting to the database! MongoServerError: First argument to $slice must be an array, but is of type: object
at UpdateManyOperation.execute (/home/user/Project/node_modules/mongodb/src/operations/update.ts:184:32)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at executeOperation (/home/user/Project/node_modules/mongodb/src/operations/execute_operation.ts:155:14)
at MigrateTheDatabase.up (/home/user/Project/dist/migrations/api1/MigrateTheDatabase.js:6:24)
at MigrationExecutor.executePendingMigrations (/home/user/Project/src/migration/MigrationExecutor.ts:336:17)
at DataSource.runMigrations (/home/user/Project/src/data-source/DataSource.ts:403:13)
at runMigrations (/home/user/Project/dist/api1/main.js:824:9)
at bootstrap (/home/user/Project/dist/api1/main.js:19224:5) {
errorResponse: {
index: 0,
code: 28724,
errmsg: 'First argument to $slice must be an array, but is of type: object'
},
index: 0,
code: 28724,
[Symbol(errorLabels)]: Set(0) {}
}
I expected it to update all records just like it did on the Playground. What am I doing wrong? How do I get the Playground code to work in TypeScript with the MongoQueryRunner?
You have some documents where outerItem.innerItems
is not an array. Like the second document in this example:
[
{
_id: "this doc is okay",
rootName: "AAA",
outerItem: {
innerItems: [
{ name: "A" },
{ name: null },
{ name: "C" }
]
}
},
{
_id: "this doc will cause an error",
rootName: "AAA",
outerItem: {
innerItems: { name: "A" }
}
}
]
You will either need to fix those documents or change the filter criteria to only update docs where it's an array:
db.collection.update(
{ "outerItem.innerItems": { $type: "array" } },
[ ... ] // the same pipeline as before
)
Mongo Playground. Adding that filter will also handle cases of outerItem.innerItems
being null, or missing, even if the parent field outerItem
is null or missing.