diff --git a/apps/desktop/src/composables/useNavigationTargets.ts b/apps/desktop/src/composables/useNavigationTargets.ts
index a3ee5c6a42..3f08842e9b 100644
--- a/apps/desktop/src/composables/useNavigationTargets.ts
+++ b/apps/desktop/src/composables/useNavigationTargets.ts
@@ -25,7 +25,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
connectionStore.activeConnectionId = target.connectionId;
const config = connectionStore.getConfig(target.connectionId);
const tabTitle = target.schema ? `${target.schema}.${target.tableName}` : target.tableName;
- if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate") {
+ if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate" || config?.db_type === "chromadb") {
await connectionStore.ensureConnected(target.connectionId);
const tabId = queryStore.createTab(target.connectionId, target.database || "default", tabTitle, "vector");
queryStore.updateSql(tabId, target.tableName);
diff --git a/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts b/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts
index 67734c2208..969b21e79f 100644
--- a/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts
+++ b/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts
@@ -331,6 +331,7 @@ describe("supportsExecutionTargetPicker", () => {
expect(supportsExecutionTargetPicker("qdrant")).toBe(false);
expect(supportsExecutionTargetPicker("milvus")).toBe(false);
expect(supportsExecutionTargetPicker("weaviate")).toBe(false);
+ expect(supportsExecutionTargetPicker("chromadb")).toBe(false);
expect(supportsExecutionTargetPicker("etcd")).toBe(false);
expect(supportsExecutionTargetPicker("zookeeper")).toBe(false);
expect(supportsExecutionTargetPicker("mq")).toBe(false);
diff --git a/apps/desktop/src/lib/connectionPresentation.ts b/apps/desktop/src/lib/connectionPresentation.ts
index f5a77b59a8..431f25374c 100644
--- a/apps/desktop/src/lib/connectionPresentation.ts
+++ b/apps/desktop/src/lib/connectionPresentation.ts
@@ -133,6 +133,7 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string {
case "qdrant":
case "milvus":
case "weaviate":
+ case "chromadb":
return "http://user:password@host:port";
case "dameng":
diff --git a/apps/desktop/src/lib/connectionUrl.ts b/apps/desktop/src/lib/connectionUrl.ts
index 7677096975..cc3a3eb69c 100644
--- a/apps/desktop/src/lib/connectionUrl.ts
+++ b/apps/desktop/src/lib/connectionUrl.ts
@@ -43,6 +43,7 @@ const SCHEME_PROFILES: Record
= {
qdrant: { type: "qdrant", profile: "qdrant", label: "Qdrant", defaultPort: 6333 },
milvus: { type: "milvus", profile: "milvus", label: "Milvus", defaultPort: 19530 },
weaviate: { type: "weaviate", profile: "weaviate", label: "Weaviate", defaultPort: 8080 },
+ chromadb: { type: "chromadb", profile: "chromadb", label: "ChromaDB", defaultPort: 8000 },
dm: { type: "dameng", profile: "dm", label: "DM (Dameng)", defaultPort: 5236 },
dameng: { type: "dameng", profile: "dm", label: "DM (Dameng)", defaultPort: 5236 },
gaussdb: { type: "gaussdb", profile: "gaussdb", label: "GaussDB", defaultPort: 5432 },
@@ -66,6 +67,7 @@ const HTTP_SELECTED_PROFILES: Record = {
qdrant: SCHEME_PROFILES.qdrant,
milvus: SCHEME_PROFILES.milvus,
weaviate: SCHEME_PROFILES.weaviate,
+ chromadb: SCHEME_PROFILES.chromadb,
};
function decodeUrlPart(value: string): string {
diff --git a/apps/desktop/src/lib/connectionVisibleDatabases.ts b/apps/desktop/src/lib/connectionVisibleDatabases.ts
index 34a28f1dc4..39aa23ca33 100644
--- a/apps/desktop/src/lib/connectionVisibleDatabases.ts
+++ b/apps/desktop/src/lib/connectionVisibleDatabases.ts
@@ -3,7 +3,7 @@ import { filterDatabaseNamesForConnection, normalizeVisibleDatabaseSelection } f
const DRAFT_VISIBLE_DATABASES_PREFIX = "__visible_draft_";
-const UNSUPPORTED_VISIBLE_DATABASE_TYPES = new Set(["elasticsearch", "qdrant", "milvus", "weaviate", "etcd", "zookeeper"]);
+const UNSUPPORTED_VISIBLE_DATABASE_TYPES = new Set(["elasticsearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper"]);
type VisibleDatabaseConnectionFields = Pick<
ConnectionConfig,
diff --git a/apps/desktop/src/lib/http.ts b/apps/desktop/src/lib/http.ts
index 9c3483c585..6c02e6a36f 100644
--- a/apps/desktop/src/lib/http.ts
+++ b/apps/desktop/src/lib/http.ts
@@ -30,6 +30,7 @@ import type {
SavedSqlFolder,
SavedSqlLibrary,
} from "@/types/database";
+import type { CollectionInfo } from "@/types/database";
import type { SchemaDiffPreparation, SchemaDiffPreparationOptions, TableDiff, FunctionDiff, SequenceDiff, RuleDiff, OwnerDiff } from "@/lib/schemaDiff";
import type { SidebarObjectKind } from "@/lib/databaseObjectCapabilities";
import type { AiConfig, AiTestConnectionResult } from "@/stores/settingsStore";
@@ -1684,7 +1685,7 @@ export async function mongoListDatabases(connectionId: string): Promise {
+export async function mongoListCollections(connectionId: string, database: string): Promise {
return post("/api/mongo/list-collections", { connectionId, database });
}
@@ -1701,10 +1702,11 @@ export async function mongoDropCollection(connectionId: string, database: string
}
export async function elasticsearchListIndices(connectionId: string): Promise {
- return mongoListCollections(connectionId, "default");
+ const collections = await mongoListCollections(connectionId, "default");
+ return collections.map((c) => c.name);
}
-export async function vectorListCollections(connectionId: string): Promise {
+export async function vectorListCollections(connectionId: string): Promise {
return mongoListCollections(connectionId, "default");
}
diff --git a/apps/desktop/src/lib/sidebarNodeOrdering.ts b/apps/desktop/src/lib/sidebarNodeOrdering.ts
index 491d0d5fe7..26bfe22304 100644
--- a/apps/desktop/src/lib/sidebarNodeOrdering.ts
+++ b/apps/desktop/src/lib/sidebarNodeOrdering.ts
@@ -30,7 +30,7 @@ export function sortSidebarTreeChildrenForParent(parent: Pick,
const regularChildren = normalized.filter((child) => child.type !== "user-admin" && child.type !== "saved-sql-root");
const withConnectionUtilityOrder = (children: TreeNode[]) => [...savedSqlNodes, ...children, ...userAdminNodes];
- if (databaseType === "mongodb" || databaseType === "elasticsearch" || databaseType === "qdrant" || databaseType === "milvus" || databaseType === "weaviate") {
+ if (databaseType === "mongodb" || databaseType === "elasticsearch" || databaseType === "qdrant" || databaseType === "milvus" || databaseType === "weaviate" || databaseType === "chromadb") {
return withConnectionUtilityOrder(sortByLabel(regularChildren));
}
diff --git a/apps/desktop/src/lib/sqlSemanticDiagnostics.ts b/apps/desktop/src/lib/sqlSemanticDiagnostics.ts
index e83821bb47..7cee9eef41 100644
--- a/apps/desktop/src/lib/sqlSemanticDiagnostics.ts
+++ b/apps/desktop/src/lib/sqlSemanticDiagnostics.ts
@@ -80,7 +80,7 @@ export function areSqlSemanticDiagnosticsEqual(left: readonly SqlSemanticDiagnos
}
export function shouldRunSqlSemanticDiagnostics(sql: string, cursor: number, options: { databaseType?: DatabaseType } = {}): boolean {
- if (options.databaseType === "mongodb" || options.databaseType === "elasticsearch" || options.databaseType === "qdrant" || options.databaseType === "milvus" || options.databaseType === "weaviate" || options.databaseType === "redis") return false;
+ if (options.databaseType === "mongodb" || options.databaseType === "elasticsearch" || options.databaseType === "qdrant" || options.databaseType === "milvus" || options.databaseType === "weaviate" || options.databaseType === "chromadb" || options.databaseType === "redis") return false;
const context = getSqlCompletionContext(sql, cursor);
if (context.suggestTables || context.exclusiveTableSuggestions || context.exclusiveColumnSuggestions) return false;
if (context.qualifier) return false;
diff --git a/apps/desktop/src/lib/sqlStatementRanges.ts b/apps/desktop/src/lib/sqlStatementRanges.ts
index af3a845365..32e2ac40e7 100644
--- a/apps/desktop/src/lib/sqlStatementRanges.ts
+++ b/apps/desktop/src/lib/sqlStatementRanges.ts
@@ -11,7 +11,7 @@ export interface SqlTextRange {
sql: string;
}
-const NON_SQL_EXECUTION_TARGET_TYPES: ReadonlySet = new Set(["mongodb", "elasticsearch", "qdrant", "milvus", "weaviate", "etcd", "zookeeper", "mq", "neo4j"]);
+const NON_SQL_EXECUTION_TARGET_TYPES: ReadonlySet = new Set(["mongodb", "elasticsearch", "qdrant", "milvus", "weaviate", "chromadb", "etcd", "zookeeper", "mq", "neo4j"]);
export function supportsExecutionTargetPicker(databaseType?: DatabaseType): boolean {
return !!databaseType && (databaseType === "redis" || !NON_SQL_EXECUTION_TARGET_TYPES.has(databaseType));
@@ -89,6 +89,7 @@ const DATABASE_SOFT_STATEMENT_KEYWORDS: Partial {
+export async function mongoListCollections(connectionId: string, database: string): Promise {
return invoke("mongo_list_collections", { connectionId, database });
}
@@ -1411,10 +1412,11 @@ export async function mongoDropCollection(connectionId: string, database: string
}
export async function elasticsearchListIndices(connectionId: string): Promise {
- return mongoListCollections(connectionId, "default");
+ const collections = await mongoListCollections(connectionId, "default");
+ return collections.map((c) => c.name);
}
-export async function vectorListCollections(connectionId: string): Promise {
+export async function vectorListCollections(connectionId: string): Promise {
return mongoListCollections(connectionId, "default");
}
diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts
index 3e0bf9a655..aeb2a1709f 100644
--- a/apps/desktop/src/stores/connectionStore.ts
+++ b/apps/desktop/src/stores/connectionStore.ts
@@ -410,6 +410,7 @@ export const useConnectionStore = defineStore("connection", () => {
qdrant: "Qdrant",
milvus: "Milvus",
weaviate: "Weaviate",
+ chromadb: "ChromaDB",
doris: "Doris",
starrocks: "StarRocks",
manticoresearch: "Manticore Search",
@@ -1061,7 +1062,7 @@ export const useConnectionStore = defineStore("connection", () => {
await loadMongoDatabases(connectionId);
} else if (config.db_type === "elasticsearch") {
await loadElasticsearchIndices(connectionId);
- } else if (config.db_type === "qdrant" || config.db_type === "milvus" || config.db_type === "weaviate") {
+ } else if (config.db_type === "qdrant" || config.db_type === "milvus" || config.db_type === "weaviate" || config.db_type === "chromadb") {
await loadVectorCollections(connectionId);
} else if (config.db_type === "mq") {
await loadMqTenants(connectionId, { force: true });
@@ -1596,17 +1597,19 @@ export const useConnectionStore = defineStore("connection", () => {
try {
await ensureConnected(connectionId);
const collections = await withMetadataLoadTimeout(connectionId, api.vectorListCollections(connectionId), "vector collections");
+ const sorted = [...collections].sort((a, b) => a.name.localeCompare(b.name));
setChildren(
node,
withSavedSqlRoot(
connectionId,
- sortSidebarNames(collections).map((collection) => ({
- id: `${connectionId}:__vector_collection:${collection}`,
- label: collection,
+ sorted.map((info) => ({
+ id: `${connectionId}:__vector_collection:${info.id}`,
+ label: info.name,
type: "vector-collection" as const,
connectionId,
database: "default",
isExpanded: false,
+ meta: info.dimension != null ? { dimension: info.dimension } : undefined,
})),
node,
),
@@ -1628,9 +1631,10 @@ export const useConnectionStore = defineStore("connection", () => {
node.isLoading = true;
try {
const collections = await api.mongoListCollections(connectionId, database);
+ const names = collections.map((c) => c.name);
setChildren(
node,
- sortSidebarNames(collections).map((col) => ({
+ sortSidebarNames(names).map((col) => ({
id: `${nodeId}:${col}`,
label: col,
type: "mongo-collection" as const,
@@ -2291,7 +2295,7 @@ export const useConnectionStore = defineStore("connection", () => {
await loadMongoDatabases(node.connectionId);
} else if (config?.db_type === "elasticsearch") {
await loadElasticsearchIndices(node.connectionId);
- } else if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate") {
+ } else if (config?.db_type === "qdrant" || config?.db_type === "milvus" || config?.db_type === "weaviate" || config?.db_type === "chromadb") {
await loadVectorCollections(node.connectionId);
} else if (config?.db_type === "mq") {
await loadMqTenants(node.connectionId, options);
@@ -2779,7 +2783,7 @@ export const useConnectionStore = defineStore("connection", () => {
if (cached) return cached;
return withCompletionInFlight(`${cacheKey}:mongo-collections`, async () => {
await ensureConnected(connectionId);
- const collections = sortSidebarNames(await api.mongoListCollections(connectionId, database));
+ const collections = sortSidebarNames((await api.mongoListCollections(connectionId, database)).map((c) => c.name));
mongoCompletionCollectionsCache.value[cacheKey] = collections;
evictOldestCacheEntries(mongoCompletionCollectionsCache.value, COMPLETION_CACHE_MAX);
return collections;
diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts
index d484068f06..071a75105b 100644
--- a/apps/desktop/src/types/database.ts
+++ b/apps/desktop/src/types/database.ts
@@ -14,6 +14,7 @@ export type DatabaseType =
| "qdrant"
| "milvus"
| "weaviate"
+ | "chromadb"
| "doris"
| "starrocks"
| "manticoresearch"
@@ -544,7 +545,7 @@ export interface TreeNode {
hiddenChildren?: TreeNode[];
savedSqlId?: string;
savedSqlFolderId?: string;
- meta?: ColumnInfo | IndexInfo | ForeignKeyInfo | TriggerInfo;
+ meta?: ColumnInfo | IndexInfo | ForeignKeyInfo | TriggerInfo | VectorCollectionMeta;
loadMore?: {
parentId: string;
offset: number;
@@ -682,3 +683,13 @@ export interface SavedSqlLibrary {
folders: SavedSqlFolder[];
files: SavedSqlFile[];
}
+
+export interface VectorCollectionMeta {
+ dimension?: number;
+}
+
+export interface CollectionInfo {
+ name: string;
+ id: string;
+ dimension?: number;
+}
diff --git a/crates/dbx-core/assets/database-drivers.manifest.json b/crates/dbx-core/assets/database-drivers.manifest.json
index c045529f1f..e86feb5c15 100644
--- a/crates/dbx-core/assets/database-drivers.manifest.json
+++ b/crates/dbx-core/assets/database-drivers.manifest.json
@@ -349,6 +349,35 @@
"driverManagement": false
}
},
+ {
+ "dbType": "chromadb",
+ "label": "ChromaDB",
+ "runtimeMode": "native",
+ "mcpMode": "bridge",
+ "singleConnectionPool": false,
+ "metadataConnectionScoped": false,
+ "skipTcpProbe": false,
+ "defaultPort": 8000,
+ "supportLevel": "browse",
+ "capabilities": {
+ "queryExecution": true,
+ "metadataBrowse": true,
+ "objectBrowser": false,
+ "objectSource": false,
+ "schemaSearch": false,
+ "diagram": false,
+ "tableDataEdit": false,
+ "tableStructureEdit": false,
+ "tableImport": false,
+ "dataTransfer": false,
+ "sqlFileExecution": false,
+ "databaseCreate": false,
+ "fieldLineage": false,
+ "sqlExplain": false,
+ "userAdmin": false,
+ "driverManagement": false
+ }
+ },
{
"dbType": "milvus",
"label": "Milvus",
diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs
index d1a8cdd7f2..a3844088e4 100644
--- a/crates/dbx-core/src/connection.rs
+++ b/crates/dbx-core/src/connection.rs
@@ -832,11 +832,12 @@ impl AppState {
db::elasticsearch_driver::test_connection(&mut client, connect_timeout).await?;
PoolKind::Elasticsearch(client)
}
- DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
+ DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate | DatabaseType::ChromaDb => {
let kind = match db_config.db_type {
DatabaseType::Qdrant => db::vector_driver::VectorDbKind::Qdrant,
DatabaseType::Milvus => db::vector_driver::VectorDbKind::Milvus,
DatabaseType::Weaviate => db::vector_driver::VectorDbKind::Weaviate,
+ DatabaseType::ChromaDb => db::vector_driver::VectorDbKind::ChromaDb,
_ => unreachable!(),
};
let client = db::vector_driver::VectorClient::new(
@@ -2140,7 +2141,11 @@ fn base_pool_key_for(
|| (include_elasticsearch_single_pool
&& matches!(
db_type,
- DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate
+ DatabaseType::Elasticsearch
+ | DatabaseType::Qdrant
+ | DatabaseType::Milvus
+ | DatabaseType::Weaviate
+ | DatabaseType::ChromaDb
));
is_single && (!database_capabilities::is_agent_type(db_type) || shares_database_pool_with_connection(db_type))
});
diff --git a/crates/dbx-core/src/db/vector_driver.rs b/crates/dbx-core/src/db/vector_driver.rs
index a74d404a03..4dbd8e1511 100644
--- a/crates/dbx-core/src/db/vector_driver.rs
+++ b/crates/dbx-core/src/db/vector_driver.rs
@@ -27,11 +27,19 @@ const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &CONTROLS
const QUERY_VALUE_ENCODE_SET: &AsciiSet = &PATH_SEGMENT_ENCODE_SET.add(b'&').add(b'=').add(b'+');
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct CollectionInfo {
+ pub name: String,
+ pub id: String,
+ pub dimension: Option,
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VectorDbKind {
Qdrant,
Milvus,
Weaviate,
+ ChromaDb,
}
impl VectorDbKind {
@@ -40,6 +48,7 @@ impl VectorDbKind {
VectorDbKind::Qdrant => "Qdrant",
VectorDbKind::Milvus => "Milvus",
VectorDbKind::Weaviate => "Weaviate",
+ VectorDbKind::ChromaDb => "ChromaDB",
}
}
}
@@ -57,6 +66,7 @@ enum VectorAuth {
Basic(String, String),
Bearer(String),
ApiKey(String),
+ ChromaToken(String),
}
impl VectorClient {
@@ -96,6 +106,7 @@ impl VectorClient {
Some(VectorAuth::Basic(user, pass)) => req.basic_auth(user, Some(pass)),
Some(VectorAuth::Bearer(token)) => req.bearer_auth(token),
Some(VectorAuth::ApiKey(token)) => req.header("api-key", token),
+ Some(VectorAuth::ChromaToken(token)) => req.header("x-chroma-token", token),
None => req,
}
}
@@ -114,6 +125,8 @@ fn vector_auth(kind: VectorDbKind, username: Option<&str>, password: Option<&str
VectorDbKind::Milvus => None,
VectorDbKind::Weaviate if !password.is_empty() => Some(VectorAuth::Bearer(password.to_string())),
VectorDbKind::Weaviate => None,
+ VectorDbKind::ChromaDb if !password.is_empty() => Some(VectorAuth::ChromaToken(password.to_string())),
+ VectorDbKind::ChromaDb => None,
}
}
@@ -123,11 +136,13 @@ pub async fn test_connection(client: &VectorClient, timeout: Duration) -> Result
VectorDbKind::Qdrant => "/collections",
VectorDbKind::Milvus => "/v2/vectordb/collections/list",
VectorDbKind::Weaviate => "/v1/meta",
+ VectorDbKind::ChromaDb => "/api/v2/heartbeat",
};
let request = match client.kind {
VectorDbKind::Qdrant => client.get(path),
VectorDbKind::Milvus => client.post(path).json(&serde_json::json!({ "dbName": "default" })),
VectorDbKind::Weaviate => client.get(path),
+ VectorDbKind::ChromaDb => client.get(path),
};
let resp = with_connection_timeout(label, timeout, async {
request.send().await.map_err(|e| format!("{label} connection failed: {}", format_reqwest_error(&e)))
@@ -136,39 +151,49 @@ pub async fn test_connection(client: &VectorClient, timeout: Duration) -> Result
ensure_success(label, resp).await.map(|_| ())
}
-pub async fn list_collections(client: &VectorClient) -> Result, String> {
+pub async fn list_collections(client: &VectorClient) -> Result, String> {
match client.kind {
VectorDbKind::Qdrant => list_qdrant_collections(client).await,
VectorDbKind::Milvus => list_milvus_collections(client).await,
VectorDbKind::Weaviate => list_weaviate_collections(client).await,
+ VectorDbKind::ChromaDb => list_chroma_collections(client).await,
}
}
-async fn list_qdrant_collections(client: &VectorClient) -> Result, String> {
+async fn list_qdrant_collections(client: &VectorClient) -> Result, String> {
let body = send_json(client.get("/collections"), "Qdrant").await?;
- let mut names: Vec = body
+ let mut infos: Vec = body
.pointer("/result/collections")
.and_then(Value::as_array)
.into_iter()
.flatten()
- .filter_map(|item| item.get("name").and_then(Value::as_str).map(str::to_string))
+ .filter_map(|item| {
+ let name = item.get("name").and_then(Value::as_str)?;
+ Some(CollectionInfo { name: name.to_string(), id: name.to_string(), dimension: None })
+ })
.collect();
- names.sort();
- Ok(names)
+ infos.sort_by(|a, b| a.name.cmp(&b.name));
+ Ok(infos)
}
-async fn list_milvus_collections(client: &VectorClient) -> Result, String> {
+async fn list_milvus_collections(client: &VectorClient) -> Result, String> {
let body = send_json(
client.post("/v2/vectordb/collections/list").json(&serde_json::json!({ "dbName": "default" })),
"Milvus",
)
.await?;
- let mut names: Vec = match body.get("data") {
- Some(Value::Array(items)) => items.iter().filter_map(collection_name_from_milvus_item).collect(),
+ let mut infos: Vec = match body.get("data") {
+ Some(Value::Array(items)) => items
+ .iter()
+ .filter_map(|item| {
+ let name = collection_name_from_milvus_item(item)?;
+ Some(CollectionInfo { name: name.clone(), id: name, dimension: None })
+ })
+ .collect(),
_ => Vec::new(),
};
- names.sort();
- Ok(names)
+ infos.sort_by(|a, b| a.name.cmp(&b.name));
+ Ok(infos)
}
fn collection_name_from_milvus_item(item: &Value) -> Option {
@@ -178,11 +203,56 @@ fn collection_name_from_milvus_item(item: &Value) -> Option {
.or_else(|| item.get("name").and_then(Value::as_str).map(str::to_string))
}
-async fn list_weaviate_collections(client: &VectorClient) -> Result, String> {
+async fn list_weaviate_collections(client: &VectorClient) -> Result, String> {
let body = send_json(client.get("/v1/schema"), "Weaviate").await?;
- let mut names = weaviate_collection_names_from_schema(&body);
- names.sort();
- Ok(names)
+ let mut infos: Vec = weaviate_collection_names_from_schema(&body)
+ .into_iter()
+ .map(|name| CollectionInfo { name: name.clone(), id: name, dimension: None })
+ .collect();
+ infos.sort_by(|a, b| a.name.cmp(&b.name));
+ Ok(infos)
+}
+
+async fn list_chroma_collections(client: &VectorClient) -> Result, String> {
+ let body =
+ send_json(client.get("/api/v2/tenants/default_tenant/databases/default_database/collections"), "ChromaDB")
+ .await?;
+ let mut infos: Vec = body
+ .as_array()
+ .into_iter()
+ .flatten()
+ .filter_map(|item| {
+ let name = item.get("name").and_then(Value::as_str)?;
+ let id = item.get("id").and_then(Value::as_str)?;
+ let dimension = item.get("dimension").and_then(|v| v.as_u64()).map(|d| d as u32);
+ Some(CollectionInfo { name: name.to_string(), id: id.to_string(), dimension })
+ })
+ .collect();
+ infos.sort_by(|a, b| a.name.cmp(&b.name));
+ Ok(infos)
+}
+
+fn chroma_get_response_to_rows(body: &Value) -> Vec {
+ let ids = body.get("ids").and_then(Value::as_array).cloned().unwrap_or_default();
+ let docs = body.get("documents").and_then(Value::as_array).cloned().unwrap_or_default();
+ let metas = body.get("metadatas").and_then(Value::as_array).cloned().unwrap_or_default();
+
+ ids.into_iter()
+ .enumerate()
+ .map(|(i, id_val)| {
+ let mut row = serde_json::Map::new();
+ row.insert("id".to_string(), id_val);
+ if let Some(doc) = docs.get(i) {
+ row.insert("document".to_string(), doc.clone());
+ }
+ if let Some(Value::Object(meta_obj)) = metas.get(i) {
+ for (k, v) in meta_obj {
+ row.insert(k.clone(), v.clone());
+ }
+ }
+ Value::Object(row)
+ })
+ .collect()
}
fn weaviate_collection_names_from_schema(body: &Value) -> Vec {
@@ -200,6 +270,45 @@ pub async fn find_documents(
skip: u64,
limit: i64,
) -> Result {
+ if client.kind == VectorDbKind::ChromaDb {
+ let start = std::time::Instant::now();
+ let url = format!(
+ "{}/api/v2/tenants/default_tenant/databases/default_database/collections/{}/get",
+ client.base_url,
+ path_segment(collection),
+ );
+ let resp = client
+ .with_auth(client.http.post(&url))
+ .json(&serde_json::json!({
+ "limit": limit.max(1) as u64,
+ "offset": skip,
+ "include": ["documents", "metadatas"],
+ }))
+ .send()
+ .await
+ .map_err(|e| format!("ChromaDB request failed: {}", format_reqwest_error(&e)))?;
+ if !resp.status().is_success() {
+ let status = resp.status();
+ let body = resp.text().await.unwrap_or_default();
+ return Err(format!("ChromaDB error ({status}): {body}"));
+ }
+ let body: Value = resp.json().await.unwrap_or(Value::Null);
+ let rows = chroma_get_response_to_rows(&body);
+ let result = values_to_query_result(rows, start);
+ let documents = result
+ .rows
+ .into_iter()
+ .map(|row| {
+ let mut map = serde_json::Map::new();
+ for (idx, col) in result.columns.iter().enumerate() {
+ map.insert(col.clone(), row.get(idx).cloned().unwrap_or(Value::Null));
+ }
+ Value::Object(map)
+ })
+ .collect();
+ return Ok(crate::db::mongo_driver::MongoDocumentResult { documents, total: result.affected_rows });
+ }
+
let query = match client.kind {
VectorDbKind::Qdrant => format!(
"POST /collections/{}/points/scroll\n{}",
@@ -225,6 +334,7 @@ pub async fn find_documents(
VectorDbKind::Weaviate => {
format!("GET /v1/objects?class={}&limit={}&offset={}", query_value(collection), limit.max(1), skip)
}
+ VectorDbKind::ChromaDb => unreachable!("ChromaDB handled above"),
};
let result = execute_rest_query(client, &query).await?;
let documents = result
@@ -301,6 +411,12 @@ fn default_collection_query(client: &VectorClient, collection: &str) -> Result Ok(client.get(&format!("/v1/objects?class={}&limit=100", query_value(collection)))),
+ VectorDbKind::ChromaDb => Ok(client
+ .post(&format!(
+ "/api/v2/tenants/default_tenant/databases/default_database/collections/{}/get",
+ path_segment(collection)
+ ))
+ .json(&serde_json::json!({"limit": 100, "include": ["documents", "metadatas"]}))),
}
}
@@ -343,6 +459,11 @@ fn json_to_query_result(status: u16, body: Value, start: Instant) -> QueryResult
if let Some(Value::Array(items)) = body.pointer("/result/collections").cloned() {
return values_to_query_result(items, start);
}
+ if body.get("ids").and_then(Value::as_array).is_some() && body.get("documents").and_then(Value::as_array).is_some()
+ {
+ let rows = chroma_get_response_to_rows(&body);
+ return values_to_query_result(rows, start);
+ }
QueryResult {
columns: vec!["status".to_string(), "response".to_string()],
column_types: Vec::new(),
@@ -428,8 +549,8 @@ fn format_reqwest_error(err: &reqwest::Error) -> String {
#[cfg(test)]
mod tests {
use super::{
- starts_with_http_method, values_to_query_result, vector_auth, weaviate_collection_names_from_schema,
- VectorAuth, VectorDbKind,
+ chroma_get_response_to_rows, starts_with_http_method, values_to_query_result, vector_auth,
+ weaviate_collection_names_from_schema, CollectionInfo, VectorAuth, VectorDbKind,
};
use serde_json::json;
use std::time::Instant;
@@ -479,4 +600,56 @@ mod tests {
Some(VectorAuth::Bearer("token".to_string()))
);
}
+
+ #[test]
+ fn chroma_db_uses_x_chroma_token_header() {
+ assert_eq!(
+ vector_auth(VectorDbKind::ChromaDb, None, Some("my-key")),
+ Some(VectorAuth::ChromaToken("my-key".to_string()))
+ );
+ }
+
+ #[test]
+ fn chroma_db_no_auth_when_no_password() {
+ assert_eq!(vector_auth(VectorDbKind::ChromaDb, None, None), None);
+ }
+
+ #[test]
+ fn parses_chroma_collection_list() {
+ let body = json!([
+ {"id": "uuid-123", "name": "my_collection", "dimension": 384},
+ {"id": "uuid-456", "name": "other", "dimension": 768}
+ ]);
+ let infos: Vec = body
+ .as_array()
+ .unwrap()
+ .iter()
+ .filter_map(|item| {
+ let name = item.get("name").and_then(|v| v.as_str())?;
+ let id = item.get("id").and_then(|v| v.as_str())?;
+ Some(CollectionInfo { name: name.to_string(), id: id.to_string(), dimension: None })
+ })
+ .collect();
+ assert_eq!(infos.len(), 2);
+ assert_eq!(infos[0].name, "my_collection");
+ assert_eq!(infos[0].id, "uuid-123");
+ assert_eq!(infos[1].name, "other");
+ assert_eq!(infos[1].id, "uuid-456");
+ }
+
+ #[test]
+ fn converts_chroma_column_major_to_rows() {
+ let body = json!({
+ "ids": ["id1", "id2"],
+ "documents": ["hello world", "test doc"],
+ "metadatas": [{"source": "test"}, {"source": "demo"}]
+ });
+ let rows = chroma_get_response_to_rows(&body);
+ assert_eq!(rows.len(), 2);
+ assert_eq!(rows[0]["id"], json!("id1"));
+ assert_eq!(rows[0]["document"], json!("hello world"));
+ assert_eq!(rows[0]["source"], json!("test"));
+ assert_eq!(rows[1]["id"], json!("id2"));
+ assert_eq!(rows[1]["source"], json!("demo"));
+ }
}
diff --git a/crates/dbx-core/src/models/connection.rs b/crates/dbx-core/src/models/connection.rs
index 5cca3459f0..b6fc4c3524 100644
--- a/crates/dbx-core/src/models/connection.rs
+++ b/crates/dbx-core/src/models/connection.rs
@@ -263,6 +263,8 @@ pub enum DatabaseType {
Milvus,
#[serde(rename = "weaviate")]
Weaviate,
+ #[serde(rename = "chromadb")]
+ ChromaDb,
Doris,
#[serde(rename = "starrocks")]
StarRocks,
@@ -743,7 +745,11 @@ impl ConnectionConfig {
format!("mongodb://{host}:{port}{db_part}{suffix}")
}
DatabaseType::Oracle => format!("oracle://{host}:{port}{db_part}"),
- DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
+ DatabaseType::Elasticsearch
+ | DatabaseType::Qdrant
+ | DatabaseType::Milvus
+ | DatabaseType::Weaviate
+ | DatabaseType::ChromaDb => {
let scheme = if self.ssl { "https" } else { "http" };
format!("{scheme}://{host}:{port}")
}
@@ -882,7 +888,11 @@ impl ConnectionConfig {
DatabaseType::Oracle => {
format!("oracle://{}:{}@{host}:{port}{db_part}", username, password)
}
- DatabaseType::Elasticsearch | DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
+ DatabaseType::Elasticsearch
+ | DatabaseType::Qdrant
+ | DatabaseType::Milvus
+ | DatabaseType::Weaviate
+ | DatabaseType::ChromaDb => {
let scheme = if self.ssl { "https" } else { "http" };
format!("{scheme}://{host}:{port}")
}
diff --git a/crates/dbx-core/src/mongo_ops.rs b/crates/dbx-core/src/mongo_ops.rs
index 44ade7e6e1..1456b35273 100644
--- a/crates/dbx-core/src/mongo_ops.rs
+++ b/crates/dbx-core/src/mongo_ops.rs
@@ -60,20 +60,29 @@ fn mongo_list_databases_unauthorized(error: &str) -> bool {
lower.contains("not authorized") && lower.contains("listdatabases")
}
+use crate::db::vector_driver::CollectionInfo;
+
pub async fn mongo_list_collections_core(
state: &AppState,
connection_id: &str,
database: &str,
-) -> Result, String> {
+) -> Result, String> {
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::list_collections(client, database).await.map(sort_names),
- PoolKind::Elasticsearch(client) => elasticsearch_driver::list_indices(client).await.map(sort_names),
- PoolKind::VectorDb(client) => vector_driver::list_collections(client).await.map(sort_names),
+ PoolKind::MongoDb(client) => {
+ let names = sort_names(mongo_driver::list_collections(client, database).await?);
+ Ok(names.into_iter().map(|n| CollectionInfo { name: n.clone(), id: n, dimension: None }).collect())
+ }
+ PoolKind::Elasticsearch(client) => {
+ let names = sort_names(elasticsearch_driver::list_indices(client).await?);
+ Ok(names.into_iter().map(|n| CollectionInfo { name: n.clone(), id: n, dimension: None }).collect())
+ }
+ PoolKind::VectorDb(client) => vector_driver::list_collections(client).await,
PoolKind::Agent(client) => {
let mut client = client.lock().await;
- client.mongo_list_collections(database).await.map(sort_names)
+ let names = sort_names(client.mongo_list_collections(database).await?);
+ Ok(names.into_iter().map(|n| CollectionInfo { name: n.clone(), id: n, dimension: None }).collect())
}
_ => Err("Not a MongoDB/Elasticsearch/vector connection".to_string()),
}
diff --git a/crates/dbx-core/src/query.rs b/crates/dbx-core/src/query.rs
index db9f734603..83120379d3 100644
--- a/crates/dbx-core/src/query.rs
+++ b/crates/dbx-core/src/query.rs
@@ -734,6 +734,7 @@ fn should_discard_pool_after_query_timeout(db_type: Option) -> boo
| DatabaseType::Qdrant
| DatabaseType::Milvus
| DatabaseType::Weaviate
+ | DatabaseType::ChromaDb
| DatabaseType::InfluxDb
)
}
diff --git a/crates/dbx-core/src/query_execution_sql.rs b/crates/dbx-core/src/query_execution_sql.rs
index cca7a6d140..0eeb23d9ef 100644
--- a/crates/dbx-core/src/query_execution_sql.rs
+++ b/crates/dbx-core/src/query_execution_sql.rs
@@ -90,6 +90,7 @@ pub fn supports_sql_query(database_type: DatabaseType) -> bool {
| DatabaseType::Qdrant
| DatabaseType::Milvus
| DatabaseType::Weaviate
+ | DatabaseType::ChromaDb
| DatabaseType::InfluxDb
| DatabaseType::Neo4j
| DatabaseType::Etcd
diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs
index 384dccd5d8..49e818461c 100644
--- a/crates/dbx-core/src/schema.rs
+++ b/crates/dbx-core/src/schema.rs
@@ -1057,7 +1057,7 @@ async fn list_tables_once(
.map(|tables| filter_table_infos(tables, filter, limit, offset, object_types)),
PoolKind::VectorDb(client) => db::vector_driver::list_collections(client)
.await
- .map(|names| collection_names_to_tables(names, "COLLECTION"))
+ .map(|infos| collection_names_to_tables(infos.into_iter().map(|i| i.name).collect(), "COLLECTION"))
.map(|tables| filter_table_infos(tables, filter, limit, offset, object_types)),
_ => Ok(vec![]),
}
diff --git a/crates/dbx-core/src/schema/providers/native.rs b/crates/dbx-core/src/schema/providers/native.rs
index 8c4a0b0bcc..74df2b8722 100644
--- a/crates/dbx-core/src/schema/providers/native.rs
+++ b/crates/dbx-core/src/schema/providers/native.rs
@@ -59,7 +59,7 @@ pub(in crate::schema) async fn list_tables(
db::elasticsearch_driver::list_indices(client).await.map(|names| collection_names_to_tables(names, "INDEX"))
}
PoolKind::VectorDb(client) => {
- db::vector_driver::list_collections(client).await.map(|names| collection_names_to_tables(names, "COLLECTION"))
+ db::vector_driver::list_collections(client).await.map(|infos| collection_names_to_tables(infos.into_iter().map(|i| i.name).collect(), "COLLECTION"))
}
_ => Ok(vec![]),
}
diff --git a/crates/dbx-web/src/routes/mongo.rs b/crates/dbx-web/src/routes/mongo.rs
index 34d49531f3..ed79f350ab 100644
--- a/crates/dbx-web/src/routes/mongo.rs
+++ b/crates/dbx-web/src/routes/mongo.rs
@@ -158,7 +158,7 @@ pub async fn list_databases(
pub async fn list_collections(
State(state): State>,
Json(req): Json,
-) -> Result>, AppError> {
+) -> Result>, AppError> {
let result = dbx_core::mongo_ops::mongo_list_collections_core(&state.app, &req.connection_id, &req.database)
.await
.map_err(AppError)?;
diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts
index 3784d753c0..556ab16379 100644
--- a/packages/mcp-server/src/index.ts
+++ b/packages/mcp-server/src/index.ts
@@ -46,7 +46,7 @@ function formatQueryToolResult(result: QueryResult, title?: string) {
}
export const DBX_CONNECTION_TYPE_DESCRIPTION =
- "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, weaviate, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, prestosql, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, zookeeper, jdbc, access, mq";
+ "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, weaviate, chromadb, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, prestosql, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, zookeeper, jdbc, access, mq";
const FILE_CAPABLE_CONNECTION_TYPES = new Set(["sqlite", "duckdb", "access", "h2"]);
interface McpScope {
diff --git a/packages/node-core/src/database.ts b/packages/node-core/src/database.ts
index 88e4d77fb3..c47950571e 100644
--- a/packages/node-core/src/database.ts
+++ b/packages/node-core/src/database.ts
@@ -13,6 +13,14 @@ export interface TableInfo {
type: string;
}
+export interface CollectionInfo {
+ name: string;
+ id?: string;
+ dimension?: number | null;
+}
+
+type CollectionListEntry = string | CollectionInfo;
+
export interface ColumnInfo {
name: string;
data_type: string;
@@ -408,6 +416,13 @@ interface BridgeColumnInfo {
character_maximum_length?: number | null;
}
+export function collectionListToTableInfos(collections: CollectionListEntry[]): TableInfo[] {
+ return collections.map((collection) => ({
+ name: typeof collection === "string" ? collection : collection.name,
+ type: "COLLECTION",
+ }));
+}
+
interface MongoDocumentResult {
documents: unknown[];
total: number;
@@ -646,12 +661,12 @@ export async function executeQuery(config: ConnectionConfig, sql: string, option
export async function listTables(config: ConnectionConfig, schema?: string): Promise {
if (config.db_type === "mongodb") {
- const collections = await bridgeDataRequest("/data/mongo/list-collections", {
+ const collections = await bridgeDataRequest("/data/mongo/list-collections", {
connection_name: config.name,
database: config.database || "",
schema: schema || "",
});
- return collections.map((name) => ({ name, type: "COLLECTION" }));
+ return collectionListToTableInfos(collections);
}
if (config.db_type === "sqlite" || config.db_type === "rqlite") {
const result = await query(config, `SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name`);
diff --git a/packages/node-core/src/diagnostics.ts b/packages/node-core/src/diagnostics.ts
index db501221da..d69bf4c344 100644
--- a/packages/node-core/src/diagnostics.ts
+++ b/packages/node-core/src/diagnostics.ts
@@ -23,6 +23,7 @@ export const BRIDGE_REQUIRED_TYPES = [
"qdrant",
"milvus",
"weaviate",
+ "chromadb",
"etcd",
"dameng",
"kingbase",
diff --git a/packages/node-core/src/web-backend.ts b/packages/node-core/src/web-backend.ts
index e2c2c0a3bf..68602eeb13 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 { evaluateMongoAggregateSafety, evaluateMongoWriteSafety, inferMongoColumns, mongoDocumentsToQueryResult, parseMongoAggregateCommand, parseMongoCountDocumentsCommand, parseMongoFindCommand, parseMongoGetIndexesCommand, parseMongoWriteCommand, type MongoWriteCommand } from "./database.js";
+import { collectionListToTableInfos, evaluateMongoAggregateSafety, evaluateMongoWriteSafety, inferMongoColumns, mongoDocumentsToQueryResult, parseMongoAggregateCommand, parseMongoCountDocumentsCommand, parseMongoFindCommand, parseMongoGetIndexesCommand, parseMongoWriteCommand, type CollectionInfo, type MongoWriteCommand } from "./database.js";
import { sqlSafetyFromEnv } from "./sql-safety.js";
const baseUrl = process.env.DBX_WEB_URL!.replace(/\/+$/, "");
@@ -93,8 +93,8 @@ export async function listTables(config: ConnectionConfig, schema?: string): Pro
method: "POST",
body: JSON.stringify({ connectionId: config.id, database: config.database || "" }),
});
- const collections = (await res.json()) as string[];
- return collections.map((name) => ({ name, type: "COLLECTION" }));
+ const collections = (await res.json()) as Array;
+ return collectionListToTableInfos(collections);
}
const params = new URLSearchParams({
connection_id: config.id,
diff --git a/packages/node-core/tests/collections.test.ts b/packages/node-core/tests/collections.test.ts
new file mode 100644
index 0000000000..1f78568b1b
--- /dev/null
+++ b/packages/node-core/tests/collections.test.ts
@@ -0,0 +1,23 @@
+import assert from "node:assert/strict";
+import { test } from "vitest";
+import { collectionListToTableInfos } from "../src/database.js";
+
+test("maps legacy collection name responses to table infos", () => {
+ assert.deepEqual(collectionListToTableInfos(["projects", "users"]), [
+ { name: "projects", type: "COLLECTION" },
+ { name: "users", type: "COLLECTION" },
+ ]);
+});
+
+test("maps collection info responses to table infos", () => {
+ assert.deepEqual(
+ collectionListToTableInfos([
+ { name: "projects", id: "projects", dimension: null },
+ { name: "embeddings", id: "uuid-123", dimension: 384 },
+ ]),
+ [
+ { name: "projects", type: "COLLECTION" },
+ { name: "embeddings", type: "COLLECTION" },
+ ],
+ );
+});
diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs
index 121fc518e9..ec21b7813c 100644
--- a/src-tauri/src/commands/connection.rs
+++ b/src-tauri/src/commands/connection.rs
@@ -713,11 +713,12 @@ pub async fn test_connection(state: State<'_, Arc>, config: Connection
.await
.map(|_| "Connection successful".to_string())
}
- DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
+ DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate | DatabaseType::ChromaDb => {
let kind = match config.db_type {
DatabaseType::Qdrant => db::vector_driver::VectorDbKind::Qdrant,
DatabaseType::Milvus => db::vector_driver::VectorDbKind::Milvus,
DatabaseType::Weaviate => db::vector_driver::VectorDbKind::Weaviate,
+ DatabaseType::ChromaDb => db::vector_driver::VectorDbKind::ChromaDb,
_ => unreachable!(),
};
let client = db::vector_driver::VectorClient::new(
@@ -999,11 +1000,12 @@ pub async fn connect_db(state: State<'_, Arc>, config: ConnectionConfi
db::elasticsearch_driver::test_connection(&mut client, connect_timeout).await?;
PoolKind::Elasticsearch(client)
}
- DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate => {
+ DatabaseType::Qdrant | DatabaseType::Milvus | DatabaseType::Weaviate | DatabaseType::ChromaDb => {
let kind = match db_config.db_type {
DatabaseType::Qdrant => db::vector_driver::VectorDbKind::Qdrant,
DatabaseType::Milvus => db::vector_driver::VectorDbKind::Milvus,
DatabaseType::Weaviate => db::vector_driver::VectorDbKind::Weaviate,
+ DatabaseType::ChromaDb => db::vector_driver::VectorDbKind::ChromaDb,
_ => unreachable!(),
};
let client = db::vector_driver::VectorClient::new(
diff --git a/src-tauri/src/commands/mongo_cmd.rs b/src-tauri/src/commands/mongo_cmd.rs
index d004eb61ca..a6beacf315 100644
--- a/src-tauri/src/commands/mongo_cmd.rs
+++ b/src-tauri/src/commands/mongo_cmd.rs
@@ -36,7 +36,7 @@ pub async fn mongo_list_collections(
state: State<'_, Arc>,
connection_id: String,
database: String,
-) -> Result, String> {
+) -> Result, String> {
dbx_core::mongo_ops::mongo_list_collections_core(&state, &connection_id, &database).await
}