fix(chromadb): honor cloud tenant and database

Closes #4486
This commit is contained in:
t8y2 committed 2026-08-13 18:26:48 +00:00
1 parent 98f5c52f8e
commit 9b983441d7
7 files changed
+217 -62

No files matched your search

@@ -292,6 +292,7 @@ const activeResultConnectionId = computed(() => activeResultExecutionTarget.valu
const activeResultDatabase = computed(() => activeResultExecutionTarget.value?.database ?? props.activeTab.database);
const activeResultSchema = computed(() => activeResultExecutionTarget.value?.schema ?? props.activeTab.schema);
const activeEffectiveDatabaseType = computed(() => effectiveDatabaseTypeForConnection(activeResultConnection.value));
const activeVectorConnection = computed(() => connectionStore.getConfig(props.activeTab.connectionId) ?? props.activeConnection);
const activeDataTabExecutionDatabase = computed(() => dataTabExecutionDatabase(props.activeConnection, props.activeTab.database, activeDataTabTableMeta.value?.catalog));
const activeProductionContext = computed(() => productionContextForDatabase(props.activeConnection, props.activeTab.database));
const productionWatermarkText = computed(() => (locale.value.startsWith("zh") ? "生产环境" : "PROD"));
@@ -2071,7 +2072,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
<!-- Vector mode: Qdrant and Milvus collections -->
<template v-else-if="activeTab.mode === 'vector'">
<div class="flex-1 min-h-0">
<VectorBrowser :key="activeTab.id" :connection-id="activeTab.connectionId" :database="activeTab.database" :collection="activeTab.sql" :collection-label="activeTab.title" :database-type="activeEffectiveDatabaseType" :dimension="activeTabDimension" />
<VectorBrowser :key="activeTab.id" :connection-id="activeTab.connectionId" :database="activeTab.database" :collection="activeTab.sql" :collection-label="activeTab.title" :database-type="activeEffectiveDatabaseType" :dimension="activeTabDimension" :tenant="activeVectorConnection?.username" />
</div>
</template>
@@ -21,6 +21,7 @@ const props = defineProps<{
collectionLabel?: string;
databaseType?: DatabaseType;
dimension?: number;
tenant?: string;
}>();
const loading = ref(false);
@@ -248,16 +249,19 @@ function defaultRequestText(databaseType: DatabaseType | undefined, database: st
}
if (databaseType === "chromadb") {
const collectionId = collection || "collection-id";
const tenant = encodeURIComponent(props.tenant?.trim() || "default_tenant");
const chromaDatabase = encodeURIComponent(database.trim() || "default_database");
const collectionPath = `/api/v2/tenants/${tenant}/databases/${chromaDatabase}/collections/${encodeURIComponent(collectionId)}`;
if (mode === "delete") {
return `POST /api/v2/tenants/default_tenant/databases/default_database/collections/${encodeURIComponent(collectionId)}/delete\n${JSON.stringify({ ids: ["id1"] }, null, 2)}`;
return `POST ${collectionPath}/delete\n${JSON.stringify({ ids: ["id1"] }, null, 2)}`;
}
if (mode === "upsert") {
return `POST /api/v2/tenants/default_tenant/databases/default_database/collections/${encodeURIComponent(collectionId)}/upsert\n${JSON.stringify({ ids: ["id1"], embeddings: [sampleVector()], documents: ["sample document"], metadatas: [{}] }, null, 2)}`;
return `POST ${collectionPath}/upsert\n${JSON.stringify({ ids: ["id1"], embeddings: [sampleVector()], documents: ["sample document"], metadatas: [{}] }, null, 2)}`;
}
if (mode === "search") {
return `POST /api/v2/tenants/default_tenant/databases/default_database/collections/${encodeURIComponent(collectionId)}/query\n${JSON.stringify({ query_embeddings: [tryParseVector(searchVector.value)], n_results: searchTopK.value, include: ["documents", "metadatas", "distances"] }, null, 2)}`;
return `POST ${collectionPath}/query\n${JSON.stringify({ query_embeddings: [tryParseVector(searchVector.value)], n_results: searchTopK.value, include: ["documents", "metadatas", "distances"] }, null, 2)}`;
}
return `POST /api/v2/tenants/default_tenant/databases/default_database/collections/${encodeURIComponent(collectionId)}/get\n${JSON.stringify({ limit: 100, include: ["documents", "metadatas"] }, null, 2)}`;
return `POST ${collectionPath}/get\n${JSON.stringify({ limit: 100, include: ["documents", "metadatas"] }, null, 2)}`;
}
const collectionPath = pathSegment(collection);
if (mode === "delete") {
@@ -100,7 +100,8 @@ describe("VectorBrowser default requests", () => {
["weaviate", "GET /v1/objects?class=demo&limit=100"],
["chromadb", "POST /api/v2/tenants/default_tenant/databases/default_database/collections/demo/get"],
] as const) {
app = createApp(VectorBrowser, { connectionId: "vector-1", database: "default", collection: "demo", databaseType });
const database = databaseType === "chromadb" ? "default_database" : "default";
app = createApp(VectorBrowser, { connectionId: "vector-1", database, collection: "demo", databaseType });
app.mount(root!);
await flushUi();
expect(root!.querySelector<HTMLTextAreaElement>("textarea")!.value).toContain(expected);
@@ -111,6 +112,31 @@ describe("VectorBrowser default requests", () => {
expect(backend.vectorGetCollectionDetail).not.toHaveBeenCalled();
});
it("uses the configured Chroma Cloud namespace in generated requests", async () => {
app = createApp(VectorBrowser, {
connectionId: "chroma-cloud-1",
database: "support/kb",
collection: "collection/id",
databaseType: "chromadb",
tenant: "tenant /eu",
});
app.mount(root!);
await flushUi();
const prefix = "/api/v2/tenants/tenant%20%2Feu/databases/support%2Fkb/collections/collection%2Fid";
expect(root!.querySelector<HTMLTextAreaElement>("textarea")!.value).toContain(`POST ${prefix}/get`);
for (const [button, operation] of [
["vector.search", "query"],
["vector.upsert", "upsert"],
["vector.delete", "delete"],
] as const) {
buttonWithText(button).click();
await nextTick();
expect(root!.querySelector<HTMLTextAreaElement>("textarea")!.value).toContain(`POST ${prefix}/${operation}`);
}
});
it("resolves the schema before executing a generated upsert request", async () => {
let resolveDetail: (value: CollectionInfo) => void;
backend.vectorGetCollectionDetail.mockReturnValue(
+1 -1
View File
@@ -4161,7 +4161,7 @@ export const useConnectionStore = defineStore("connection", () => {
async function loadVectorCollections(connectionId: string, database?: string) {
const config = getConfig(connectionId);
const isMilvus = config?.db_type === "milvus";
const effectiveDb = database || config?.database || "default";
const effectiveDb = database || config?.database || (config?.db_type === "chromadb" ? "default_database" : "default");
// Milvus groups collections under a per-database node; other vector stores stay flat under the connection.
const node = isMilvus && database ? findNode(treeNodes.value, `${connectionId}:${database}`) : findConnectionNode(connectionId);
if (!node) return;