mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 06:00:39 +00:00
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1109,7 +1109,12 @@ async fn execute_browse_collection(
|
||||
collection.to_string()
|
||||
};
|
||||
|
||||
let query = build_browse_query(db_type, &collection_id, database, limit)?;
|
||||
let tenant = if *db_type == DatabaseType::ChromaDb {
|
||||
state.configs.read().await.get(connection_id).map(|config| config.username.clone()).unwrap_or_default()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let query = build_browse_query(db_type, &collection_id, database, &tenant, limit)?;
|
||||
|
||||
let options = QueryExecutionOptions { max_rows: Some(limit), timeout_secs: Some(30), ..Default::default() };
|
||||
let result =
|
||||
@@ -1125,6 +1130,7 @@ fn build_browse_query(
|
||||
db_type: &DatabaseType,
|
||||
collection: &str,
|
||||
database: &str,
|
||||
tenant: &str,
|
||||
limit: usize,
|
||||
) -> Result<String, String> {
|
||||
let collection = collection.trim();
|
||||
@@ -1151,11 +1157,9 @@ fn build_browse_query(
|
||||
DatabaseType::Weaviate => {
|
||||
Ok(format!("GET /v1/objects?class={}&limit={}", vector_driver::query_value(collection), limit))
|
||||
}
|
||||
// TODO: ChromaDB Cloud 支持自定义租户和数据库,当前只实现了本地部署
|
||||
// (固定 default_tenant / default_database),后续支持云服务时需改为可配置。
|
||||
DatabaseType::ChromaDb => Ok(format!(
|
||||
"POST /api/v2/tenants/default_tenant/databases/default_database/collections/{}/get\n{}",
|
||||
collection,
|
||||
"POST {}/get\n{}",
|
||||
vector_driver::chroma_collection_path(tenant, database, collection, None),
|
||||
serde_json::json!({ "limit": limit, "include": ["documents", "metadatas"] })
|
||||
)),
|
||||
_ => Err(format!("Unsupported database type: {:?}", db_type)),
|
||||
@@ -1721,7 +1725,7 @@ for line in sys.stdin:
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_qdrant() {
|
||||
let q = build_browse_query(&DatabaseType::Qdrant, "articles", "", 10).unwrap();
|
||||
let q = build_browse_query(&DatabaseType::Qdrant, "articles", "", "", 10).unwrap();
|
||||
assert!(q.starts_with("POST /collections/articles/points/scroll"));
|
||||
assert!(q.contains("\"limit\":10"));
|
||||
assert!(q.contains("\"with_payload\":true"));
|
||||
@@ -1729,13 +1733,13 @@ for line in sys.stdin:
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_qdrant_encodes_url_chars() {
|
||||
let q = build_browse_query(&DatabaseType::Qdrant, "my collection", "", 10).unwrap();
|
||||
let q = build_browse_query(&DatabaseType::Qdrant, "my collection", "", "", 10).unwrap();
|
||||
assert!(q.starts_with("POST /collections/my%20collection/points/scroll"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_milvus() {
|
||||
let q = build_browse_query(&DatabaseType::Milvus, "articles", "custom_db", 20).unwrap();
|
||||
let q = build_browse_query(&DatabaseType::Milvus, "articles", "custom_db", "", 20).unwrap();
|
||||
assert!(q.starts_with("POST /v2/vectordb/entities/query"));
|
||||
assert!(q.contains("\"dbName\":\"custom_db\""));
|
||||
assert!(q.contains("\"collectionName\":\"articles\""));
|
||||
@@ -1745,40 +1749,46 @@ for line in sys.stdin:
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_milvus_default_db() {
|
||||
let q = build_browse_query(&DatabaseType::Milvus, "articles", "", 10).unwrap();
|
||||
let q = build_browse_query(&DatabaseType::Milvus, "articles", "", "", 10).unwrap();
|
||||
assert!(q.contains("\"dbName\":\"default\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_weaviate() {
|
||||
let q = build_browse_query(&DatabaseType::Weaviate, "Articles", "", 5).unwrap();
|
||||
let q = build_browse_query(&DatabaseType::Weaviate, "Articles", "", "", 5).unwrap();
|
||||
assert_eq!(q, "GET /v1/objects?class=Articles&limit=5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_weaviate_encodes_query_param() {
|
||||
let q = build_browse_query(&DatabaseType::Weaviate, "A&B", "", 5).unwrap();
|
||||
let q = build_browse_query(&DatabaseType::Weaviate, "A&B", "", "", 5).unwrap();
|
||||
assert!(q.contains("class=A%26B"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_chromadb() {
|
||||
let q = build_browse_query(&DatabaseType::ChromaDb, "uuid-123", "", 15).unwrap();
|
||||
assert!(
|
||||
q.starts_with("POST /api/v2/tenants/default_tenant/databases/default_database/collections/uuid-123/get")
|
||||
);
|
||||
let q = build_browse_query(&DatabaseType::ChromaDb, "uuid-123", "cloud/db", "tenant /eu", 15).unwrap();
|
||||
assert!(q.starts_with("POST /api/v2/tenants/tenant%20%2Feu/databases/cloud%2Fdb/collections/uuid-123/get"));
|
||||
assert!(q.contains("\"limit\":15"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_chromadb_keeps_local_defaults() {
|
||||
let q = build_browse_query(&DatabaseType::ChromaDb, "uuid-123", "", "", 15).unwrap();
|
||||
assert!(
|
||||
q.starts_with("POST /api/v2/tenants/default_tenant/databases/default_database/collections/uuid-123/get")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_rejects_empty_collection() {
|
||||
let result = build_browse_query(&DatabaseType::Qdrant, " ", "", 10);
|
||||
let result = build_browse_query(&DatabaseType::Qdrant, " ", "", "", 10);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_browse_query_rejects_unsupported_type() {
|
||||
let result = build_browse_query(&DatabaseType::Postgres, "articles", "", 10);
|
||||
let result = build_browse_query(&DatabaseType::Postgres, "articles", "", "", 10);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ pub struct VectorClient {
|
||||
http: HttpClient,
|
||||
base_url: String,
|
||||
auth: Option<VectorAuth>,
|
||||
tenant: Option<String>,
|
||||
database: Option<String>,
|
||||
}
|
||||
|
||||
@@ -111,9 +112,14 @@ impl VectorClient {
|
||||
) -> Self {
|
||||
let base_url = url.trim_end_matches('/').to_string();
|
||||
let auth = vector_auth(kind, username, password);
|
||||
let tenant = if kind == VectorDbKind::ChromaDb {
|
||||
username.map(str::trim).filter(|tenant| !tenant.is_empty()).map(str::to_string)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let builder = http_client_builder(timeout).danger_accept_invalid_certs(accept_invalid_certs);
|
||||
let http = builder.build().unwrap_or_else(|_| HttpClient::new());
|
||||
Self { kind, http, base_url, auth, database: None }
|
||||
Self { kind, http, base_url, auth, tenant, database: None }
|
||||
}
|
||||
|
||||
pub fn with_database(mut self, database: Option<&str>) -> Self {
|
||||
@@ -125,6 +131,22 @@ impl VectorClient {
|
||||
self.database.as_deref().unwrap_or("default")
|
||||
}
|
||||
|
||||
fn chroma_tenant(&self) -> &str {
|
||||
self.tenant.as_deref().unwrap_or("default_tenant")
|
||||
}
|
||||
|
||||
fn chroma_database(&self) -> &str {
|
||||
self.database.as_deref().unwrap_or("default_database")
|
||||
}
|
||||
|
||||
fn chroma_collections_path(&self) -> String {
|
||||
chroma_collections_path(self.chroma_tenant(), self.chroma_database())
|
||||
}
|
||||
|
||||
fn chroma_collection_path(&self, collection: &str, operation: Option<&str>) -> String {
|
||||
chroma_collection_path(self.chroma_tenant(), self.chroma_database(), collection, operation)
|
||||
}
|
||||
|
||||
fn get(&self, path: &str) -> reqwest::RequestBuilder {
|
||||
self.with_auth(self.http.get(format!("{}{}", self.base_url, path)))
|
||||
}
|
||||
@@ -153,17 +175,13 @@ impl VectorClient {
|
||||
}
|
||||
|
||||
fn test_connection_request(client: &VectorClient) -> reqwest::RequestBuilder {
|
||||
let path = match client.kind {
|
||||
VectorDbKind::Qdrant => "/collections",
|
||||
VectorDbKind::Milvus => "/v2/vectordb/collections/list",
|
||||
VectorDbKind::Weaviate => "/v1/meta",
|
||||
VectorDbKind::ChromaDb => "/api/v2/heartbeat",
|
||||
};
|
||||
match client.kind {
|
||||
VectorDbKind::Qdrant => client.get(path),
|
||||
VectorDbKind::Milvus => client.post(path).json(&serde_json::json!({ "dbName": client.database_or_default() })),
|
||||
VectorDbKind::Weaviate => client.get(path),
|
||||
VectorDbKind::ChromaDb => client.get(path),
|
||||
VectorDbKind::Qdrant => client.get("/collections"),
|
||||
VectorDbKind::Milvus => client
|
||||
.post("/v2/vectordb/collections/list")
|
||||
.json(&serde_json::json!({ "dbName": client.database_or_default() })),
|
||||
VectorDbKind::Weaviate => client.get("/v1/meta"),
|
||||
VectorDbKind::ChromaDb => client.get(&client.chroma_collections_path()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,9 +328,7 @@ async fn list_weaviate_collections(client: &VectorClient) -> Result<Vec<Collecti
|
||||
}
|
||||
|
||||
async fn list_chroma_collections(client: &VectorClient) -> Result<Vec<CollectionInfo>, String> {
|
||||
let body =
|
||||
send_json(client.get("/api/v2/tenants/default_tenant/databases/default_database/collections"), client.kind)
|
||||
.await?;
|
||||
let body = send_json(client.get(&client.chroma_collections_path()), client.kind).await?;
|
||||
let mut infos: Vec<CollectionInfo> = body
|
||||
.as_array()
|
||||
.into_iter()
|
||||
@@ -473,14 +489,7 @@ async fn get_milvus_collection_detail(
|
||||
}
|
||||
|
||||
async fn get_chroma_collection_detail(client: &VectorClient, collection: &str) -> Result<CollectionInfo, String> {
|
||||
let body = send_json(
|
||||
client.get(&format!(
|
||||
"/api/v2/tenants/default_tenant/databases/default_database/collections/{}",
|
||||
path_segment(collection)
|
||||
)),
|
||||
client.kind,
|
||||
)
|
||||
.await?;
|
||||
let body = send_json(client.get(&client.chroma_collection_path(collection, None)), client.kind).await?;
|
||||
let name = body.get("name").and_then(Value::as_str).unwrap_or(collection);
|
||||
let id = body.get("id").and_then(Value::as_str).unwrap_or(collection);
|
||||
let dimension = body.get("dimension").and_then(|v| v.as_u64()).map(|d| d as u32);
|
||||
@@ -577,13 +586,8 @@ pub async fn find_documents(
|
||||
) -> Result<crate::db::document_result::DocumentQueryResult, String> {
|
||||
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))
|
||||
.post(&client.chroma_collection_path(collection, Some("get")))
|
||||
.json(&serde_json::json!({
|
||||
"limit": limit.max(1) as u64,
|
||||
"offset": skip,
|
||||
@@ -753,10 +757,7 @@ fn default_collection_query(client: &VectorClient, collection: &str) -> Result<r
|
||||
}))),
|
||||
VectorDbKind::Weaviate => 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)
|
||||
))
|
||||
.post(&client.chroma_collection_path(collection, Some("get")))
|
||||
.json(&serde_json::json!({"limit": 100, "include": ["documents", "metadatas"]}))),
|
||||
}
|
||||
}
|
||||
@@ -769,6 +770,30 @@ pub(crate) fn path_segment(value: &str) -> String {
|
||||
utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn chroma_collections_path(tenant: &str, database: &str) -> String {
|
||||
let tenant = tenant.trim();
|
||||
let database = database.trim();
|
||||
format!(
|
||||
"/api/v2/tenants/{}/databases/{}/collections",
|
||||
path_segment(if tenant.is_empty() { "default_tenant" } else { tenant }),
|
||||
path_segment(if database.is_empty() { "default_database" } else { database })
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn chroma_collection_path(
|
||||
tenant: &str,
|
||||
database: &str,
|
||||
collection: &str,
|
||||
operation: Option<&str>,
|
||||
) -> String {
|
||||
let mut path = format!("{}/{}", chroma_collections_path(tenant, database), path_segment(collection));
|
||||
if let Some(operation) = operation {
|
||||
path.push('/');
|
||||
path.push_str(&path_segment(operation));
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
pub(crate) fn query_value(value: &str) -> String {
|
||||
utf8_percent_encode(value, QUERY_VALUE_ENCODE_SET).to_string()
|
||||
}
|
||||
@@ -909,10 +934,10 @@ fn format_reqwest_error(err: &reqwest::Error) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
chroma_get_response_to_rows, milvus_collection_schema, milvus_database_names, rest_query_result,
|
||||
starts_with_http_method, test_connection, test_connection_request, values_to_query_result, vector_auth,
|
||||
weaviate_collection_names_from_schema, weaviate_vector_dimension_from_graphql, CollectionInfo, VectorAuth,
|
||||
VectorClient, VectorDbKind,
|
||||
chroma_get_response_to_rows, default_collection_query, milvus_collection_schema, milvus_database_names,
|
||||
rest_query_result, starts_with_http_method, test_connection, test_connection_request, values_to_query_result,
|
||||
vector_auth, weaviate_collection_names_from_schema, weaviate_vector_dimension_from_graphql, CollectionInfo,
|
||||
VectorAuth, VectorClient, VectorDbKind,
|
||||
};
|
||||
use serde_json::{json, Value};
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -939,6 +964,28 @@ mod tests {
|
||||
(format!("http://{address}"), server)
|
||||
}
|
||||
|
||||
async fn spawn_recording_json_response_server(
|
||||
body: Value,
|
||||
) -> (String, tokio::sync::oneshot::Receiver<String>, tokio::task::JoinHandle<()>) {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let address = listener.local_addr().unwrap();
|
||||
let body = body.to_string();
|
||||
let (request_tx, request_rx) = tokio::sync::oneshot::channel();
|
||||
let server = tokio::spawn(async move {
|
||||
let (mut stream, _) = listener.accept().await.unwrap();
|
||||
let mut request = [0_u8; 8192];
|
||||
let read = stream.read(&mut request).await.unwrap();
|
||||
request_tx.send(String::from_utf8_lossy(&request[..read]).into_owned()).unwrap();
|
||||
let response = format!(
|
||||
"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
|
||||
body.len(),
|
||||
body
|
||||
);
|
||||
stream.write_all(response.as_bytes()).await.unwrap();
|
||||
});
|
||||
(format!("http://{address}"), request_rx, server)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_rest_queries_case_insensitively() {
|
||||
assert!(starts_with_http_method("post /collections/foo"));
|
||||
@@ -1177,6 +1224,71 @@ mod tests {
|
||||
assert_eq!(vector_auth(VectorDbKind::ChromaDb, None, None), None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn chroma_cloud_connection_test_uses_configured_namespace_and_token() {
|
||||
let (url, request_rx, server) = spawn_recording_json_response_server(json!([])).await;
|
||||
let client = VectorClient::new(
|
||||
VectorDbKind::ChromaDb,
|
||||
&url,
|
||||
Some("tenant /eu"),
|
||||
Some("cloud-api-key"),
|
||||
false,
|
||||
Duration::from_secs(1),
|
||||
)
|
||||
.with_database(Some("support/kb"));
|
||||
|
||||
test_connection(&client, Duration::from_secs(1)).await.unwrap();
|
||||
let request = request_rx.await.unwrap();
|
||||
|
||||
assert!(
|
||||
request.starts_with("GET /api/v2/tenants/tenant%20%2Feu/databases/support%2Fkb/collections HTTP/1.1\r\n")
|
||||
);
|
||||
assert!(request.to_ascii_lowercase().contains("\r\nx-chroma-token: cloud-api-key\r\n"));
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chroma_local_connection_test_keeps_default_namespace() {
|
||||
let client = VectorClient::new(
|
||||
VectorDbKind::ChromaDb,
|
||||
"http://localhost:8000",
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
Duration::from_secs(1),
|
||||
);
|
||||
|
||||
let request = test_connection_request(&client).build().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
request.url().as_str(),
|
||||
"http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database/collections"
|
||||
);
|
||||
assert!(request.headers().get("x-chroma-token").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chroma_collection_paths_encode_every_dynamic_segment() {
|
||||
let client = VectorClient::new(
|
||||
VectorDbKind::ChromaDb,
|
||||
"https://api.trychroma.com",
|
||||
Some("tenant /eu"),
|
||||
None,
|
||||
false,
|
||||
Duration::from_secs(1),
|
||||
)
|
||||
.with_database(Some("support/kb"));
|
||||
|
||||
assert_eq!(
|
||||
client.chroma_collection_path("collection/id", Some("get/by-id")),
|
||||
"/api/v2/tenants/tenant%20%2Feu/databases/support%2Fkb/collections/collection%2Fid/get%2Fby-id"
|
||||
);
|
||||
assert_eq!(
|
||||
default_collection_query(&client, "collection/id").unwrap().build().unwrap().url().path(),
|
||||
"/api/v2/tenants/tenant%20%2Feu/databases/support%2Fkb/collections/collection%2Fid/get"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_chroma_collection_list() {
|
||||
let body = json!([
|
||||
|
||||
@@ -1040,7 +1040,8 @@ async fn test_connection_with_info_inner(
|
||||
Some(&config.password),
|
||||
config.ssl,
|
||||
connect_timeout,
|
||||
);
|
||||
)
|
||||
.with_database(config.database.as_deref());
|
||||
db::vector_driver::test_connection(&client, connect_timeout)
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string())
|
||||
@@ -1420,7 +1421,8 @@ pub async fn connect_db(
|
||||
Some(&db_config.password),
|
||||
db_config.ssl,
|
||||
connect_timeout,
|
||||
);
|
||||
)
|
||||
.with_database(db_config.database.as_deref());
|
||||
db::vector_driver::test_connection(&client, connect_timeout).await?;
|
||||
PoolKind::VectorDb(client)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user