diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index 20f749ebd..4a34f7de9 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -448,7 +448,9 @@ const { const { canDropMongoDatabase, + canDropMilvusDatabase, canDropMongoCollection, + canDropMilvusCollection, canRenameMongoCollection, canCloneMongoCollection, prepareRenameMongoCollectionDialog, @@ -497,6 +499,7 @@ const { openEditNacosNamespaceDialog, confirmEditNacosNamespace, dropMongoCollection, + dropMilvusCollection, dropMongoIndex, dropAllMongoIndexes, flushRedisDb, @@ -509,6 +512,8 @@ const { confirmFlushRedisDb, confirmDropMongoDatabase, confirmDropMongoCollection, + confirmDropMilvusDatabase, + confirmDropMilvusCollection, confirmDropMongoIndex, confirmDropAllMongoIndexes, } = useSidebarDatabaseSpecificMutationRuntime({ activeNode, connectionStore }); @@ -1285,10 +1290,18 @@ function requestDeleteSelectedNode(): boolean { dropDatabase(); return true; } + if (canDropMilvusDatabase.value) { + dropDatabase(); + return true; + } if (canDropMongoCollection.value) { dropMongoCollection(); return true; } + if (canDropMilvusCollection.value) { + dropMilvusCollection(); + return true; + } if (canDropSchema.value) { dropSchema(); return true; @@ -2945,6 +2958,10 @@ async function refreshDropDatabasePreviewSql() { dropDatabasePreviewSql.value = `db.getSiblingDB(${JSON.stringify(node.label)}).dropDatabase();`; return; } + if (node.type === "vector-database") { + dropDatabasePreviewSql.value = `POST /v2/vectordb/databases/drop\n{"dbName":${JSON.stringify(node.database || node.label)}}`; + return; + } dropDatabasePreviewSql.value = ""; dropDatabasePreviewSql.value = await buildDropDatabaseSql({ databaseType: currentDatabaseType(), @@ -3513,6 +3530,10 @@ async function confirmDropDatabase() { await confirmDropMongoDatabase(); return; } + if (node.type === "vector-database") { + await confirmDropMilvusDatabase(); + return; + } const connectionId = node.connectionId; if (!connectionId || dropDatabaseLoading.value) return; @@ -4250,7 +4271,7 @@ routeDangerDialog(showDropMongoCollectionConfirm, () => return dropMongoCollectionLoading.value; }, closeOnConfirm: false, - confirm: confirmDropMongoCollection, + confirm: () => (activeNode.value.type === "vector-collection" ? confirmDropMilvusCollection() : confirmDropMongoCollection()), }), ); @@ -5040,7 +5061,7 @@ function buildSpecialSidebarMenu(context: SidebarMenuFactoryContext): boolean { return true; } - if (node.type === "redis-db" || node.type === "mongo-db") { + if (node.type === "redis-db" || node.type === "mongo-db" || node.type === "vector-database") { items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare }); if (!isNodeDefaultDatabase.value) { items.push({ label: t("contextMenu.setDefaultDatabase"), action: setNodeAsDefaultDatabase, icon: Database }); @@ -5056,7 +5077,7 @@ function buildSpecialSidebarMenu(context: SidebarMenuFactoryContext): boolean { items.push({ label: t("redis.setDatabaseAlias"), action: openRedisDatabaseAliasDialog, icon: Pencil }); items.push({ label: t("redis.flushDb"), action: flushRedisDb, icon: Eraser, variant: "destructive" as const }); } - if (canDropMongoDatabase.value) { + if (canDropMongoDatabase.value || canDropMilvusDatabase.value) { items.push({ label: "", separator: true }); items.push({ label: t("contextMenu.dropDatabase"), @@ -5125,6 +5146,10 @@ function buildSpecialSidebarMenu(context: SidebarMenuFactoryContext): boolean { items.push({ label: "", separator: true }); items.push({ label: t("contextMenu.viewData"), action: toggle, icon: TableProperties }); items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare }); + if (canDropMilvusCollection.value) { + items.push({ label: "", separator: true }); + items.push({ label: t("contextMenu.dropCollection"), action: dropMilvusCollection, icon: Trash2, shortcut: shortcutDelete, variant: "destructive" as const }); + } return true; } diff --git a/apps/desktop/src/composables/__tests__/useSidebarConnectionMutationRuntime.selection.spec.ts b/apps/desktop/src/composables/__tests__/useSidebarConnectionMutationRuntime.selection.spec.ts index ee1fc75e0..29204c129 100644 --- a/apps/desktop/src/composables/__tests__/useSidebarConnectionMutationRuntime.selection.spec.ts +++ b/apps/desktop/src/composables/__tests__/useSidebarConnectionMutationRuntime.selection.spec.ts @@ -40,6 +40,18 @@ function connectionGroupNode(groupId: string): TreeNode { }; } +function vectorDatabaseNode(database: string): TreeNode { + return { + id: `conn-1:${database}`, + label: database, + type: "vector-database", + connectionId: "conn-1", + database, + isExpanded: false, + children: [], + }; +} + function connectionStore(selectedTreeNodeIds: string[]) { const lastSelectedTreeNodeId = selectedTreeNodeIds[selectedTreeNodeIds.length - 1] ?? null; return { @@ -57,6 +69,7 @@ function connectionStore(selectedTreeNodeIds: string[]) { disconnect: vi.fn().mockResolvedValue(undefined), isTreeNodeChildrenLoaded: vi.fn(() => false), getConfig: vi.fn(() => undefined), + isDefaultDatabase: vi.fn(() => false), }; } @@ -73,6 +86,22 @@ function runtime(activeNode: TreeNode, store: ReturnType }); } +describe("sidebar default database state", () => { + beforeEach(() => { + vi.clearAllMocks(); + sidebarFormTarget.value = null; + }); + + it("recognizes the configured Milvus vector database as default", () => { + const store = connectionStore([]); + store.isDefaultDatabase.mockReturnValue(true); + const { isNodeDefaultDatabase } = runtime(vectorDatabaseNode("analytics"), store); + + expect(isNodeDefaultDatabase.value).toBe(true); + expect(store.isDefaultDatabase).toHaveBeenCalledWith("conn-1", "analytics"); + }); +}); + describe("sidebar connection move selection", () => { beforeEach(() => { vi.clearAllMocks(); diff --git a/apps/desktop/src/composables/__tests__/useSidebarDatabaseSpecificMutationRuntime.mongo.spec.ts b/apps/desktop/src/composables/__tests__/useSidebarDatabaseSpecificMutationRuntime.mongo.spec.ts index 2b0e99208..839ed3c5e 100644 --- a/apps/desktop/src/composables/__tests__/useSidebarDatabaseSpecificMutationRuntime.mongo.spec.ts +++ b/apps/desktop/src/composables/__tests__/useSidebarDatabaseSpecificMutationRuntime.mongo.spec.ts @@ -89,6 +89,31 @@ function mongoConfig(driverProfile?: string, production = false) { }; } +function milvusConfig(database?: string) { + return { + id: "conn-1", + name: "Milvus", + db_type: "milvus" as const, + host: "localhost", + port: 19530, + username: "", + password: "", + database, + }; +} + +function milvusDatabaseNode(database: string): TreeNode { + return { + id: `conn-1:${database}`, + label: database, + type: "vector-database", + connectionId: "conn-1", + database, + isExpanded: false, + children: [], + }; +} + function mongoDatabaseNode(): TreeNode { return { id: "conn-1:app", @@ -209,6 +234,29 @@ describe("MongoDB sidebar mutation runtime", () => { cloneMongoCollectionLoading.value = false; }); + it("protects the built-in and configured Milvus databases from deletion", () => { + mocks.getConfig.mockReturnValue(milvusConfig("analytics")); + const activeNode = shallowRef(milvusDatabaseNode("default")); + const feature = useSidebarDatabaseSpecificMutationRuntime({ + activeNode, + connectionStore: { + getConfig: mocks.getConfig, + } as any, + }); + + expect(feature.canDropMilvusDatabase.value).toBe(false); + + activeNode.value = milvusDatabaseNode("analytics"); + expect(feature.canDropMilvusDatabase.value).toBe(false); + + activeNode.value = milvusDatabaseNode("archive"); + expect(feature.canDropMilvusDatabase.value).toBe(true); + + mocks.getConfig.mockReturnValue(mongoConfig()); + activeNode.value = milvusDatabaseNode("archive-2"); + expect(feature.canDropMilvusDatabase.value).toBe(false); + }); + it("keeps Legacy MongoDB mutations available while limiting index actions to the Indexes group", () => { mocks.getConfig.mockReturnValue(mongoConfig("mongodb-legacy")); const activeNode = shallowRef(mongoDatabaseNode()); diff --git a/apps/desktop/src/composables/useSidebarConnectionMutationRuntime.ts b/apps/desktop/src/composables/useSidebarConnectionMutationRuntime.ts index 054813ab9..e3e263ffa 100644 --- a/apps/desktop/src/composables/useSidebarConnectionMutationRuntime.ts +++ b/apps/desktop/src/composables/useSidebarConnectionMutationRuntime.ts @@ -290,7 +290,11 @@ export function useSidebarConnectionMutationRuntime(options: SidebarConnectionMu const isPinned = computed(() => activeNode.value.pinned || connectionStore.isTreeNodePinned(activeNode.value)); const isNodeDefaultDatabase = computed( - () => (activeNode.value.type === "database" || activeNode.value.type === "redis-db" || activeNode.value.type === "mongo-db") && !!activeNode.value.connectionId && !!activeNode.value.database && connectionStore.isDefaultDatabase(activeNode.value.connectionId, activeNode.value.database), + () => + (activeNode.value.type === "database" || activeNode.value.type === "redis-db" || activeNode.value.type === "mongo-db" || activeNode.value.type === "vector-database") && + !!activeNode.value.connectionId && + !!activeNode.value.database && + connectionStore.isDefaultDatabase(activeNode.value.connectionId, activeNode.value.database), ); const isNodeDefaultSchema = computed(() => activeNode.value.type === "schema" && !!activeNode.value.connectionId && !!activeNode.value.schema && connectionStore.isDefaultSchema(activeNode.value.connectionId, activeNode.value.schema)); const isConnected = computed(() => activeNode.value.type === "connection" && !!activeNode.value.connectionId && connectionStore.connectedIds.has(activeNode.value.connectionId)); diff --git a/apps/desktop/src/composables/useSidebarDatabaseSpecificMutationRuntime.ts b/apps/desktop/src/composables/useSidebarDatabaseSpecificMutationRuntime.ts index d207d288e..e01735550 100644 --- a/apps/desktop/src/composables/useSidebarDatabaseSpecificMutationRuntime.ts +++ b/apps/desktop/src/composables/useSidebarDatabaseSpecificMutationRuntime.ts @@ -117,6 +117,15 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba } const canDropMongoDatabase = computed(() => activeNode.value.type === "mongo-db" && !!activeNode.value.database && usesAnyMongoDriver(activeNode.value)); + function canDropMilvusDatabaseNode(node: TreeNode): boolean { + const connectionId = node.connectionId; + const database = node.database; + if (node.type !== "vector-database" || !connectionId || !database || database === "default") return false; + const config = connectionStore.getConfig(connectionId); + return config?.db_type === "milvus" && config.database !== database; + } + + const canDropMilvusDatabase = computed(() => canDropMilvusDatabaseNode(activeNode.value)); function canMutateMongoCollectionNode(node: TreeNode): boolean { if (node.type !== "mongo-collection" || !node.connectionId || !node.database) return false; @@ -132,6 +141,7 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba } const canDropMongoCollection = computed(() => canMutateMongoCollectionNode(activeNode.value)); + const canDropMilvusCollection = computed(() => activeNode.value.type === "vector-collection" && !!activeNode.value.connectionId && !!activeNode.value.database && connectionStore.getConfig(activeNode.value.connectionId)?.db_type === "milvus"); const canRenameMongoCollection = computed(() => canRenameMongoCollectionNode(activeNode.value)); const canCloneMongoCollection = computed(() => canCloneMongoCollectionNode(activeNode.value)); @@ -732,6 +742,10 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba showDropMongoCollectionConfirm.value = true; } + function dropMilvusCollection() { + dropMongoCollection(); + } + function dropMongoIndex() { dropMongoIndexLoading.value = false; showDropMongoIndexConfirm.value = true; @@ -836,8 +850,6 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba toast(t("contextMenu.dropCollectionSuccess", { name: collectionName }), 3000); showDropMongoCollectionConfirm.value = false; await refreshMongoTreeAfterDrop(node, async () => { - // The final collection can remove its database; if the database - // remains, refresh its preserved expanded children as well. await connectionStore.loadMongoDatabases(connectionId); await connectionStore.loadMongoCollections(connectionId, database); }); @@ -846,6 +858,54 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba }); } + async function confirmDropMilvusDatabase() { + const node = sidebarDangerTarget.value ?? activeNode.value; + const connectionId = node.connectionId; + const database = node.database; + if (!connectionId || !database || !canDropMilvusDatabaseNode(node)) return; + await runMongoSidebarMutation({ + connection: connectionStore.getConfig(connectionId), + database, + reviewText: `POST /v2/vectordb/databases/drop\n{"dbName":${JSON.stringify(database)}}`, + source: t("production.sourceSidebar"), + loading: dropDatabaseLoading, + beforeExecute: () => connectionStore.ensureConnected(connectionId), + execute: () => api.vectorDropDatabase(connectionId, database), + onSuccess: async () => { + toast(t("contextMenu.dropDatabaseSuccess", { name: node.label }), 3000); + showDropDatabaseConfirm.value = false; + await refreshMongoTreeAfterDrop(node, () => connectionStore.loadMilvusDatabases(connectionId)); + }, + onError: toastMutationError, + }); + } + + async function confirmDropMilvusCollection() { + const node = sidebarDangerTarget.value ?? activeNode.value; + const connectionId = node.connectionId; + const database = node.database; + if (node.type !== "vector-collection" || !connectionId || !database || connectionStore.getConfig(connectionId)?.db_type !== "milvus") return; + const collectionName = node.label; + await runMongoSidebarMutation({ + connection: connectionStore.getConfig(connectionId), + database, + reviewText: `POST /v2/vectordb/collections/drop\n{"dbName":${JSON.stringify(database)},"collectionName":${JSON.stringify(collectionName)}}`, + source: t("production.sourceSidebar"), + loading: dropMongoCollectionLoading, + beforeExecute: () => connectionStore.ensureConnected(connectionId), + execute: () => api.vectorDropCollection(connectionId, database, collectionName), + onSuccess: async () => { + toast(t("contextMenu.dropCollectionSuccess", { name: collectionName }), 3000); + showDropMongoCollectionConfirm.value = false; + await refreshMongoTreeAfterDrop(node, async () => { + await connectionStore.loadMilvusDatabases(connectionId); + await connectionStore.loadVectorCollections(connectionId, database); + }); + }, + onError: toastMutationError, + }); + } + async function refreshMongoTreeAfterDrop(node: TreeNode, refresh: () => Promise) { try { await refresh(); @@ -937,7 +997,9 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba return { canDropMongoDatabase, + canDropMilvusDatabase, canDropMongoCollection, + canDropMilvusCollection, canRenameMongoCollection, canCloneMongoCollection, prepareRenameMongoCollectionDialog, @@ -998,6 +1060,9 @@ export function useSidebarDatabaseSpecificMutationRuntime(options: SidebarDataba confirmFlushRedisDb, confirmDropMongoDatabase, confirmDropMongoCollection, + confirmDropMilvusDatabase, + confirmDropMilvusCollection, + dropMilvusCollection, confirmDropMongoIndex, confirmDropAllMongoIndexes, }; diff --git a/apps/desktop/src/lib/backend/api.ts b/apps/desktop/src/lib/backend/api.ts index d4e755cbe..d025c5a67 100644 --- a/apps/desktop/src/lib/backend/api.ts +++ b/apps/desktop/src/lib/backend/api.ts @@ -700,6 +700,8 @@ export const documentDownloadGridFsFile = forward("documentDownloadGridFsFile"); export const documentUploadGridFsFile = forward("documentUploadGridFsFile"); export const documentDeleteGridFsFile = forward("documentDeleteGridFsFile"); export const vectorGetCollectionDetail = forward("vectorGetCollectionDetail"); +export const vectorDropDatabase = forward("vectorDropDatabase"); +export const vectorDropCollection = forward("vectorDropCollection"); export const mongoCreateDatabase = forward("mongoCreateDatabase"); export const mongoDropDatabase = forward("mongoDropDatabase"); export const mongoDropCollection = forward("mongoDropCollection"); diff --git a/apps/desktop/src/lib/backend/http.ts b/apps/desktop/src/lib/backend/http.ts index 6ddee2eb6..eddee802c 100644 --- a/apps/desktop/src/lib/backend/http.ts +++ b/apps/desktop/src/lib/backend/http.ts @@ -3589,6 +3589,14 @@ export async function vectorGetCollectionDetail(connectionId: string, database: }); } +export async function vectorDropDatabase(connectionId: string, database: string): Promise { + await post("/api/vector/drop-database", { connectionId, database }); +} + +export async function vectorDropCollection(connectionId: string, database: string, collection: string): Promise { + await post("/api/vector/drop-collection", { connectionId, database, collection }); +} + export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, projection?: string, sort?: string, collation?: string, executionId?: string): Promise { return documentFindDocuments(connectionId, database, collection, skip, limit, filter, projection, sort, collation, executionId); } diff --git a/apps/desktop/src/lib/backend/tauri.ts b/apps/desktop/src/lib/backend/tauri.ts index 7e8248d96..9e41ea591 100644 --- a/apps/desktop/src/lib/backend/tauri.ts +++ b/apps/desktop/src/lib/backend/tauri.ts @@ -3504,6 +3504,14 @@ export async function mongoCloneCollection(connectionId: string, database: strin }); } +export async function vectorDropDatabase(connectionId: string, database: string): Promise { + return invoke("vector_drop_database", { connectionId, database }); +} + +export async function vectorDropCollection(connectionId: string, database: string, collection: string): Promise { + return invoke("vector_drop_collection", { connectionId, database, collection }); +} + export async function elasticsearchListIndices(connectionId: string): Promise { const collections = await documentListCollections(connectionId, "default"); return collections.map((c) => c.name); diff --git a/crates/dbx-core/src/db/vector_driver.rs b/crates/dbx-core/src/db/vector_driver.rs index 91be854c2..ce82bd890 100644 --- a/crates/dbx-core/src/db/vector_driver.rs +++ b/crates/dbx-core/src/db/vector_driver.rs @@ -239,6 +239,31 @@ pub async fn list_databases(client: &VectorClient) -> Result, String } } +/// Drop a Milvus database through the v2 REST API. +pub async fn drop_database(client: &VectorClient, database: &str) -> Result<(), String> { + if client.kind != VectorDbKind::Milvus { + return Err("Database deletion is only supported for Milvus connections".to_string()); + } + send_json(client.post("/v2/vectordb/databases/drop").json(&serde_json::json!({ "dbName": database })), client.kind) + .await + .map(|_| ()) +} + +/// Drop a Milvus collection through the v2 REST API. +pub async fn drop_collection(client: &VectorClient, database: &str, collection: &str) -> Result<(), String> { + if client.kind != VectorDbKind::Milvus { + return Err("Collection deletion is only supported for Milvus connections".to_string()); + } + send_json( + client + .post("/v2/vectordb/collections/drop") + .json(&serde_json::json!({ "dbName": database, "collectionName": collection })), + client.kind, + ) + .await + .map(|_| ()) +} + async fn list_milvus_databases(client: &VectorClient) -> Result, String> { // Older Milvus versions (pre-2.2) do not expose the databases endpoint; fall back to the // configured database (or "default") so the connection stays browsable instead of failing the whole tree load. @@ -254,11 +279,15 @@ async fn list_milvus_databases(client: &VectorClient) -> Result, Str } fn milvus_database_names(body: &Value, configured_database: &str) -> Vec { + let has_database_list = body.get("data").is_some_and(Value::is_array); let mut names: Vec = match body.get("data") { Some(Value::Array(items)) => items.iter().filter_map(milvus_database_name_from_item).collect(), _ => Vec::new(), }; - if !names.iter().any(|name| name == configured_database) { + // Only use the configured database as a compatibility fallback when the + // server does not return a database list. This lets a successfully dropped + // database disappear from the sidebar instead of being re-added locally. + if !has_database_list && !names.iter().any(|name| name == configured_database) { names.push(configured_database.to_string()); } names.sort(); @@ -1061,11 +1090,8 @@ mod tests { } #[test] - fn milvus_database_list_keeps_the_configured_database() { - assert_eq!( - milvus_database_names(&json!({ "data": ["default"] }), "resume_test"), - vec!["default".to_string(), "resume_test".to_string()] - ); + fn milvus_database_list_does_not_readd_deleted_configured_database() { + assert_eq!(milvus_database_names(&json!({ "data": ["default"] }), "resume_test"), vec!["default".to_string()]); assert_eq!( milvus_database_names(&json!({ "data": ["resume_test"] }), "resume_test"), vec!["resume_test".to_string()] diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs index 396dc19ae..ab5d9544b 100644 --- a/crates/dbx-core/src/schema.rs +++ b/crates/dbx-core/src/schema.rs @@ -1089,6 +1089,35 @@ pub async fn get_vector_collection_detail_core( db::vector_driver::get_collection_detail(&client, database, collection).await } +pub async fn drop_vector_database_core(state: &AppState, connection_id: &str, database: &str) -> Result<(), String> { + let pool_key = state.get_or_create_metadata_pool_for_session(connection_id, Some(database), None).await?; + let client = { + let connections = state.connections.read().await; + match connections.get(&pool_key) { + Some(PoolKind::VectorDb(client)) => client.clone(), + _ => return Err("Not a vector database connection".to_string()), + } + }; + db::vector_driver::drop_database(&client, database).await +} + +pub async fn drop_vector_collection_core( + state: &AppState, + connection_id: &str, + database: &str, + collection: &str, +) -> Result<(), String> { + let pool_key = state.get_or_create_metadata_pool_for_session(connection_id, Some(database), None).await?; + let client = { + let connections = state.connections.read().await; + match connections.get(&pool_key) { + Some(PoolKind::VectorDb(client)) => client.clone(), + _ => return Err("Not a vector database connection".to_string()), + } + }; + db::vector_driver::drop_collection(&client, database, collection).await +} + pub async fn get_table_comment_core( state: &AppState, connection_id: &str, diff --git a/crates/dbx-web/src/main.rs b/crates/dbx-web/src/main.rs index 813654680..b642e1b37 100644 --- a/crates/dbx-web/src/main.rs +++ b/crates/dbx-web/src/main.rs @@ -776,6 +776,8 @@ async fn main() { .route("/mongo/list-databases", post(routes::mongo::list_databases)) .route("/mongo/list-collections", post(routes::mongo::list_collections)) .route("/mongo/vector-collection-detail", post(routes::mongo::vector_collection_detail)) + .route("/vector/drop-database", post(routes::mongo::vector_drop_database)) + .route("/vector/drop-collection", post(routes::mongo::vector_drop_collection)) .route("/mongo/create-database", post(routes::mongo::create_database)) .route("/mongo/drop-database", post(routes::mongo::drop_database)) .route("/mongo/drop-collection", post(routes::mongo::drop_collection)) diff --git a/crates/dbx-web/src/routes/mongo.rs b/crates/dbx-web/src/routes/mongo.rs index ff18c72a9..8b63016d6 100644 --- a/crates/dbx-web/src/routes/mongo.rs +++ b/crates/dbx-web/src/routes/mongo.rs @@ -367,6 +367,34 @@ pub async fn vector_collection_detail( Ok(Json(result)) } +pub async fn vector_drop_database( + State(state): State>, + headers: HeaderMap, + Json(req): Json, +) -> Result, AppError> { + super::mcp_policy::ensure_dangerous_write(&state, &headers, &req.connection_id, &req.database, "Drop database") + .await?; + ensure_writable(&state.app, &req.connection_id, "Drop database").await?; + dbx_core::schema::drop_vector_database_core(&state.app, &req.connection_id, &req.database) + .await + .map_err(AppError::from)?; + Ok(Json(serde_json::json!({ "ok": true }))) +} + +pub async fn vector_drop_collection( + State(state): State>, + headers: HeaderMap, + Json(req): Json, +) -> Result, AppError> { + super::mcp_policy::ensure_dangerous_write(&state, &headers, &req.connection_id, &req.database, "Drop collection") + .await?; + ensure_writable(&state.app, &req.connection_id, "Drop collection").await?; + dbx_core::schema::drop_vector_collection_core(&state.app, &req.connection_id, &req.database, &req.collection) + .await + .map_err(AppError::from)?; + Ok(Json(serde_json::json!({ "ok": true }))) +} + pub async fn create_database( State(state): State>, Json(req): Json, diff --git a/src-tauri/src/commands/mongo_cmd.rs b/src-tauri/src/commands/mongo_cmd.rs index f88a7594a..a6828f96a 100644 --- a/src-tauri/src/commands/mongo_cmd.rs +++ b/src-tauri/src/commands/mongo_cmd.rs @@ -86,6 +86,27 @@ pub async fn mongo_drop_collection( dbx_core::mongo_ops::mongo_drop_collection_core(&state, &connection_id, &database, &collection).await } +#[tauri::command] +pub async fn vector_drop_database( + state: State<'_, Arc>, + connection_id: String, + database: String, +) -> Result<(), String> { + ensure_connection_writable(&state, &connection_id, "Drop database").await?; + dbx_core::schema::drop_vector_database_core(&state, &connection_id, &database).await +} + +#[tauri::command] +pub async fn vector_drop_collection( + state: State<'_, Arc>, + connection_id: String, + database: String, + collection: String, +) -> Result<(), String> { + ensure_connection_writable(&state, &connection_id, "Drop collection").await?; + dbx_core::schema::drop_vector_collection_core(&state, &connection_id, &database, &collection).await +} + #[tauri::command] pub async fn mongo_rename_collection( state: State<'_, Arc>, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 859ce80a8..facafbba2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2006,6 +2006,8 @@ pub fn run() { commands::mongo_cmd::mongo_create_database, commands::mongo_cmd::mongo_drop_database, commands::mongo_cmd::mongo_drop_collection, + commands::mongo_cmd::vector_drop_database, + commands::mongo_cmd::vector_drop_collection, commands::mongo_cmd::mongo_rename_collection, commands::mongo_cmd::mongo_clone_collection, commands::docs::docs_collect_snapshot,