feat(mongodb): support bulkWrite

This commit is contained in:
Loc Nguyen authored and GitHub committed 2026-09-17 12:54:21 +08:00
1 parent 3f3c21aae9
commit f78dc1ba16
25 files changed
+1089 -18

No files matched your search

+2 -1
View File
@@ -501,8 +501,9 @@ test("suggests only helpers the shell parser accepts", () => {
assert.ok(methodLabels.includes("distinct"));
assert.ok(methodLabels.includes("estimatedDocumentCount"));
assert.ok(methodLabels.includes("replaceOne"));
assert.ok(methodLabels.includes("bulkWrite"));
// Suggesting a helper DBX cannot run just hands the user a command that fails.
for (const unsupported of ["bulkWrite", "renameCollection", "mapReduce"]) {
for (const unsupported of ["renameCollection", "mapReduce", "watch"]) {
assert.equal(methodLabels.includes(unsupported), false, `${unsupported} is not executable`);
}
// Cursor methods are not collection methods.
+63 -3
View File
@@ -5,6 +5,7 @@ import {
evaluateMongoAggregateSafety,
evaluateMongoWriteSafety,
mongoAggregateWriteStage,
mongoBulkWriteToQueryResult,
mongoCollectionStatsToQueryResult,
mongoCountToQueryResult,
mongoDatabasesToQueryResult,
@@ -672,6 +673,65 @@ test("parseMongoWriteCommand accepts unquoted insert and update commands", () =>
});
});
test("parseMongoWriteCommand reads bulkWrite with every operation kind", () => {
const command = parseMongoWriteCommand(`db.orders.bulkWrite([
{ insertOne: { document: { sku: "A1", stock: 1 } } },
{ updateOne: { filter: { sku: "A1" }, update: { $inc: { stock: 1 } }, upsert: true } },
{ updateMany: { filter: { archived: true }, update: [{ $set: { stock: 0 } }] } },
{ replaceOne: { filter: { sku: "B2" }, replacement: { sku: "B2", stock: 9 } } },
{ deleteOne: { filter: { sku: "C3" } } },
{ deleteMany: { filter: { stock: { $lt: 0 } } } }
], { ordered: false })`);
assert.ok(command && command.kind === "bulkWrite");
assert.equal(command.collection, "orders");
assert.equal(command.options, '{ "ordered": false }');
assert.deepEqual(
(JSON.parse(command.operations) as Array<Record<string, unknown>>).map((entry) => Object.keys(entry)[0]),
["insertOne", "updateOne", "updateMany", "replaceOne", "deleteOne", "deleteMany"],
);
assert.equal(parseMongoWriteCommand('db["my-coll"].bulkWrite([{ deleteOne: { filter: { a: 1 } } }]);')?.kind, "bulkWrite");
});
test("bulkWrite operations are validated and the problem is named", () => {
for (const [source, expected] of [
["db.orders.bulkWrite()", /array of operations and optional options/],
["db.orders.bulkWrite({})", /requires an array of operations/],
["db.orders.bulkWrite([])", /at least one operation/],
["db.orders.bulkWrite([1])", /operation 1 must be a document/],
["db.orders.bulkWrite([{ insertOne: {}, deleteOne: {} }])", /exactly one operation key/],
["db.orders.bulkWrite([{ upsertOne: { document: {} } }])", /unsupported operation upsertOne/],
["db.orders.bulkWrite([{ insertOne: { doc: {} } }])", /unsupported field doc/],
["db.orders.bulkWrite([{ insertOne: {} }])", /requires a document document/],
["db.orders.bulkWrite([{ updateOne: { filter: {}, update: { a: 1 } } }])", /must use operators such as \$set/],
["db.orders.bulkWrite([{ updateOne: { filter: {}, update: { $set: { a: 1 } }, upsert: 1 } }])", /upsert must be a boolean/],
["db.orders.bulkWrite([{ updateOne: { filter: {}, update: { $set: { a: 1 } }, collation: {} } }])", /unsupported field collation/],
["db.orders.bulkWrite([{ replaceOne: { filter: {}, replacement: { $set: { a: 1 } } } }])", /must not contain update operators such as \$set/],
["db.orders.bulkWrite([{ deleteOne: { filter: [] } }])", /field filter must be a document/],
["db.orders.bulkWrite([{ deleteOne: { filter: {} } }], { ordered: 1 })", /ordered option must be a boolean/],
["db.orders.bulkWrite([{ deleteOne: { filter: {} } }], { writeConcern: {} })", /Unsupported bulkWrite\(\) option: writeConcern/],
] as const) {
assert.equal(parseMongoWriteCommand(source), null, source);
assert.match(describeMongoCommandParseFailure(source), expected, source);
}
});
test("evaluateMongoWriteSafety treats a bulkWrite as risky as its least-bounded operation", () => {
const policy = { allowWrites: true, allowDangerous: false } as Parameters<typeof evaluateMongoWriteSafety>[1];
const bounded = parseMongoWriteCommand("db.orders.bulkWrite([{ insertOne: { document: { a: 1 } } }, { deleteOne: { filter: { sku: 'X' } } }])")!;
const unbounded = parseMongoWriteCommand("db.orders.bulkWrite([{ updateOne: { filter: { sku: 'X' }, update: { $set: { a: 1 } } } }, { deleteMany: { filter: {} } }])")!;
assert.equal(evaluateMongoWriteSafety(bounded, policy).allowed, true);
assert.equal(evaluateMongoWriteSafety(unbounded, policy).allowed, false);
assert.equal(evaluateMongoWriteSafety(unbounded, { ...policy, allowDangerous: true }).allowed, true);
});
test("mongoBulkWriteToQueryResult renders one row of counts", () => {
const result = mongoBulkWriteToQueryResult({ inserted_count: 2, matched_count: 3, modified_count: 1, deleted_count: 4, upserted_count: 1 }, 12.6);
assert.deepEqual(result.columns, ["insertedCount", "matchedCount", "modifiedCount", "deletedCount", "upsertedCount"]);
assert.deepEqual(result.rows, [[2, 3, 1, 4, 1]]);
assert.equal(result.affected_rows, 8);
assert.equal(result.execution_time_ms, 13);
});
test("parseMongoWriteCommand reads replaceOne as a filtered replace", () => {
assert.deepEqual(parseMongoWriteCommand('db.orders.replaceOne({_id: ObjectId("507f1f77bcf86cd799439011")}, {name: "new", tags: []}, {upsert: true})'), {
kind: "replace",
@@ -1249,8 +1309,8 @@ test("describeMongoCommandParseFailure explains the argument shape a known metho
});
test("describeMongoCommandParseFailure names unsupported methods and points at alternatives", () => {
const bulkWrite = describeMongoCommandParseFailure("db.c.bulkWrite([{insertOne: {document: {a: 1}}}])");
assert.match(bulkWrite, /^Collection method bulkWrite\(\) is not supported\. Supported collection methods: find, findOne/);
const rename = describeMongoCommandParseFailure('db.c.renameCollection("d")');
assert.match(rename, /^Collection method renameCollection\(\) is not supported\. Supported collection methods: find, findOne/);
// Bracket and getCollection targets are recognised too.
assert.match(describeMongoCommandParseFailure('db["my-coll"].renameCollection("x")'), /renameCollection\(\) is not supported/);
assert.match(describeMongoCommandParseFailure('db.getCollection("my-coll").watch()'), /watch\(\) is not supported/);
@@ -1261,7 +1321,7 @@ test("describeMongoCommandParseFailure names unsupported methods and points at a
assert.match(describeMongoCommandParseFailure("show collections"), /listed in the sidebar/);
// Leading comments do not hide the command shape.
assert.match(describeMongoCommandParseFailure("// note\ndb.c.bulkWrite([])"), /bulkWrite\(\) is not supported/);
assert.match(describeMongoCommandParseFailure("// note\ndb.c.mapReduce()"), /mapReduce\(\) is not supported/);
});
test("describeMongoCommandParseFailure reports unclosed delimiters and shell hints", () => {