fix(mongodb): support collection drop shell command
This commit is contained in:
parent
315c6e235b
commit
ca9e11217e
|
|
@ -46,6 +46,7 @@ public final class AgentProtocol {
|
|||
public static final String MONGO_METHOD_SERVER_VERSION = "server_version";
|
||||
public static final String MONGO_METHOD_CREATE_INDEX = "create_index";
|
||||
public static final String MONGO_METHOD_DROP_INDEXES = "drop_indexes";
|
||||
public static final String MONGO_METHOD_DROP_COLLECTION = "drop_collection";
|
||||
public static final String MONGO_METHOD_INSERT_DOCUMENT = "insert_document";
|
||||
public static final String MONGO_METHOD_UPDATE_DOCUMENT = "update_document";
|
||||
public static final String MONGO_METHOD_UPDATE_DOCUMENTS = "update_documents";
|
||||
|
|
@ -126,6 +127,7 @@ public final class AgentProtocol {
|
|||
MONGO_METHOD_SERVER_VERSION,
|
||||
MONGO_METHOD_CREATE_INDEX,
|
||||
MONGO_METHOD_DROP_INDEXES,
|
||||
MONGO_METHOD_DROP_COLLECTION,
|
||||
MONGO_METHOD_INSERT_DOCUMENT,
|
||||
MONGO_METHOD_UPDATE_DOCUMENT,
|
||||
MONGO_METHOD_UPDATE_DOCUMENTS,
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
"server_version",
|
||||
"create_index",
|
||||
"drop_indexes",
|
||||
"drop_collection",
|
||||
"insert_document",
|
||||
"update_document",
|
||||
"update_documents",
|
||||
|
|
|
|||
|
|
@ -504,6 +504,14 @@ public final class MongoAgent {
|
|||
return result;
|
||||
}
|
||||
|
||||
private static Object dropCollection(JsonObject params) {
|
||||
MongoClient c = requireClient();
|
||||
String database = params.get("database").getAsString();
|
||||
String collection = params.get("collection").getAsString();
|
||||
c.getDatabase(database).getCollection(collection).drop();
|
||||
return Collections.singletonMap("ok", true);
|
||||
}
|
||||
|
||||
private static Object parseDropIndexesValue(String indexesJson, boolean single) {
|
||||
if (indexesJson == null || indexesJson.isBlank()) {
|
||||
if (single) {
|
||||
|
|
@ -808,6 +816,7 @@ public final class MongoAgent {
|
|||
case AgentProtocol.MONGO_METHOD_SERVER_VERSION -> serverVersion(params);
|
||||
case AgentProtocol.MONGO_METHOD_CREATE_INDEX -> createIndex(params);
|
||||
case AgentProtocol.MONGO_METHOD_DROP_INDEXES -> dropIndexes(params);
|
||||
case AgentProtocol.MONGO_METHOD_DROP_COLLECTION -> dropCollection(params);
|
||||
case AgentProtocol.MONGO_METHOD_INSERT_DOCUMENT -> insertDocument(params);
|
||||
case AgentProtocol.MONGO_METHOD_UPDATE_DOCUMENT -> updateDocument(params);
|
||||
case AgentProtocol.MONGO_METHOD_UPDATE_DOCUMENTS -> updateDocuments(params);
|
||||
|
|
|
|||
|
|
@ -176,6 +176,19 @@ class MongoAgentTest {
|
|||
assertTrue(AgentProtocol.MONGO_LEGACY_METHODS.contains(AgentProtocol.MONGO_METHOD_DROP_INDEXES));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dropCollectionMethodIsRecognizedOverJsonRpc() {
|
||||
String response = MongoAgent.handleRequest(
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":14,\"method\":\"drop_collection\","
|
||||
+ "\"params\":{\"database\":\"app\",\"collection\":\"orders\"}}");
|
||||
|
||||
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
|
||||
assertEquals(14, json.get("id").getAsInt());
|
||||
assertEquals("Not connected", json.getAsJsonObject("error").get("message").getAsString());
|
||||
assertFalse(json.getAsJsonObject("error").get("message").getAsString().contains("Unknown method"));
|
||||
assertTrue(AgentProtocol.MONGO_LEGACY_METHODS.contains(AgentProtocol.MONGO_METHOD_DROP_COLLECTION));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateDocumentsMethodIsRecognizedOverJsonRpc() {
|
||||
String response = MongoAgent.handleRequest(
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export interface MongoCollectionStatsCommand {
|
|||
scale?: number;
|
||||
}
|
||||
|
||||
type MongoWriteKind = "insert" | "update" | "delete" | "createIndex" | "dropIndex" | "dropIndexes";
|
||||
type MongoWriteKind = "insert" | "update" | "delete" | "createIndex" | "dropIndex" | "dropIndexes" | "dropCollection";
|
||||
|
||||
export type MongoCommand =
|
||||
| ({ kind: "find" } & MongoFindCommand)
|
||||
|
|
@ -54,7 +54,8 @@ export type MongoCommand =
|
|||
| { kind: "delete"; collection: string; filter: string; many: boolean }
|
||||
| { kind: "createIndex"; collection: string; keys: string; options?: string }
|
||||
| { kind: "dropIndex"; collection: string; index: string }
|
||||
| { kind: "dropIndexes"; collection: string; indexes?: string };
|
||||
| { kind: "dropIndexes"; collection: string; indexes?: string }
|
||||
| { kind: "dropCollection"; collection: string };
|
||||
|
||||
export type MongoWriteCommand = Extract<MongoCommand, { kind: MongoWriteKind }>;
|
||||
|
||||
|
|
@ -340,6 +341,13 @@ export function parseMongoWriteCommand(input: string): MongoWriteCommand | null
|
|||
return indexes !== null ? { kind: "dropIndexes", collection: dropIndexes.collection, ...(indexes ? { indexes } : {}) } : null;
|
||||
}
|
||||
|
||||
const dropCollection = parseCollectionMethodTarget(source, "drop");
|
||||
if (dropCollection) {
|
||||
const args = parseMethodArgs(source, dropCollection.methodCallIndex);
|
||||
if (!args || args.some((arg) => arg.trim())) return null;
|
||||
return { kind: "dropCollection", collection: dropCollection.collection };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -427,6 +435,12 @@ export function evaluateMongoWriteSafety(command: MongoWriteCommand, options: Mo
|
|||
reason: "MongoDB dropIndexes() without a specific single index requires DBX_MCP_ALLOW_DANGEROUS_SQL=1.",
|
||||
};
|
||||
}
|
||||
if (!options.allowDangerous && command.kind === "dropCollection") {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: "MongoDB drop() requires DBX_MCP_ALLOW_DANGEROUS_SQL=1.",
|
||||
};
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2246,7 +2246,8 @@ export const useQueryStore = defineStore("query", () => {
|
|||
case "delete":
|
||||
case "createIndex":
|
||||
case "dropIndex":
|
||||
case "dropIndexes": {
|
||||
case "dropIndexes":
|
||||
case "dropCollection": {
|
||||
if (options?.mongoSafety) {
|
||||
const safety = evaluateMongoWriteSafety(mongoCommand, options.mongoSafety);
|
||||
if (!safety.allowed) throw new Error(safety.reason);
|
||||
|
|
@ -2270,6 +2271,9 @@ export const useQueryStore = defineStore("query", () => {
|
|||
} else if (mongoCommand.kind === "dropIndex" || mongoCommand.kind === "dropIndexes") {
|
||||
const result = await api.mongoDropIndexes(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.kind === "dropIndex" ? mongoCommand.index : mongoCommand.indexes, mongoCommand.kind === "dropIndex");
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoDroppedIndexesToQueryResult(result.dropped_names, performance.now() - commandStartedAt), sourceStatement)));
|
||||
} else if (mongoCommand.kind === "dropCollection") {
|
||||
await api.mongoDropCollection(tab.connectionId, currentDatabase, mongoCommand.collection);
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoWriteToQueryResult(1, performance.now() - commandStartedAt), sourceStatement)));
|
||||
} else {
|
||||
const result = await api.mongoDeleteDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.many);
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt), sourceStatement)));
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
"server_version",
|
||||
"create_index",
|
||||
"drop_indexes",
|
||||
"drop_collection",
|
||||
"insert_document",
|
||||
"update_document",
|
||||
"update_documents",
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ pub enum MongoAgentMethod {
|
|||
ServerVersion,
|
||||
CreateIndex,
|
||||
DropIndexes,
|
||||
DropCollection,
|
||||
InsertDocument,
|
||||
UpdateDocument,
|
||||
UpdateDocuments,
|
||||
|
|
@ -264,7 +265,7 @@ pub enum MongoAgentMethod {
|
|||
}
|
||||
|
||||
impl MongoAgentMethod {
|
||||
pub const ALL: [Self; 12] = [
|
||||
pub const ALL: [Self; 13] = [
|
||||
Self::ListDatabases,
|
||||
Self::ListCollections,
|
||||
Self::FindDocuments,
|
||||
|
|
@ -272,6 +273,7 @@ impl MongoAgentMethod {
|
|||
Self::ServerVersion,
|
||||
Self::CreateIndex,
|
||||
Self::DropIndexes,
|
||||
Self::DropCollection,
|
||||
Self::InsertDocument,
|
||||
Self::UpdateDocument,
|
||||
Self::UpdateDocuments,
|
||||
|
|
@ -288,6 +290,7 @@ impl MongoAgentMethod {
|
|||
Self::ServerVersion => "server_version",
|
||||
Self::CreateIndex => "create_index",
|
||||
Self::DropIndexes => "drop_indexes",
|
||||
Self::DropCollection => "drop_collection",
|
||||
Self::InsertDocument => "insert_document",
|
||||
Self::UpdateDocument => "update_document",
|
||||
Self::UpdateDocuments => "update_documents",
|
||||
|
|
@ -1024,6 +1027,13 @@ impl AgentDriverClient {
|
|||
self.call_mongo_method(MongoAgentMethod::DropIndexes, params).await
|
||||
}
|
||||
|
||||
pub async fn mongo_drop_collection<T: DeserializeOwned + Send + 'static>(
|
||||
&mut self,
|
||||
params: Value,
|
||||
) -> Result<T, String> {
|
||||
self.call_mongo_method(MongoAgentMethod::DropCollection, params).await
|
||||
}
|
||||
|
||||
pub async fn mongo_insert_document<T: DeserializeOwned + Send + 'static>(
|
||||
&mut self,
|
||||
params: Value,
|
||||
|
|
@ -1698,6 +1708,7 @@ mod tests {
|
|||
assert_eq!(MongoAgentMethod::ServerVersion.as_str(), "server_version");
|
||||
assert_eq!(MongoAgentMethod::CreateIndex.as_str(), "create_index");
|
||||
assert_eq!(MongoAgentMethod::DropIndexes.as_str(), "drop_indexes");
|
||||
assert_eq!(MongoAgentMethod::DropCollection.as_str(), "drop_collection");
|
||||
assert_eq!(MongoAgentMethod::InsertDocument.as_str(), "insert_document");
|
||||
assert_eq!(MongoAgentMethod::UpdateDocument.as_str(), "update_document");
|
||||
assert_eq!(MongoAgentMethod::UpdateDocuments.as_str(), "update_documents");
|
||||
|
|
@ -1744,6 +1755,7 @@ mod tests {
|
|||
let _mongo_server_version = AgentDriverClient::mongo_server_version::<serde_json::Value>;
|
||||
let _mongo_create_index = AgentDriverClient::mongo_create_index::<serde_json::Value>;
|
||||
let _mongo_drop_indexes = AgentDriverClient::mongo_drop_indexes::<serde_json::Value>;
|
||||
let _mongo_drop_collection = AgentDriverClient::mongo_drop_collection::<serde_json::Value>;
|
||||
let _mongo_insert_document = AgentDriverClient::mongo_insert_document::<serde_json::Value>;
|
||||
let _mongo_update_document = AgentDriverClient::mongo_update_document::<serde_json::Value>;
|
||||
let _mongo_update_documents = AgentDriverClient::mongo_update_documents::<serde_json::Value>;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,16 @@ pub async fn mongo_drop_collection_core(
|
|||
let connections = state.connections.read().await;
|
||||
match connections.get(connection_id).ok_or("Not found")? {
|
||||
PoolKind::MongoDb(client) => mongo_driver::drop_collection(client, database, collection).await,
|
||||
PoolKind::Agent(_) => Err("MongoDB legacy agent does not support drop collection".to_string()),
|
||||
PoolKind::Agent(client) => {
|
||||
let mut client = client.lock().await;
|
||||
let _: serde_json::Value = client
|
||||
.mongo_drop_collection(serde_json::json!({
|
||||
"database": database,
|
||||
"collection": collection,
|
||||
}))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err("Not a MongoDB connection".to_string()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,6 +184,25 @@ test("parseMongoWriteCommand parses dropIndex and dropIndexes variants", () => {
|
|||
});
|
||||
});
|
||||
|
||||
test("parseMongoWriteCommand parses collection drop commands", () => {
|
||||
assert.deepEqual(parseMongoWriteCommand("db.users.drop()"), {
|
||||
kind: "dropCollection",
|
||||
collection: "users",
|
||||
});
|
||||
assert.deepEqual(parseMongoWriteCommand('db.getCollection("audit.logs").drop();'), {
|
||||
kind: "dropCollection",
|
||||
collection: "audit.logs",
|
||||
});
|
||||
assert.deepEqual(parseMongoCommand("db.users.drop()")?.command, {
|
||||
kind: "dropCollection",
|
||||
collection: "users",
|
||||
});
|
||||
});
|
||||
|
||||
test("parseMongoWriteCommand rejects collection drop arguments", () => {
|
||||
assert.equal(parseMongoWriteCommand("db.users.drop({ writeConcern: 1 })"), null);
|
||||
});
|
||||
|
||||
test("parseMongoWriteCommand rejects invalid dropIndex/dropIndexes variants", () => {
|
||||
assert.equal(parseMongoWriteCommand("db.users.dropIndex()"), null);
|
||||
assert.equal(parseMongoWriteCommand('db.users.dropIndex("*")'), null);
|
||||
|
|
@ -191,6 +210,13 @@ test("parseMongoWriteCommand rejects invalid dropIndex/dropIndexes variants", ()
|
|||
assert.equal(parseMongoWriteCommand('db.users.dropIndexes([{"a":1}])'), null);
|
||||
});
|
||||
|
||||
test("evaluateMongoWriteSafety blocks collection drop unless dangerous writes are enabled", () => {
|
||||
const dropCollection = parseMongoWriteCommand("db.users.drop()");
|
||||
assert.ok(dropCollection);
|
||||
assert.match(evaluateMongoWriteSafety(dropCollection, { allowWrites: true }).reason || "", /DBX_MCP_ALLOW_DANGEROUS_SQL=1/);
|
||||
assert.equal(evaluateMongoWriteSafety(dropCollection, { allowWrites: true, allowDangerous: true }).allowed, true);
|
||||
});
|
||||
|
||||
test("evaluateMongoWriteSafety blocks dangerous dropIndexes shapes unless enabled", () => {
|
||||
const dropAll = parseMongoWriteCommand("db.users.dropIndexes()");
|
||||
assert.ok(dropAll);
|
||||
|
|
|
|||
|
|
@ -919,7 +919,7 @@ export async function executeQuery(config: ConnectionConfig, sql: string, option
|
|||
return { columns: [], rows: [], row_count: result.affectedRows };
|
||||
}
|
||||
throw new Error(
|
||||
'Use MongoDB shell-style commands, for example: db.projects.find({}).limit(100), db.version(), db.projects.countDocuments({}), db.projects.count({}), db.projects.getIndexes(), db.projects.dataSize(), db.projects.storageSize(1024), db.projects.totalIndexSize(), db.projects.stats(), db.projects.createIndex({...}), db.projects.dropIndex("name"), db.projects.dropIndexes(), db.projects.insertOne({...}), db.projects.updateOne({...}, {$set: {...}}), or db.projects.deleteOne({...})',
|
||||
'Use MongoDB shell-style commands, for example: db.projects.find({}).limit(100), db.version(), db.projects.countDocuments({}), db.projects.count({}), db.projects.getIndexes(), db.projects.dataSize(), db.projects.storageSize(1024), db.projects.totalIndexSize(), db.projects.stats(), db.projects.createIndex({...}), db.projects.dropIndex("name"), db.projects.dropIndexes(), db.projects.drop(), db.projects.insertOne({...}), db.projects.updateOne({...}, {$set: {...}}), or db.projects.deleteOne({...})',
|
||||
);
|
||||
}
|
||||
if (isDirectQueryType(config.db_type)) {
|
||||
|
|
@ -1167,6 +1167,14 @@ async function executeMongoWrite(config: ConnectionConfig, command: MongoWriteCo
|
|||
});
|
||||
return { affectedRows: result.affected_rows, droppedNames: result.dropped_names };
|
||||
}
|
||||
if (command.kind === "dropCollection") {
|
||||
await bridgeDataRequest<{ ok: boolean }>("/data/mongo/drop-collection", {
|
||||
connection_name: config.name,
|
||||
database: config.database || "",
|
||||
collection: command.collection,
|
||||
});
|
||||
return { affectedRows: 1 };
|
||||
}
|
||||
const result = await bridgeDataRequest<{ affected_rows: number }>("/data/mongo/delete-documents", {
|
||||
connection_name: config.name,
|
||||
database: config.database || "",
|
||||
|
|
@ -1288,7 +1296,8 @@ export type MongoWriteCommand =
|
|||
| { kind: "delete"; collection: string; filter: string; many: boolean }
|
||||
| { kind: "createIndex"; collection: string; keys: string; options?: string }
|
||||
| { kind: "dropIndex"; collection: string; index: string }
|
||||
| { kind: "dropIndexes"; collection: string; indexes?: string };
|
||||
| { kind: "dropIndexes"; collection: string; indexes?: string }
|
||||
| { kind: "dropCollection"; collection: string };
|
||||
|
||||
export function parseMongoFindCommand(input: string): MongoFindCommand | null {
|
||||
const source = input.trim().replace(/;$/, "").trim();
|
||||
|
|
@ -1480,6 +1489,13 @@ export function parseMongoWriteCommand(input: string): MongoWriteCommand | null
|
|||
return indexes !== null ? { kind: "dropIndexes", collection: dropIndexes.collection, ...(indexes ? { indexes } : {}) } : null;
|
||||
}
|
||||
|
||||
const dropCollection = parseCollectionMethodTarget(source, "drop");
|
||||
if (dropCollection) {
|
||||
const args = parseMethodArgs(source, dropCollection.methodCallIndex);
|
||||
if (!args || args.some((arg) => arg.trim())) return null;
|
||||
return { kind: "dropCollection", collection: dropCollection.collection };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -1502,6 +1518,12 @@ export function evaluateMongoWriteSafety(command: MongoWriteCommand, options: {
|
|||
reason: "MongoDB dropIndexes() without a specific single index requires DBX_MCP_ALLOW_DANGEROUS_SQL=1.",
|
||||
};
|
||||
}
|
||||
if (!options.allowDangerous && command.kind === "dropCollection") {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: "MongoDB drop() requires DBX_MCP_ALLOW_DANGEROUS_SQL=1.",
|
||||
};
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
import type { ConnectionConfig } from "./connections.js";
|
||||
import type { TableInfo, ColumnInfo, QueryOptions, QueryResult } from "./database.js";
|
||||
import { collectionListToTableInfos, evaluateMongoAggregateSafety, evaluateMongoWriteSafety, inferMongoColumns, mongoCollectionStatsToQueryResult, mongoDocumentsToQueryResult, parseMongoAggregateCommand, parseMongoCollectionStatsCommand, parseMongoCountDocumentsCommand, parseMongoFindCommand, parseMongoGetIndexesCommand, parseMongoVersionCommand, parseMongoWriteCommand, type CollectionInfo, type MongoWriteCommand } from "./database.js";
|
||||
import {
|
||||
collectionListToTableInfos,
|
||||
evaluateMongoAggregateSafety,
|
||||
evaluateMongoWriteSafety,
|
||||
inferMongoColumns,
|
||||
mongoCollectionStatsToQueryResult,
|
||||
mongoDocumentsToQueryResult,
|
||||
parseMongoAggregateCommand,
|
||||
parseMongoCollectionStatsCommand,
|
||||
parseMongoCountDocumentsCommand,
|
||||
parseMongoFindCommand,
|
||||
parseMongoGetIndexesCommand,
|
||||
parseMongoVersionCommand,
|
||||
parseMongoWriteCommand,
|
||||
type CollectionInfo,
|
||||
type MongoWriteCommand,
|
||||
} from "./database.js";
|
||||
import type { RedisCommandOptions, RedisCommandResult } from "./redis-command.js";
|
||||
import { sqlSafetyFromEnv } from "./sql-safety.js";
|
||||
|
||||
|
|
@ -288,7 +304,7 @@ export async function executeQuery(config: ConnectionConfig, sql: string, option
|
|||
return { columns: [], rows: [], row_count: result.affectedRows };
|
||||
}
|
||||
throw new Error(
|
||||
"Use MongoDB shell-style commands, for example: db.projects.find({}).limit(100), db.version(), db.projects.countDocuments({}), db.projects.count({}), db.projects.getIndexes(), db.projects.dataSize(), db.projects.storageSize(1024), db.projects.totalIndexSize(), db.projects.stats(), db.projects.createIndex({...}), db.projects.dropIndex(\"name\"), db.projects.dropIndexes(), db.projects.insertOne({...}), db.projects.updateOne({...}, {$set: {...}}), or db.projects.deleteOne({...})",
|
||||
'Use MongoDB shell-style commands, for example: db.projects.find({}).limit(100), db.version(), db.projects.countDocuments({}), db.projects.count({}), db.projects.getIndexes(), db.projects.dataSize(), db.projects.storageSize(1024), db.projects.totalIndexSize(), db.projects.stats(), db.projects.createIndex({...}), db.projects.dropIndex("name"), db.projects.dropIndexes(), db.projects.drop(), db.projects.insertOne({...}), db.projects.updateOne({...}, {$set: {...}}), or db.projects.deleteOne({...})',
|
||||
);
|
||||
}
|
||||
const res = await apiFetch("/api/query/execute", {
|
||||
|
|
@ -328,10 +344,7 @@ export async function executeRedisCommand(config: ConnectionConfig, db: number,
|
|||
return (await res.json()) as RedisCommandResult;
|
||||
}
|
||||
|
||||
async function executeMongoWrite(
|
||||
config: ConnectionConfig,
|
||||
command: MongoWriteCommand,
|
||||
): Promise<{ affectedRows: number; indexName?: string; droppedNames?: string[] }> {
|
||||
async function executeMongoWrite(config: ConnectionConfig, command: MongoWriteCommand): Promise<{ affectedRows: number; indexName?: string; droppedNames?: string[] }> {
|
||||
if (command.kind === "insert") {
|
||||
const res = await apiFetch("/api/mongo/insert-documents", {
|
||||
method: "POST",
|
||||
|
|
@ -388,6 +401,17 @@ async function executeMongoWrite(
|
|||
const result = (await res.json()) as { dropped_names: string[]; affected_rows: number };
|
||||
return { affectedRows: result.affected_rows, droppedNames: result.dropped_names };
|
||||
}
|
||||
if (command.kind === "dropCollection") {
|
||||
await apiFetch("/api/mongo/drop-collection", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
connectionId: config.id,
|
||||
database: config.database || "",
|
||||
collection: command.collection,
|
||||
}),
|
||||
});
|
||||
return { affectedRows: 1 };
|
||||
}
|
||||
const res = await apiFetch("/api/mongo/delete-documents", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,19 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
import { executeQuery, inferMongoColumns, mongoAggregateWriteStage, mongoCollectionStatsToQueryResult, mongoDocumentsToQueryResult, parseMongoAggregateCommand, parseMongoCollectionStatsCommand, parseMongoCountDocumentsCommand, parseMongoFindCommand, parseMongoGetIndexesCommand, parseMongoVersionCommand, parseMongoWriteCommand } from "../src/database.js";
|
||||
import {
|
||||
executeQuery,
|
||||
inferMongoColumns,
|
||||
mongoAggregateWriteStage,
|
||||
mongoCollectionStatsToQueryResult,
|
||||
mongoDocumentsToQueryResult,
|
||||
parseMongoAggregateCommand,
|
||||
parseMongoCollectionStatsCommand,
|
||||
parseMongoCountDocumentsCommand,
|
||||
parseMongoFindCommand,
|
||||
parseMongoGetIndexesCommand,
|
||||
parseMongoVersionCommand,
|
||||
parseMongoWriteCommand,
|
||||
} from "../src/database.js";
|
||||
|
||||
test("parseMongoFindCommand accepts shell-style find commands", () => {
|
||||
assert.deepEqual(parseMongoFindCommand('db.getCollection("operation_logs").find({"level":"info"}).sort({"ts":-1}).skip(5).limit(10)'), {
|
||||
|
|
@ -233,6 +246,14 @@ test("parseMongoWriteCommand accepts supported write commands", () => {
|
|||
collection: "projects",
|
||||
indexes: '["a_1","b_1"]',
|
||||
});
|
||||
assert.deepEqual(parseMongoWriteCommand("db.projects.drop()"), {
|
||||
kind: "dropCollection",
|
||||
collection: "projects",
|
||||
});
|
||||
assert.deepEqual(parseMongoWriteCommand('db.getCollection("audit.logs").drop();'), {
|
||||
kind: "dropCollection",
|
||||
collection: "audit.logs",
|
||||
});
|
||||
});
|
||||
|
||||
test("parseMongoWriteCommand rejects invalid dropIndex and dropIndexes commands", () => {
|
||||
|
|
@ -240,6 +261,7 @@ test("parseMongoWriteCommand rejects invalid dropIndex and dropIndexes commands"
|
|||
assert.equal(parseMongoWriteCommand('db.projects.dropIndex("*")'), null);
|
||||
assert.equal(parseMongoWriteCommand('db.projects.dropIndex(["a_1"])'), null);
|
||||
assert.equal(parseMongoWriteCommand('db.projects.dropIndexes([{"email":1}])'), null);
|
||||
assert.equal(parseMongoWriteCommand("db.projects.drop({ writeConcern: 1 })"), null);
|
||||
});
|
||||
|
||||
test("mongodb executeQuery blocks writes when writes are explicitly disabled", async () => {
|
||||
|
|
@ -339,6 +361,7 @@ test("mongodb executeQuery blocks dangerous dropIndexes shapes until dangerous S
|
|||
await assert.rejects(executeQuery(config, "db.projects.dropIndexes()"), /DBX_MCP_ALLOW_DANGEROUS_SQL=1/);
|
||||
await assert.rejects(executeQuery(config, 'db.projects.dropIndexes("*")'), /DBX_MCP_ALLOW_DANGEROUS_SQL=1/);
|
||||
await assert.rejects(executeQuery(config, 'db.projects.dropIndexes(["a_1","b_1"])'), /DBX_MCP_ALLOW_DANGEROUS_SQL=1/);
|
||||
await assert.rejects(executeQuery(config, "db.projects.drop()"), /DBX_MCP_ALLOW_DANGEROUS_SQL=1/);
|
||||
|
||||
if (oldAllowWrites === undefined) delete process.env.DBX_MCP_ALLOW_WRITES;
|
||||
else process.env.DBX_MCP_ALLOW_WRITES = oldAllowWrites;
|
||||
|
|
|
|||
|
|
@ -98,6 +98,13 @@ struct MongoDropIndexesRequest {
|
|||
single: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MongoDropCollectionRequest {
|
||||
connection_name: String,
|
||||
database: Option<String>,
|
||||
collection: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MongoInsertDocumentsRequest {
|
||||
connection_name: String,
|
||||
|
|
@ -203,6 +210,8 @@ pub fn start(app_handle: AppHandle, state: Arc<AppState>, data_dir: PathBuf) {
|
|||
handle_mongo_create_index_data(&st, body, &mut stream).await;
|
||||
} else if first_line.starts_with("POST /data/mongo/drop-indexes") {
|
||||
handle_mongo_drop_indexes_data(&st, body, &mut stream).await;
|
||||
} else if first_line.starts_with("POST /data/mongo/drop-collection") {
|
||||
handle_mongo_drop_collection_data(&st, body, &mut stream).await;
|
||||
} else if first_line.starts_with("POST /data/mongo/insert-documents") {
|
||||
handle_mongo_insert_documents_data(&st, body, &mut stream).await;
|
||||
} else if first_line.starts_with("POST /data/mongo/update-documents") {
|
||||
|
|
@ -628,6 +637,29 @@ async fn handle_mongo_drop_indexes_data(state: &Arc<AppState>, body: &str, strea
|
|||
}
|
||||
}
|
||||
|
||||
async fn handle_mongo_drop_collection_data(state: &Arc<AppState>, body: &str, stream: &mut tokio::net::TcpStream) {
|
||||
let req: MongoDropCollectionRequest = match serde_json::from_str(body) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
respond_error(stream, "400 Bad Request", "Invalid JSON").await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let Some((pool_key, database, connection_id)) =
|
||||
resolve_mongo_pool_key(state, &req.connection_name, req.database, stream).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Err(e) = ensure_connection_writable(state, &connection_id, "Drop collection").await {
|
||||
respond_error(stream, "403 Forbidden", &e).await;
|
||||
return;
|
||||
}
|
||||
match dbx_core::mongo_ops::mongo_drop_collection_core(state, &pool_key, &database, &req.collection).await {
|
||||
Ok(()) => respond_json(stream, &serde_json::json!({ "ok": true })).await,
|
||||
Err(e) => respond_error(stream, "500 Internal Server Error", &e).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_mongo_insert_documents_data(state: &Arc<AppState>, body: &str, stream: &mut tokio::net::TcpStream) {
|
||||
let req: MongoInsertDocumentsRequest = match serde_json::from_str(body) {
|
||||
Ok(r) => r,
|
||||
|
|
|
|||
Loading…
Reference in New Issue