feat(mongodb): support bulkWrite
This commit is contained in:
1 parent
3f3c21aae9
commit
f78dc1ba16
25 files changed
+1089
-18
No files matched your search
+2
-1
@@ -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
@@ -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", () => {
|
||||
|
||||
Reference in new issue
Block a user