From 7a5984a1fdbe894f72bcdefd2b66105869914678 Mon Sep 17 00:00:00 2001 From: Guoyu Su Date: Mon, 29 Jun 2026 00:27:27 +0800 Subject: [PATCH] fix(mongodb): support shell projection and db.version --- .../java/com/dbx/agent/AgentProtocol.java | 2 + .../src/main/resources/agent-protocol-v1.json | 1 + .../com/dbx/agent/mongodb/MongoAgent.java | 37 +++++++++++++-- .../com/dbx/agent/mongodb/MongoAgentTest.java | 33 +++++++++++++ .../components/document/DocumentBrowser.vue | 2 +- apps/desktop/src/lib/api.ts | 1 + apps/desktop/src/lib/http.ts | 12 +++-- apps/desktop/src/lib/mongoShellCommand.ts | 27 +++++++++++ apps/desktop/src/lib/tauri.ts | 12 +++-- apps/desktop/src/stores/queryStore.ts | 35 ++++++++++++-- crates/dbx-core/assets/agent-protocol-v1.json | 1 + crates/dbx-core/src/db/agent_driver.rs | 14 +++++- crates/dbx-core/src/db/mongo_driver.rs | 46 +++++++++++++++++++ crates/dbx-core/src/mongo_ops.rs | 46 ++++++++++++++----- crates/dbx-core/src/transfer.rs | 1 + crates/dbx-web/src/main.rs | 1 + crates/dbx-web/src/routes/mongo.rs | 24 ++++++++++ packages/app-tests/mongoShellCommand.test.ts | 19 ++++++++ packages/node-core/src/database.ts | 36 +++++++++++++-- packages/node-core/src/web-backend.ts | 16 ++++++- packages/node-core/tests/mongo-query.test.ts | 16 ++++++- src-tauri/src/commands/mcp_bridge.rs | 29 ++++++++++++ src-tauri/src/commands/mongo_cmd.rs | 16 +++++++ src-tauri/src/lib.rs | 1 + 24 files changed, 390 insertions(+), 38 deletions(-) diff --git a/agents/common/src/main/java/com/dbx/agent/AgentProtocol.java b/agents/common/src/main/java/com/dbx/agent/AgentProtocol.java index 411a02ea6c..0f5d2468f2 100644 --- a/agents/common/src/main/java/com/dbx/agent/AgentProtocol.java +++ b/agents/common/src/main/java/com/dbx/agent/AgentProtocol.java @@ -39,6 +39,7 @@ public final class AgentProtocol { public static final String MONGO_METHOD_LIST_DATABASES = "list_databases"; public static final String MONGO_METHOD_LIST_COLLECTIONS = "list_collections"; public static final String MONGO_METHOD_FIND_DOCUMENTS = "find_documents"; + public static final String MONGO_METHOD_SERVER_VERSION = "server_version"; 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_DELETE_DOCUMENT = "delete_document"; @@ -113,6 +114,7 @@ public final class AgentProtocol { MONGO_METHOD_LIST_DATABASES, MONGO_METHOD_LIST_COLLECTIONS, MONGO_METHOD_FIND_DOCUMENTS, + MONGO_METHOD_SERVER_VERSION, MONGO_METHOD_INSERT_DOCUMENT, MONGO_METHOD_UPDATE_DOCUMENT, MONGO_METHOD_DELETE_DOCUMENT diff --git a/agents/common/src/main/resources/agent-protocol-v1.json b/agents/common/src/main/resources/agent-protocol-v1.json index bd5a31e59e..f4b14f27db 100644 --- a/agents/common/src/main/resources/agent-protocol-v1.json +++ b/agents/common/src/main/resources/agent-protocol-v1.json @@ -68,6 +68,7 @@ "list_databases", "list_collections", "find_documents", + "server_version", "insert_document", "update_document", "delete_document" diff --git a/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java b/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java index df124f0503..e8ae5da4a3 100644 --- a/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java +++ b/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java @@ -61,6 +61,11 @@ public final class MongoAgent { return element == null || element instanceof JsonNull ? null : element.getAsString(); } + static Document documentOrNull(JsonObject object, String key) { + String json = stringOrNull(object, key); + return json == null || json.isBlank() ? null : Document.parse(json); + } + static MongoClientSettings.Builder configureBuilder(JsonObject connObj) { String host = connObj.has("host") ? connObj.get("host").getAsString() : "127.0.0.1"; int port = connObj.has("port") ? connObj.get("port").getAsInt() : 27017; @@ -350,16 +355,22 @@ public final class MongoAgent { String collection = params.get("collection").getAsString(); long skip = params.has("skip") ? params.get("skip").getAsLong() : 0; int limit = params.has("limit") ? params.get("limit").getAsInt() : 50; - String filterJson = stringOrNull(params, "filter"); - String sortJson = stringOrNull(params, "sort"); + Document filterDoc = documentOrNull(params, "filter"); + Document projectionDoc = documentOrNull(params, "projection"); + Document sortDoc = documentOrNull(params, "sort"); var col = c.getDatabase(database).getCollection(collection); - Document filterDoc = filterJson != null && !filterJson.isBlank() ? Document.parse(filterJson) : new Document(); + if (filterDoc == null) { + filterDoc = new Document(); + } long total = col.countDocuments(filterDoc); var iterable = col.find(filterDoc).skip((int) skip).limit(limit); - if (sortJson != null && !sortJson.isBlank()) { - iterable = iterable.sort(Document.parse(sortJson)); + if (projectionDoc != null) { + iterable = iterable.projection(projectionDoc); + } + if (sortDoc != null) { + iterable = iterable.sort(sortDoc); } List> documents = new ArrayList<>(); @@ -372,6 +383,21 @@ public final class MongoAgent { return result; } + private static Object serverVersion(JsonObject params) { + MongoClient c = requireClient(); + String database = defaultString(stringOrNull(params, "database"), "admin"); + Document buildInfo = c.getDatabase(database).runCommand(new Document("buildInfo", 1)); + return serverVersionFromBuildInfo(buildInfo); + } + + static String serverVersionFromBuildInfo(Document buildInfo) { + String version = buildInfo.getString("version"); + if (version == null || version.isBlank()) { + throw new IllegalStateException("MongoDB server version not found"); + } + return version; + } + private static Object insertDocument(JsonObject params) { MongoClient c = requireClient(); String database = params.get("database").getAsString(); @@ -559,6 +585,7 @@ public final class MongoAgent { case AgentProtocol.MONGO_METHOD_LIST_COLLECTIONS -> listCollections(params); case AgentProtocol.METHOD_LIST_INDEXES -> listIndexes(params); case AgentProtocol.MONGO_METHOD_FIND_DOCUMENTS -> findDocuments(params); + case AgentProtocol.MONGO_METHOD_SERVER_VERSION -> serverVersion(params); case AgentProtocol.MONGO_METHOD_INSERT_DOCUMENT -> insertDocument(params); case AgentProtocol.MONGO_METHOD_UPDATE_DOCUMENT -> updateDocument(params); case AgentProtocol.MONGO_METHOD_DELETE_DOCUMENT -> deleteDocument(params); diff --git a/agents/drivers/mongodb/src/test/java/com/dbx/agent/mongodb/MongoAgentTest.java b/agents/drivers/mongodb/src/test/java/com/dbx/agent/mongodb/MongoAgentTest.java index b6a3a14e4a..f5e7074ca9 100644 --- a/agents/drivers/mongodb/src/test/java/com/dbx/agent/mongodb/MongoAgentTest.java +++ b/agents/drivers/mongodb/src/test/java/com/dbx/agent/mongodb/MongoAgentTest.java @@ -108,6 +108,39 @@ class MongoAgentTest { assertFalse(json.getAsJsonObject("error").get("message").getAsString().contains("Unknown method")); } + @Test + void parsesOptionalDocumentParameters() { + JsonObject params = new JsonObject(); + params.addProperty("projection", "{\"title\":1,\"_id\":0}"); + params.addProperty("filter", ""); + + Document projection = MongoAgent.documentOrNull(params, "projection"); + + assertNotNull(projection); + assertEquals(1, projection.get("title")); + assertEquals(0, projection.get("_id")); + assertEquals(null, MongoAgent.documentOrNull(params, "filter")); + assertEquals(null, MongoAgent.documentOrNull(params, "sort")); + } + + @Test + void serverVersionMethodIsRecognizedOverJsonRpc() { + String response = MongoAgent.handleRequest( + "{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"server_version\"," + + "\"params\":{\"database\":\"admin\"}}"); + + JsonObject json = JsonParser.parseString(response).getAsJsonObject(); + assertEquals(9, json.get("id").getAsInt()); + assertEquals("Not connected", json.getAsJsonObject("error").get("message").getAsString()); + assertFalse(json.getAsJsonObject("error").get("message").getAsString().contains("Unknown method")); + } + + @Test + void extractsServerVersionFromBuildInfo() { + assertEquals("4.4.29", MongoAgent.serverVersionFromBuildInfo(new Document("version", "4.4.29"))); + assertThrows(IllegalStateException.class, () -> MongoAgent.serverVersionFromBuildInfo(new Document("ok", 1))); + } + @Test void convertsMongoIndexDocumentToIndexInfo() { Document index = new Document("name", "idx_user_status") diff --git a/apps/desktop/src/components/document/DocumentBrowser.vue b/apps/desktop/src/components/document/DocumentBrowser.vue index b7f0c9e1c1..09010d42bb 100644 --- a/apps/desktop/src/components/document/DocumentBrowser.vue +++ b/apps/desktop/src/components/document/DocumentBrowser.vue @@ -396,7 +396,7 @@ async function load() { try { const filter = currentDocumentFilter(); const sort = sortInput.value.trim() || undefined; - const result = await api.documentFindDocuments(props.connectionId, props.database, props.collection, page.value * pageSize.value, pageSize.value, filter, sort, executionId); + const result = await api.documentFindDocuments(props.connectionId, props.database, props.collection, page.value * pageSize.value, pageSize.value, filter, undefined, sort, executionId); if (documentLoadExecutionId.value !== executionId) return; const nextDocuments = result.documents.map(asRecord); documents.value = nextDocuments; diff --git a/apps/desktop/src/lib/api.ts b/apps/desktop/src/lib/api.ts index a74fdc923d..4b76d41566 100644 --- a/apps/desktop/src/lib/api.ts +++ b/apps/desktop/src/lib/api.ts @@ -369,6 +369,7 @@ export const mongoDropDatabase = forward("mongoDropDatabase"); export const mongoDropCollection = forward("mongoDropCollection"); export const documentFindDocuments = forward("documentFindDocuments"); export const mongoFindDocuments = forward("mongoFindDocuments"); +export const mongoServerVersion = forward("mongoServerVersion"); export const mongoAggregateDocuments = forward("mongoAggregateDocuments"); export const mongoInsertDocument = forward("mongoInsertDocument"); export const mongoInsertDocuments = forward("mongoInsertDocuments"); diff --git a/apps/desktop/src/lib/http.ts b/apps/desktop/src/lib/http.ts index 6a6643acba..4502d2eac7 100644 --- a/apps/desktop/src/lib/http.ts +++ b/apps/desktop/src/lib/http.ts @@ -1720,12 +1720,16 @@ export async function vectorListCollections(connectionId: string): Promise { - return post("/api/mongo/find-documents", { connectionId, database, collection, skip, limit, filter, sort, executionId }); +export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, projection?: string, sort?: string, executionId?: string): Promise { + return post("/api/mongo/find-documents", { connectionId, database, collection, skip, limit, filter, projection, sort, executionId }); } -export async function documentFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string, executionId?: string): Promise { - return post("/api/document-store/find-documents", { connectionId, database, collection, skip, limit, filter, sort, executionId }); +export async function documentFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, projection?: string, sort?: string, executionId?: string): Promise { + return post("/api/document-store/find-documents", { connectionId, database, collection, skip, limit, filter, projection, sort, executionId }); +} + +export async function mongoServerVersion(connectionId: string, database: string, executionId?: string): Promise { + return post("/api/mongo/server-version", { connectionId, database, executionId }); } export async function mongoAggregateDocuments(connectionId: string, database: string, collection: string, pipelineJson: string, maxRows?: number, executionId?: string): Promise { diff --git a/apps/desktop/src/lib/mongoShellCommand.ts b/apps/desktop/src/lib/mongoShellCommand.ts index b867ed4781..338a8d8789 100644 --- a/apps/desktop/src/lib/mongoShellCommand.ts +++ b/apps/desktop/src/lib/mongoShellCommand.ts @@ -3,6 +3,7 @@ import type { QueryResult } from "@/types/database"; export interface MongoFindCommand { collection: string; filter: string; + projection?: string; skip: number; limit: number; sort?: string; @@ -26,6 +27,10 @@ export interface MongoUseCommand { database: string; } +export interface MongoVersionCommand { + kind: "version"; +} + export type MongoWriteCommand = { kind: "insert"; collection: string; docsJson: string } | { kind: "update"; collection: string; filter: string; update: string; many: boolean } | { kind: "delete"; collection: string; filter: string; many: boolean }; export interface MongoAggregateSafetyOptions { @@ -45,8 +50,15 @@ export function parseMongoFindCommand(input: string): MongoFindCommand | null { if (findCloseIndex < 0) return null; const findArgs = splitTopLevel(source.slice(findOpenIndex + 1, findCloseIndex)); + if (findArgs.length > 2 && findArgs.slice(2).some((arg) => arg.trim())) return null; const filter = normalizeJsonArgument(findArgs[0] || "{}"); if (!filter) return null; + let projection: string | undefined; + if (findArgs[1]?.trim()) { + const parsedProjection = normalizeJsonArgument(findArgs[1]); + if (!parsedProjection) return null; + projection = parsedProjection; + } const chain = source.slice(findCloseIndex + 1).trim(); if (chain && !chain.startsWith(".")) return null; @@ -66,6 +78,7 @@ export function parseMongoFindCommand(input: string): MongoFindCommand | null { return { collection: target.collection, filter, + ...(projection ? { projection } : {}), skip, limit, sort, @@ -143,6 +156,11 @@ export function parseMongoUseCommand(input: string): MongoUseCommand | null { }; } +export function parseMongoVersionCommand(input: string): MongoVersionCommand | null { + const source = input.trim().replace(/;$/, "").trim(); + return /^db\s*\.\s*version\s*\(\s*\)$/i.test(source) ? { kind: "version" } : null; +} + export function parseMongoWriteCommand(input: string): MongoWriteCommand | null { const source = input.trim().replace(/;$/, "").trim(); const insertOne = parseCollectionMethodTarget(source, "insertOne"); @@ -273,6 +291,15 @@ export function mongoUseToQueryResult(database: string, executionTimeMs: number) }; } +export function mongoVersionToQueryResult(version: string, executionTimeMs: number): QueryResult { + return { + columns: ["version"], + rows: [[version]], + affected_rows: 1, + execution_time_ms: Math.max(0, Math.round(executionTimeMs)), + }; +} + export function mongoIndexesToQueryResult( indexes: { name: string; diff --git a/apps/desktop/src/lib/tauri.ts b/apps/desktop/src/lib/tauri.ts index a1acc79e94..f9aa02499c 100644 --- a/apps/desktop/src/lib/tauri.ts +++ b/apps/desktop/src/lib/tauri.ts @@ -1450,12 +1450,16 @@ export async function vectorListCollections(connectionId: string): Promise { - return invoke("mongo_find_documents", { connectionId, database, collection, skip, limit, filter, sort, executionId }); +export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, projection?: string, sort?: string, executionId?: string): Promise { + return invoke("mongo_find_documents", { connectionId, database, collection, skip, limit, filter, projection, sort, executionId }); } -export async function documentFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string, executionId?: string): Promise { - return invoke("document_find_documents", { connectionId, database, collection, skip, limit, filter, sort, executionId }); +export async function documentFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, projection?: string, sort?: string, executionId?: string): Promise { + return invoke("document_find_documents", { connectionId, database, collection, skip, limit, filter, projection, sort, executionId }); +} + +export async function mongoServerVersion(connectionId: string, database: string, executionId?: string): Promise { + return invoke("mongo_server_version", { connectionId, database, executionId }); } export async function mongoAggregateDocuments(connectionId: string, database: string, collection: string, pipelineJson: string, maxRows?: number, executionId?: string): Promise { diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts index bad52eff01..88a15c55fe 100644 --- a/apps/desktop/src/stores/queryStore.ts +++ b/apps/desktop/src/stores/queryStore.ts @@ -14,14 +14,16 @@ import { mongoCountToQueryResult, mongoDocumentsToQueryResult, mongoIndexesToQueryResult, - mongoWriteToQueryResult, mongoUseToQueryResult, + mongoVersionToQueryResult, + mongoWriteToQueryResult, parseMongoAggregateCommand, parseMongoCountDocumentsCommand, parseMongoFindCommand, parseMongoGetIndexesCommand, parseMongoWriteCommand, parseMongoUseCommand, + parseMongoVersionCommand, type MongoAggregateSafetyOptions, } from "@/lib/mongoShellCommand"; import { redisCommandResultToQueryResult } from "@/lib/redisQueryResult"; @@ -1589,7 +1591,7 @@ export const useQueryStore = defineStore("query", () => { if (mongoFind) { await connStore.ensureConnected(tab.connectionId); console.info("[DBX][executeTabSql:mongo-find:start]", { traceId, collection: mongoFind.collection }); - const result = await api.mongoFindDocuments(tab.connectionId, tab.database, mongoFind.collection, mongoFind.skip, mongoFind.limit, mongoFind.filter, mongoFind.sort, executionId); + const result = await api.mongoFindDocuments(tab.connectionId, tab.database, mongoFind.collection, mongoFind.skip, mongoFind.limit, mongoFind.filter, mongoFind.projection, mongoFind.sort, executionId); console.info("[DBX][executeTabSql:mongo-find:done]", { traceId, rowCount: result.documents.length, @@ -1613,11 +1615,38 @@ export const useQueryStore = defineStore("query", () => { } return; } + const mongoVersion = conn?.db_type === "mongodb" ? parseMongoVersionCommand(sql) : null; + if (mongoVersion) { + await connStore.ensureConnected(tab.connectionId); + console.info("[DBX][executeTabSql:mongo-version:start]", { traceId }); + const version = await api.mongoServerVersion(tab.connectionId, tab.database, executionId); + console.info("[DBX][executeTabSql:mongo-version:done]", { + traceId, + version, + elapsed: elapsed(), + }); + const current = tabs.value.find((t) => t.id === id); + if (current?.executionId === executionId) { + current.results = undefined; + current.activeResultIndex = undefined; + current.result = markQueryResultRowsRaw(mongoVersionToQueryResult(version, performance.now() - startedAt)); + touchResult(current); + current.queryAnalysis = undefined; + current.querySourceColumns = undefined; + current.queryEditabilityReason = undefined; + current.mongoEditTarget = undefined; + current.tableMeta = undefined; + current.resultBaseSql = options?.resultBaseSql ?? sql; + current.resultSortedSql = options?.resultSortedSql; + syncDisplayedResultRun(current, options?.resultBaseSql ?? sql); + } + return; + } const mongoCount = conn?.db_type === "mongodb" ? parseMongoCountDocumentsCommand(sql) : null; if (mongoCount) { await connStore.ensureConnected(tab.connectionId); console.info("[DBX][executeTabSql:mongo-count:start]", { traceId, collection: mongoCount.collection }); - const result = await api.mongoFindDocuments(tab.connectionId, tab.database, mongoCount.collection, 0, 1, mongoCount.filter, undefined, executionId); + const result = await api.mongoFindDocuments(tab.connectionId, tab.database, mongoCount.collection, 0, 1, mongoCount.filter, undefined, undefined, executionId); console.info("[DBX][executeTabSql:mongo-count:done]", { traceId, total: result.total, diff --git a/crates/dbx-core/assets/agent-protocol-v1.json b/crates/dbx-core/assets/agent-protocol-v1.json index bd5a31e59e..f4b14f27db 100644 --- a/crates/dbx-core/assets/agent-protocol-v1.json +++ b/crates/dbx-core/assets/agent-protocol-v1.json @@ -68,6 +68,7 @@ "list_databases", "list_collections", "find_documents", + "server_version", "insert_document", "update_document", "delete_document" diff --git a/crates/dbx-core/src/db/agent_driver.rs b/crates/dbx-core/src/db/agent_driver.rs index b82e5e298e..6dd3333633 100644 --- a/crates/dbx-core/src/db/agent_driver.rs +++ b/crates/dbx-core/src/db/agent_driver.rs @@ -243,16 +243,18 @@ pub enum MongoAgentMethod { ListDatabases, ListCollections, FindDocuments, + ServerVersion, InsertDocument, UpdateDocument, DeleteDocument, } impl MongoAgentMethod { - pub const ALL: [Self; 6] = [ + pub const ALL: [Self; 7] = [ Self::ListDatabases, Self::ListCollections, Self::FindDocuments, + Self::ServerVersion, Self::InsertDocument, Self::UpdateDocument, Self::DeleteDocument, @@ -263,6 +265,7 @@ impl MongoAgentMethod { Self::ListDatabases => "list_databases", Self::ListCollections => "list_collections", Self::FindDocuments => "find_documents", + Self::ServerVersion => "server_version", Self::InsertDocument => "insert_document", Self::UpdateDocument => "update_document", Self::DeleteDocument => "delete_document", @@ -921,6 +924,13 @@ impl AgentDriverClient { self.call_mongo_method(MongoAgentMethod::FindDocuments, params).await } + pub async fn mongo_server_version( + &mut self, + database: &str, + ) -> Result { + self.call_mongo_method(MongoAgentMethod::ServerVersion, mongo_database_params(database)).await + } + pub async fn mongo_insert_document( &mut self, params: Value, @@ -1461,6 +1471,7 @@ mod tests { assert_eq!(MongoAgentMethod::ListDatabases.as_str(), "list_databases"); assert_eq!(MongoAgentMethod::ListCollections.as_str(), "list_collections"); assert_eq!(MongoAgentMethod::FindDocuments.as_str(), "find_documents"); + assert_eq!(MongoAgentMethod::ServerVersion.as_str(), "server_version"); assert_eq!(MongoAgentMethod::InsertDocument.as_str(), "insert_document"); assert_eq!(MongoAgentMethod::UpdateDocument.as_str(), "update_document"); assert_eq!(MongoAgentMethod::DeleteDocument.as_str(), "delete_document"); @@ -1500,6 +1511,7 @@ mod tests { let _mongo_list_databases = AgentDriverClient::mongo_list_databases::; let _mongo_list_collections = AgentDriverClient::mongo_list_collections::; let _mongo_find_documents = AgentDriverClient::mongo_find_documents::; + let _mongo_server_version = AgentDriverClient::mongo_server_version::; let _mongo_insert_document = AgentDriverClient::mongo_insert_document::; let _mongo_update_document = AgentDriverClient::mongo_update_document::; let _mongo_delete_document = AgentDriverClient::mongo_delete_document::; diff --git a/crates/dbx-core/src/db/mongo_driver.rs b/crates/dbx-core/src/db/mongo_driver.rs index 6f15c6f061..bf011a171b 100644 --- a/crates/dbx-core/src/db/mongo_driver.rs +++ b/crates/dbx-core/src/db/mongo_driver.rs @@ -113,6 +113,17 @@ pub async fn test_connection(client: &Client, timeout: Duration, database: Optio .await } +pub async fn server_version(client: &Client, database: &str) -> Result { + let database = database.trim(); + let database = if database.is_empty() { "admin" } else { database }; + let result = client.database(database).run_command(doc! { "buildInfo": 1 }).await.map_err(|e| e.to_string())?; + server_version_from_build_info(&result) +} + +fn server_version_from_build_info(result: &Document) -> Result { + result.get_str("version").map(str::to_string).map_err(|e| format!("MongoDB server version not found: {e}")) +} + pub async fn list_databases(client: &Client) -> Result, String> { client.list_database_names().await.map_err(|e| e.to_string()) } @@ -193,6 +204,7 @@ pub async fn find_documents( skip: u64, limit: i64, filter: Option<&str>, + projection: Option<&str>, sort: Option<&str>, ) -> Result { let col = client.database(database).collection::(collection); @@ -212,6 +224,14 @@ pub async fn find_documents( }; let mut find = col.find(filter_doc).skip(skip).limit(limit); + if let Some(p) = projection { + if !p.trim().is_empty() { + let json: serde_json::Value = + serde_json::from_str(p).map_err(|e| format!("Invalid projection JSON: {e}"))?; + let projection_doc = json_object_to_document(&json).map_err(|e| format!("Invalid projection: {e}"))?; + find = find.projection(projection_doc); + } + } if let Some(s) = sort { if !s.trim().is_empty() { let json: serde_json::Value = serde_json::from_str(s).map_err(|e| format!("Invalid sort JSON: {e}"))?; @@ -844,6 +864,32 @@ mod tests { )); } + #[test] + fn json_object_to_document_parses_find_projection() { + let value = serde_json::json!({ + "title": 1, + "_id": 0, + }); + let doc = json_object_to_document(&value).unwrap(); + + assert!(matches!(doc.get("title"), Some(Bson::Int64(1)))); + assert!(matches!(doc.get("_id"), Some(Bson::Int64(0)))); + } + + #[test] + fn server_version_from_build_info_reads_version_field() { + let version = server_version_from_build_info(&doc! { "version": "4.4.29" }).unwrap(); + + assert_eq!(version, "4.4.29"); + } + + #[test] + fn server_version_from_build_info_rejects_missing_version() { + let error = server_version_from_build_info(&doc! { "ok": 1 }).unwrap_err(); + + assert!(error.contains("MongoDB server version not found")); + } + #[test] fn json_object_to_document_parses_mongo_shell_isodate_strings() { let value = serde_json::json!({ diff --git a/crates/dbx-core/src/mongo_ops.rs b/crates/dbx-core/src/mongo_ops.rs index 1456b35273..28adbb7ce7 100644 --- a/crates/dbx-core/src/mongo_ops.rs +++ b/crates/dbx-core/src/mongo_ops.rs @@ -123,6 +123,23 @@ pub async fn mongo_drop_collection_core( } } +pub async fn mongo_server_version_core( + state: &AppState, + connection_id: &str, + database: &str, +) -> Result { + ensure_document_pool(state, connection_id).await?; + let connections = state.connections.read().await; + match connections.get(connection_id).ok_or("Not found")? { + PoolKind::MongoDb(client) => mongo_driver::server_version(client, database).await, + PoolKind::Agent(client) => { + let mut client = client.lock().await; + client.mongo_server_version(database).await + } + _ => Err("Not a MongoDB connection".to_string()), + } +} + #[allow(clippy::too_many_arguments)] pub async fn document_find_documents_core( state: &AppState, @@ -132,13 +149,14 @@ pub async fn document_find_documents_core( skip: u64, limit: i64, filter: Option<&str>, + projection: Option<&str>, sort: Option<&str>, ) -> Result { ensure_document_pool(state, connection_id).await?; let connections = state.connections.read().await; match connections.get(connection_id).ok_or("Not found")? { PoolKind::MongoDb(client) => { - mongo_driver::find_documents(client, database, collection, skip, limit, filter, sort).await + mongo_driver::find_documents(client, database, collection, skip, limit, filter, projection, sort).await } PoolKind::Elasticsearch(client) => { let client = client.clone(); @@ -153,16 +171,18 @@ pub async fn document_find_documents_core( } PoolKind::Agent(client) => { let mut client = client.lock().await; - client - .mongo_find_documents(serde_json::json!({ - "database": database, - "collection": collection, - "skip": skip, - "limit": limit, - "filter": filter, - "sort": sort, - })) - .await + let mut params = serde_json::json!({ + "database": database, + "collection": collection, + "skip": skip, + "limit": limit, + "filter": filter, + "sort": sort, + }); + if let Some(projection) = projection { + params["projection"] = serde_json::json!(projection); + } + client.mongo_find_documents(params).await } _ => Err("Not a MongoDB/Elasticsearch/vector connection".to_string()), } @@ -177,9 +197,11 @@ pub async fn mongo_find_documents_core( skip: u64, limit: i64, filter: Option<&str>, + projection: Option<&str>, sort: Option<&str>, ) -> Result { - document_find_documents_core(state, connection_id, database, collection, skip, limit, filter, sort).await + document_find_documents_core(state, connection_id, database, collection, skip, limit, filter, projection, sort) + .await } pub async fn mongo_aggregate_documents_core( diff --git a/crates/dbx-core/src/transfer.rs b/crates/dbx-core/src/transfer.rs index 0135462d41..1644a2c7c6 100644 --- a/crates/dbx-core/src/transfer.rs +++ b/crates/dbx-core/src/transfer.rs @@ -1967,6 +1967,7 @@ async fn find_mongo_documents_for_transfer( offset, batch_size as i64, None, + None, Some(r#"{"_id":1}"#), ) .await diff --git a/crates/dbx-web/src/main.rs b/crates/dbx-web/src/main.rs index d1a3b88627..dcbe1a2358 100644 --- a/crates/dbx-web/src/main.rs +++ b/crates/dbx-web/src/main.rs @@ -365,6 +365,7 @@ async fn main() { .route("/mongo/drop-collection", post(routes::mongo::drop_collection)) .route("/document-store/find-documents", post(routes::mongo::document_find_documents)) .route("/mongo/find-documents", post(routes::mongo::find_documents)) + .route("/mongo/server-version", post(routes::mongo::server_version)) .route("/mongo/aggregate-documents", post(routes::mongo::aggregate_documents)) .route("/mongo/insert-document", post(routes::mongo::insert_document)) .route("/mongo/insert-documents", post(routes::mongo::insert_documents)) diff --git a/crates/dbx-web/src/routes/mongo.rs b/crates/dbx-web/src/routes/mongo.rs index ed79f350ab..ffbc9b1844 100644 --- a/crates/dbx-web/src/routes/mongo.rs +++ b/crates/dbx-web/src/routes/mongo.rs @@ -73,10 +73,19 @@ pub struct MongoFindRequest { pub skip: Option, pub limit: Option, pub filter: Option, + pub projection: Option, pub sort: Option, pub execution_id: Option, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MongoServerVersionRequest { + pub connection_id: String, + pub database: String, + pub execution_id: Option, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct MongoAggregateRequest { @@ -213,6 +222,7 @@ pub async fn find_documents( req.skip.unwrap_or(0), req.limit.unwrap_or(50), req.filter.as_deref(), + req.projection.as_deref(), req.sort.as_deref(), ), ) @@ -235,6 +245,7 @@ pub async fn document_find_documents( req.skip.unwrap_or(0), req.limit.unwrap_or(50), req.filter.as_deref(), + req.projection.as_deref(), req.sort.as_deref(), ), ) @@ -242,6 +253,19 @@ pub async fn document_find_documents( Ok(Json(serde_json::to_value(result).map_err(|e| AppError(e.to_string()))?)) } +pub async fn server_version( + State(state): State>, + Json(req): Json, +) -> Result, AppError> { + let result = run_cancellable( + &state, + req.execution_id.clone(), + dbx_core::mongo_ops::mongo_server_version_core(&state.app, &req.connection_id, &req.database), + ) + .await?; + Ok(Json(serde_json::to_value(result).map_err(|e| AppError(e.to_string()))?)) +} + pub async fn aggregate_documents( State(state): State>, Json(req): Json, diff --git a/packages/app-tests/mongoShellCommand.test.ts b/packages/app-tests/mongoShellCommand.test.ts index fc19ebfda3..f34d6b72cf 100644 --- a/packages/app-tests/mongoShellCommand.test.ts +++ b/packages/app-tests/mongoShellCommand.test.ts @@ -10,6 +10,7 @@ import { parseMongoCountDocumentsCommand, parseMongoFindCommand, parseMongoGetIndexesCommand, + parseMongoVersionCommand, parseMongoWriteCommand, } from "../../apps/desktop/src/lib/mongoShellCommand.ts"; import { buildMongoUpdateDocument as buildMongoDocumentUpdate, formatMongoShellLiteral as formatMongoDocumentShellLiteral } from "../../apps/desktop/src/lib/mongoDocumentValues.ts"; @@ -67,8 +68,26 @@ test("parseMongoFindCommand accepts single-quoted string values and unquoted sor assert.deepEqual(JSON.parse(command.sort || "{}"), { price: -1 }); }); +test("parseMongoFindCommand parses projection arguments", () => { + const command = parseMongoFindCommand(`db.jobs.find({ status: "open" }, { + title: 1, + _id: 0 + }).sort({ title: 1 })`); + assert.ok(command); + assert.equal(command.collection, "jobs"); + assert.deepEqual(JSON.parse(command.filter), { status: "open" }); + assert.deepEqual(JSON.parse(command.projection || "{}"), { title: 1, _id: 0 }); + assert.deepEqual(JSON.parse(command.sort || "{}"), { title: 1 }); +}); + test("parseMongoFindCommand rejects unsupported mongo shell commands", () => { assert.equal(parseMongoFindCommand("db.users.drop()"), null); + assert.equal(parseMongoFindCommand("db.users.find({}, {}, { hint: { name: 1 } })"), null); +}); + +test("parseMongoVersionCommand parses db.version", () => { + assert.deepEqual(parseMongoVersionCommand("db.version();"), { kind: "version" }); + assert.equal(parseMongoVersionCommand("db.jobs.version()"), null); }); test("parseMongoWriteCommand accepts unquoted insert and update commands", () => { diff --git a/packages/node-core/src/database.ts b/packages/node-core/src/database.ts index cdb55379f6..3dcac67c5a 100644 --- a/packages/node-core/src/database.ts +++ b/packages/node-core/src/database.ts @@ -626,9 +626,14 @@ export async function executeQuery(config: ConnectionConfig, sql: string, option if (config.db_type === "mongodb") { const find = parseMongoFindCommand(sql); if (find) { - const result = await withTimeout(mongoFindDocuments(config, find.collection, find.skip, find.limit, find.filter, find.sort), resolveTimeoutMs(options)); + const result = await withTimeout(mongoFindDocuments(config, find.collection, find.skip, find.limit, find.filter, find.projection, find.sort), resolveTimeoutMs(options)); return mongoDocumentsToQueryResult(result.documents.slice(0, resolveMaxRows(options)), result.total); } + const version = parseMongoVersionCommand(sql); + if (version) { + const result = await withTimeout(mongoServerVersion(config), resolveTimeoutMs(options)); + return { columns: ["version"], rows: [{ version: result }], row_count: 1 }; + } const count = parseMongoCountDocumentsCommand(sql); if (count) { const result = await withTimeout(mongoFindDocuments(config, count.collection, 0, 1, count.filter), resolveTimeoutMs(options)); @@ -653,7 +658,7 @@ export async function executeQuery(config: ConnectionConfig, sql: string, option const affected = await withTimeout(executeMongoWrite(config, write), resolveTimeoutMs(options)); return { columns: [], rows: [], row_count: affected }; } - throw new Error("Use MongoDB shell-style commands, for example: db.projects.find({}).limit(100), db.projects.countDocuments({}), db.projects.getIndexes(), db.projects.insertOne({...}), db.projects.updateOne({...}, {$set: {...}}), or db.projects.deleteOne({...})"); + throw new Error("Use MongoDB shell-style commands, for example: db.projects.find({}).limit(100), db.version(), db.projects.countDocuments({}), db.projects.getIndexes(), db.projects.insertOne({...}), db.projects.updateOne({...}, {$set: {...}}), or db.projects.deleteOne({...})"); } if (isDirectQueryType(config.db_type)) { return query(config, sql, undefined, options); @@ -848,7 +853,7 @@ export async function describeTable(config: ConnectionConfig, table: string, sch })); } -async function mongoFindDocuments(config: ConnectionConfig, collection: string, skip: number, limit: number, filter: string, sort?: string): Promise { +async function mongoFindDocuments(config: ConnectionConfig, collection: string, skip: number, limit: number, filter: string, projection?: string, sort?: string): Promise { return bridgeDataRequest("/data/mongo/find-documents", { connection_name: config.name, database: config.database || "", @@ -856,10 +861,18 @@ async function mongoFindDocuments(config: ConnectionConfig, collection: string, skip, limit, filter, + projection, sort, }); } +async function mongoServerVersion(config: ConnectionConfig): Promise { + return bridgeDataRequest("/data/mongo/server-version", { + connection_name: config.name, + database: config.database || "", + }); +} + async function executeMongoWrite(config: ConnectionConfig, command: MongoWriteCommand): Promise { if (command.kind === "insert") { const result = await bridgeDataRequest<{ affected_rows: number }>("/data/mongo/insert-documents", { @@ -901,7 +914,7 @@ async function mongoAggregateDocuments(config: ConnectionConfig, collection: str }); } -export function mongoDocumentsToQueryResult(documents: unknown[], total: number): QueryResult { +export function mongoDocumentsToQueryResult(documents: unknown[], _total: number): QueryResult { const columns: string[] = []; for (const doc of documents) { if (isRecord(doc)) { @@ -951,6 +964,7 @@ export function inferMongoColumns(documents: unknown[]): ColumnInfo[] { interface MongoFindCommand { collection: string; filter: string; + projection?: string; skip: number; limit: number; sort?: string; @@ -980,8 +994,15 @@ export function parseMongoFindCommand(input: string): MongoFindCommand | null { const findCloseIndex = findMatchingParen(source, findOpenIndex); if (findCloseIndex < 0) return null; const findArgs = splitTopLevel(source.slice(findOpenIndex + 1, findCloseIndex)); + if (findArgs.length > 2 && findArgs.slice(2).some((arg) => arg.trim())) return null; const filter = normalizeJsonArgument(findArgs[0] || "{}"); if (!filter) return null; + let projection: string | undefined; + if (findArgs[1]?.trim()) { + const parsedProjection = normalizeJsonArgument(findArgs[1]); + if (!parsedProjection) return null; + projection = parsedProjection; + } const chain = source.slice(findCloseIndex + 1).trim(); if (chain && !chain.startsWith(".")) return null; const sortArg = readChainedCallArgument(chain, "sort"); @@ -994,7 +1015,12 @@ export function parseMongoFindCommand(input: string): MongoFindCommand | null { const skip = readChainedIntegerArgument(chain, "skip", 0); const limit = readChainedIntegerArgument(chain, "limit", MAX_ROWS); if (skip === null || limit === null) return null; - return { collection: target.collection, filter, skip, limit, sort }; + return { collection: target.collection, filter, ...(projection ? { projection } : {}), skip, limit, sort }; +} + +export function parseMongoVersionCommand(input: string): boolean { + const source = input.trim().replace(/;$/, "").trim(); + return /^db\s*\.\s*version\s*\(\s*\)$/i.test(source); } export function parseMongoCountDocumentsCommand(input: string): MongoCountDocumentsCommand | null { diff --git a/packages/node-core/src/web-backend.ts b/packages/node-core/src/web-backend.ts index 6420d43447..9eae17197e 100644 --- a/packages/node-core/src/web-backend.ts +++ b/packages/node-core/src/web-backend.ts @@ -1,6 +1,6 @@ import type { ConnectionConfig } from "./connections.js"; import type { TableInfo, ColumnInfo, QueryOptions, QueryResult } from "./database.js"; -import { collectionListToTableInfos, evaluateMongoAggregateSafety, evaluateMongoWriteSafety, inferMongoColumns, mongoDocumentsToQueryResult, parseMongoAggregateCommand, parseMongoCountDocumentsCommand, parseMongoFindCommand, parseMongoGetIndexesCommand, parseMongoWriteCommand, type CollectionInfo, type MongoWriteCommand } from "./database.js"; +import { collectionListToTableInfos, evaluateMongoAggregateSafety, evaluateMongoWriteSafety, inferMongoColumns, mongoDocumentsToQueryResult, parseMongoAggregateCommand, 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"; @@ -140,12 +140,24 @@ export async function executeQuery(config: ConnectionConfig, sql: string, option skip: find.skip, limit: find.limit, filter: find.filter, + projection: find.projection, sort: find.sort, }), }); const result = (await res.json()) as { documents: unknown[]; total: number }; return mongoDocumentsToQueryResult(result.documents.slice(0, options?.maxRows ?? result.documents.length), result.total); } + if (parseMongoVersionCommand(sql)) { + const res = await apiFetch("/api/mongo/server-version", { + method: "POST", + body: JSON.stringify({ + connectionId: config.id, + database: config.database || "", + }), + }); + const version = (await res.json()) as string; + return { columns: ["version"], rows: [{ version }], row_count: 1 }; + } const count = parseMongoCountDocumentsCommand(sql); if (count) { const res = await apiFetch("/api/mongo/find-documents", { @@ -201,7 +213,7 @@ export async function executeQuery(config: ConnectionConfig, sql: string, option const affected = await executeMongoWrite(config, write); return { columns: [], rows: [], row_count: affected }; } - throw new Error("Use MongoDB shell-style commands, for example: db.projects.find({}).limit(100), db.projects.countDocuments({}), db.projects.getIndexes(), db.projects.insertOne({...}), db.projects.updateOne({...}, {$set: {...}}), or db.projects.deleteOne({...})"); + throw new Error("Use MongoDB shell-style commands, for example: db.projects.find({}).limit(100), db.version(), db.projects.countDocuments({}), db.projects.getIndexes(), db.projects.insertOne({...}), db.projects.updateOne({...}, {$set: {...}}), or db.projects.deleteOne({...})"); } const res = await apiFetch("/api/query/execute", { method: "POST", diff --git a/packages/node-core/tests/mongo-query.test.ts b/packages/node-core/tests/mongo-query.test.ts index 2f87a2c269..8412611209 100644 --- a/packages/node-core/tests/mongo-query.test.ts +++ b/packages/node-core/tests/mongo-query.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { test } from "vitest"; -import { executeQuery, inferMongoColumns, mongoAggregateWriteStage, mongoDocumentsToQueryResult, parseMongoAggregateCommand, parseMongoCountDocumentsCommand, parseMongoFindCommand, parseMongoGetIndexesCommand, parseMongoWriteCommand } from "../src/database.js"; +import { executeQuery, inferMongoColumns, mongoAggregateWriteStage, mongoDocumentsToQueryResult, parseMongoAggregateCommand, 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)'), { @@ -36,6 +36,20 @@ test("parseMongoFindCommand accepts Compass-style unquoted keys and ObjectId", ( assert.deepEqual(JSON.parse(command.filter), { _id: { $oid: "6a045a92d2971e44243771a1" } }); }); +test("parseMongoFindCommand accepts projection arguments", () => { + const command = parseMongoFindCommand("db.jobs.find({status: 'open'}, {title: 1, _id: 0}).sort({title: 1})"); + assert.ok(command); + assert.equal(command.collection, "jobs"); + assert.deepEqual(JSON.parse(command.filter), { status: "open" }); + assert.deepEqual(JSON.parse(command.projection || "{}"), { title: 1, _id: 0 }); + assert.deepEqual(JSON.parse(command.sort || "{}"), { title: 1 }); +}); + +test("parseMongoVersionCommand accepts db.version", () => { + assert.equal(parseMongoVersionCommand("db.version();"), true); + assert.equal(parseMongoVersionCommand("db.jobs.version()"), false); +}); + test("parseMongoWriteCommand accepts unquoted update operator keys", () => { assert.deepEqual(parseMongoWriteCommand("db.projects.updateOne({_id: ObjectId('507f1f77bcf86cd799439011')}, {$set: {name: 'next'}})"), { kind: "update", diff --git a/src-tauri/src/commands/mcp_bridge.rs b/src-tauri/src/commands/mcp_bridge.rs index 8aae7a0832..41e54d2a65 100644 --- a/src-tauri/src/commands/mcp_bridge.rs +++ b/src-tauri/src/commands/mcp_bridge.rs @@ -51,9 +51,16 @@ struct MongoFindDocumentsRequest { skip: Option, limit: Option, filter: Option, + projection: Option, sort: Option, } +#[derive(Deserialize)] +struct MongoServerVersionRequest { + connection_name: String, + database: Option, +} + #[derive(Deserialize)] struct MongoAggregateDocumentsRequest { connection_name: String, @@ -157,6 +164,8 @@ pub fn start(app_handle: AppHandle, state: Arc) { handle_mongo_list_collections_data(&st, body, &mut stream).await; } else if first_line.starts_with("POST /data/mongo/find-documents") { handle_mongo_find_documents_data(&st, body, &mut stream).await; + } else if first_line.starts_with("POST /data/mongo/server-version") { + handle_mongo_server_version_data(&st, body, &mut stream).await; } else if first_line.starts_with("POST /data/mongo/aggregate-documents") { handle_mongo_aggregate_documents_data(&st, body, &mut stream).await; } else if first_line.starts_with("POST /data/mongo/insert-documents") { @@ -410,6 +419,7 @@ async fn handle_mongo_find_documents_data(state: &Arc, body: &str, str req.skip.unwrap_or(0), req.limit.unwrap_or(100), req.filter.as_deref(), + req.projection.as_deref(), req.sort.as_deref(), ) .await @@ -419,6 +429,25 @@ async fn handle_mongo_find_documents_data(state: &Arc, body: &str, str } } +async fn handle_mongo_server_version_data(state: &Arc, body: &str, stream: &mut tokio::net::TcpStream) { + let req: MongoServerVersionRequest = 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; + }; + match dbx_core::mongo_ops::mongo_server_version_core(state, &pool_key, &database).await { + Ok(version) => respond_json(stream, &version).await, + Err(e) => respond_error(stream, "500 Internal Server Error", &e).await, + } +} + async fn handle_mongo_aggregate_documents_data(state: &Arc, body: &str, stream: &mut tokio::net::TcpStream) { let req: MongoAggregateDocumentsRequest = match serde_json::from_str(body) { Ok(r) => r, diff --git a/src-tauri/src/commands/mongo_cmd.rs b/src-tauri/src/commands/mongo_cmd.rs index a6beacf315..1acb2ad63f 100644 --- a/src-tauri/src/commands/mongo_cmd.rs +++ b/src-tauri/src/commands/mongo_cmd.rs @@ -81,6 +81,7 @@ pub async fn mongo_find_documents( skip: u64, limit: i64, filter: Option, + projection: Option, sort: Option, execution_id: Option, ) -> Result { @@ -96,6 +97,7 @@ pub async fn mongo_find_documents( skip, limit, filter.as_deref(), + projection.as_deref(), sort.as_deref(), ), ) @@ -112,6 +114,7 @@ pub async fn document_find_documents( skip: u64, limit: i64, filter: Option, + projection: Option, sort: Option, execution_id: Option, ) -> Result { @@ -127,12 +130,25 @@ pub async fn document_find_documents( skip, limit, filter.as_deref(), + projection.as_deref(), sort.as_deref(), ), ) .await } +#[tauri::command] +pub async fn mongo_server_version( + state: State<'_, Arc>, + connection_id: String, + database: String, + execution_id: Option, +) -> Result { + let app = state.inner().clone(); + run_cancellable(&app, execution_id, dbx_core::mongo_ops::mongo_server_version_core(&app, &connection_id, &database)) + .await +} + #[tauri::command] pub async fn mongo_aggregate_documents( state: State<'_, Arc>, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 77ede2b840..270322c286 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -846,6 +846,7 @@ pub fn run() { commands::mongo_cmd::mongo_drop_collection, commands::mongo_cmd::document_find_documents, commands::mongo_cmd::mongo_find_documents, + commands::mongo_cmd::mongo_server_version, commands::mongo_cmd::mongo_aggregate_documents, commands::mongo_cmd::mongo_insert_document, commands::mongo_cmd::mongo_insert_documents,