feat(vector): add qdrant and milvus support

This commit is contained in:
t8y2 committed 2026-06-19 10:07:56 +08:00
1 parent fffdcb9db1
commit e27d34a34a
45 files changed
+1116 -29

No files matched your search

+1
View File
@@ -338,6 +338,7 @@ export const mongoDeleteDocuments = forward("mongoDeleteDocuments");
// Elasticsearch
export const elasticsearchListIndices = forward("elasticsearchListIndices");
export const vectorListCollections = forward("vectorListCollections");
// History
export const saveHistory = forward("saveHistory");
@@ -127,6 +127,8 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string {
return "oracle://user:password@host:port/service_name";
case "elasticsearch":
case "qdrant":
case "milvus":
return "http://user:password@host:port";
case "dameng":
+4
View File
@@ -39,6 +39,8 @@ const SCHEME_PROFILES: Record<string, ConnectionProfile> = {
mssql: { type: "sqlserver", profile: "sqlserver", label: "SQL Server", defaultPort: 1433 },
oracle: { type: "oracle", profile: "oracle", label: "Oracle", defaultPort: 1521 },
elasticsearch: { type: "elasticsearch", profile: "elasticsearch", label: "Elasticsearch", defaultPort: 9200 },
qdrant: { type: "qdrant", profile: "qdrant", label: "Qdrant", defaultPort: 6333 },
milvus: { type: "milvus", profile: "milvus", label: "Milvus", defaultPort: 19530 },
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 },
@@ -59,6 +61,8 @@ const SCHEME_PROFILES: Record<string, ConnectionProfile> = {
const HTTP_SELECTED_PROFILES: Record<string, ConnectionProfile> = {
clickhouse: SCHEME_PROFILES.clickhouse,
elasticsearch: SCHEME_PROFILES.elasticsearch,
qdrant: SCHEME_PROFILES.qdrant,
milvus: SCHEME_PROFILES.milvus,
};
function decodeUrlPart(value: string): string {
@@ -3,7 +3,7 @@ import { filterDatabaseNamesForConnection, normalizeVisibleDatabaseSelection } f
const DRAFT_VISIBLE_DATABASES_PREFIX = "__visible_draft_";
const UNSUPPORTED_VISIBLE_DATABASE_TYPES = new Set<DatabaseType>(["elasticsearch", "etcd"]);
const UNSUPPORTED_VISIBLE_DATABASE_TYPES = new Set<DatabaseType>(["elasticsearch", "qdrant", "milvus", "etcd"]);
type VisibleDatabaseConnectionFields = Pick<
ConnectionConfig,
+4
View File
@@ -1460,6 +1460,10 @@ export async function elasticsearchListIndices(connectionId: string): Promise<st
return mongoListCollections(connectionId, "default");
}
export async function vectorListCollections(connectionId: string): Promise<string[]> {
return mongoListCollections(connectionId, "default");
}
export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string, executionId?: string): Promise<MongoDocumentResult> {
return post("/api/mongo/find-documents", { connectionId, database, collection, skip, limit, filter, sort, executionId });
}
@@ -15,6 +15,11 @@ export type ActiveTabSidebarTarget =
database: string;
collectionName: string;
}
| {
type: "vector-collection";
connectionId: string;
collectionName: string;
}
| {
type: "etcd-root";
connectionId: string;
@@ -61,6 +66,16 @@ export function activeTabSidebarTarget(tab: QueryTab | undefined | null): Active
};
}
if (tab.mode === "vector") {
const collectionName = tab.sql || tab.title;
if (!collectionName) return null;
return {
type: "vector-collection",
connectionId: tab.connectionId,
collectionName,
};
}
if (tab.mode === "etcd") {
return { type: "etcd-root", connectionId: tab.connectionId };
}
@@ -99,6 +114,10 @@ export function matchesTarget(node: TreeNode, target: ActiveTabSidebarTarget): b
return node.type === "mongo-collection" && node.connectionId === target.connectionId && node.database === target.database && node.label === target.collectionName;
}
if (target.type === "vector-collection") {
return node.type === "vector-collection" && node.connectionId === target.connectionId && node.label === target.collectionName;
}
if (target.type === "query-context") {
if (target.schema) {
return node.type === "schema" && node.connectionId === target.connectionId && node.database === target.database && node.label === target.schema;
+1 -1
View File
@@ -30,7 +30,7 @@ export function sortSidebarTreeChildrenForParent(parent: Pick<TreeNode, "type">,
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") {
if (databaseType === "mongodb" || databaseType === "elasticsearch" || databaseType === "qdrant" || databaseType === "milvus") {
return withConnectionUtilityOrder(sortByLabel(regularChildren));
}
@@ -1,8 +1,8 @@
import type { TreeNodeType } from "@/types/database";
const leafTypes: Set<TreeNodeType> = new Set(["column", "index", "fkey", "trigger", "procedure", "function", "package", "package-body", "object-browser", "redis-db", "mq-tenant", "mongo-collection", "elasticsearch-index", "user-admin", "saved-sql-file", "load-more"]);
const leafTypes: Set<TreeNodeType> = new Set(["column", "index", "fkey", "trigger", "procedure", "function", "package", "package-body", "object-browser", "redis-db", "mq-tenant", "mongo-collection", "vector-collection", "elasticsearch-index", "user-admin", "saved-sql-file", "load-more"]);
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "materialized_view", "mongo-collection", "elasticsearch-index"]);
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "materialized_view", "mongo-collection", "vector-collection", "elasticsearch-index"]);
const emptyContainerTypes: Set<TreeNodeType> = new Set(["saved-sql-root", "saved-sql-folder"]);
@@ -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 === "redis") return false;
if (options.databaseType === "mongodb" || options.databaseType === "elasticsearch" || options.databaseType === "qdrant" || options.databaseType === "milvus" || options.databaseType === "redis") return false;
const context = getSqlCompletionContext(sql, cursor);
if (context.suggestTables || context.exclusiveTableSuggestions || context.exclusiveColumnSuggestions) return false;
if (context.qualifier) return false;
+8
View File
@@ -70,6 +70,10 @@ export function tabDisplayTitle(tab: QueryTab, t: Translate): string {
if (compact) return tab.sql;
return `${tab.sql}@${database}`;
}
if (tab.mode === "vector" && tab.sql) {
if (compact) return tab.sql;
return `${tab.sql}@${database}`;
}
if (tab.mode === "redis") {
if (compact) return connectionDisplayName(tab.connectionId);
return `${connectionDisplayName(tab.connectionId)}@${database}`;
@@ -106,6 +110,9 @@ export function tabTooltipLines(tab: QueryTab, t: Translate): { label: string; v
if (tab.mode === "mongo" && tab.sql) {
lines.push({ label: t("tabs.tooltipCollection"), value: tab.sql });
}
if (tab.mode === "vector" && tab.sql) {
lines.push({ label: t("tabs.tooltipCollection"), value: tab.sql });
}
if (tab.mode === "objects" && tab.objectBrowser?.schema) {
lines.push({ label: t("tabs.tooltipSchema"), value: tab.objectBrowser.schema });
}
@@ -171,6 +178,7 @@ export function tabModeLabel(tab: QueryTab, t: Translate): string {
if (tab.mode === "data") return t("tabs.table");
if (tab.mode === "query") return t("tabs.sql");
if (tab.mode === "mongo") return t("tabs.mongo");
if (tab.mode === "vector") return t("tabs.vector");
if (tab.mode === "redis") return t("tabs.redis");
if (tab.mode === "etcd") return t("tabs.etcd");
if (tab.mode === "objects") return t("tabs.objects");
@@ -31,6 +31,18 @@ const capabilityByType: Partial<Record<DatabaseType, Partial<TableMetadataCapabi
triggers: false,
ddl: false,
},
qdrant: {
indexes: false,
foreignKeys: false,
triggers: false,
ddl: false,
},
milvus: {
indexes: false,
foreignKeys: false,
triggers: false,
ddl: false,
},
influxdb: {
indexes: false,
foreignKeys: false,
+4
View File
@@ -1300,6 +1300,10 @@ export async function elasticsearchListIndices(connectionId: string): Promise<st
return mongoListCollections(connectionId, "default");
}
export async function vectorListCollections(connectionId: string): Promise<string[]> {
return mongoListCollections(connectionId, "default");
}
export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string, executionId?: string): Promise<MongoDocumentResult> {
return invoke("mongo_find_documents", { connectionId, database, collection, skip, limit, filter, sort, executionId });
}
+1 -1
View File
@@ -7,7 +7,7 @@ export type SidebarSelectionCopyAction = "copy-name" | "none";
export type SidebarActivation = "single" | "double";
const dataNodeTypes = new Set<TreeNodeType>(["table", "view", "materialized_view"]);
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mq-tenant", "mongo-collection", "elasticsearch-index", "user-admin"]);
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mq-tenant", "mongo-collection", "vector-collection", "elasticsearch-index", "user-admin"]);
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
const sourceNodeTypes = new Set<TreeNodeType>(["materialized_view", "procedure", "function", "sequence", "package", "package-body"]);
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);