From e27d34a34ad329b5aaeb5ff52aad68446b436f9c Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 19 Jun 2026 10:07:56 +0800 Subject: [PATCH] feat(vector): add qdrant and milvus support --- apps/desktop/public/icons/database/milvus.png | Bin 0 -> 36642 bytes apps/desktop/public/icons/database/qdrant.svg | 35 ++ .../connection/ConnectionDialog.vue | 8 +- .../src/components/diff/DataCompareDialog.vue | 2 +- .../export/DatabaseExportDialog.vue | 2 +- .../src/components/icons/DatabaseIcon.vue | 2 + .../src/components/layout/AppTabBar.vue | 4 +- .../src/components/layout/ContentArea.vue | 8 + .../src/components/layout/EditorToolbar.vue | 4 +- .../src/components/sidebar/ConnectionTree.vue | 4 +- .../src/components/sidebar/TreeItem.vue | 21 +- .../sql-file/SqlFileExecutionDialog.vue | 2 +- .../src/components/vector/VectorBrowser.vue | 256 +++++++++++ .../src/composables/useNavigationTargets.ts | 6 + apps/desktop/src/i18n/locales/en.ts | 14 + apps/desktop/src/i18n/locales/zh-CN.ts | 14 + apps/desktop/src/i18n/locales/zh-TW.ts | 14 + apps/desktop/src/lib/api.ts | 1 + .../desktop/src/lib/connectionPresentation.ts | 2 + apps/desktop/src/lib/connectionUrl.ts | 4 + .../src/lib/connectionVisibleDatabases.ts | 2 +- apps/desktop/src/lib/http.ts | 4 + .../desktop/src/lib/sidebarActiveTabTarget.ts | 19 + apps/desktop/src/lib/sidebarNodeOrdering.ts | 2 +- apps/desktop/src/lib/sidebarTreeItemLayout.ts | 4 +- .../desktop/src/lib/sqlSemanticDiagnostics.ts | 2 +- apps/desktop/src/lib/tabPresentation.ts | 8 + .../src/lib/tableMetadataCapabilities.ts | 12 + apps/desktop/src/lib/tauri.ts | 4 + apps/desktop/src/lib/treeNodeClick.ts | 2 +- apps/desktop/src/stores/connectionStore.ts | 41 +- apps/desktop/src/types/database.ts | 5 +- .../assets/database-drivers.manifest.json | 58 +++ crates/dbx-core/src/connection.rs | 25 +- crates/dbx-core/src/db/mod.rs | 1 + crates/dbx-core/src/db/vector_driver.rs | 408 ++++++++++++++++++ crates/dbx-core/src/models/connection.rs | 8 +- crates/dbx-core/src/mongo_ops.rs | 16 +- crates/dbx-core/src/query.rs | 11 + crates/dbx-core/src/query_execution_sql.rs | 2 + crates/dbx-core/src/schema.rs | 4 + .../dbx-core/src/schema/providers/native.rs | 6 +- .../dbx-core/src/sql_dialect/table_select.rs | 20 + crates/dbx-core/src/sql_dialect/tests.rs | 43 ++ src-tauri/src/commands/connection.rs | 35 ++ 45 files changed, 1116 insertions(+), 29 deletions(-) create mode 100644 apps/desktop/public/icons/database/milvus.png create mode 100644 apps/desktop/public/icons/database/qdrant.svg create mode 100644 apps/desktop/src/components/vector/VectorBrowser.vue create mode 100644 crates/dbx-core/src/db/vector_driver.rs diff --git a/apps/desktop/public/icons/database/milvus.png b/apps/desktop/public/icons/database/milvus.png new file mode 100644 index 0000000000000000000000000000000000000000..4d3214c7df716b73e99c1b5bc18ce41c8b03c186 GIT binary patch literal 36642 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 Gh5sKV3NkVP literal 0 HcmV?d00001 diff --git a/apps/desktop/public/icons/database/qdrant.svg b/apps/desktop/public/icons/database/qdrant.svg new file mode 100644 index 0000000000..c10ad96f11 --- /dev/null +++ b/apps/desktop/public/icons/database/qdrant.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index b877e89b2c..fe5615f4c0 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -407,6 +407,8 @@ const driverProfiles: Record< label: "Elasticsearch", icon: "elasticsearch", }, + qdrant: { type: "qdrant", port: 6333, user: "", label: "Qdrant", icon: "qdrant" }, + milvus: { type: "milvus", port: 19530, user: "root", label: "Milvus", icon: "milvus" }, mariadb: { type: "mysql", port: 3306, user: "root", label: "MariaDB", icon: "mariadb" }, tidb: { type: "mysql", port: 4000, user: "root", label: "TiDB", icon: "tidb" }, oceanbase: { type: "mysql", port: 2881, user: "root", label: "OceanBase", icon: "oceanbase" }, @@ -870,6 +872,8 @@ const iconTypeMap: Record = { sqlserver: "sqlserver", oracle: "oracle", elasticsearch: "elasticsearch", + qdrant: "qdrant", + milvus: "milvus", mariadb: "mariadb", tidb: "tidb", oceanbase: "oceanbase", @@ -933,6 +937,8 @@ const dbOptions: DbOption[] = [ { value: "sqlite", label: "SQLite" }, { value: "sqlserver", label: "SQL Server" }, { value: "elasticsearch", label: "Elasticsearch" }, + { value: "qdrant", label: "Qdrant" }, + { value: "milvus", label: "Milvus" }, { value: "dm", label: "DM (Dameng)" }, { value: "opengauss", label: "openGauss" }, { value: "turso", label: "Turso" }, @@ -1034,7 +1040,7 @@ const sqliteExtensionPaths = computed({ form.value.url_params = setSqliteExtensionPaths(form.value.url_params, value); }, }); -const tlsCapableDatabaseTypes = new Set(["mysql", "postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb", "redis", "etcd", "clickhouse", "elasticsearch", "influxdb"]); +const tlsCapableDatabaseTypes = new Set(["mysql", "postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb", "redis", "etcd", "clickhouse", "elasticsearch", "qdrant", "milvus", "influxdb"]); const supportsTlsToggle = computed(() => tlsCapableDatabaseTypes.has(form.value.db_type)); const supportsCaCertificatePath = computed(() => form.value.db_type === "clickhouse"); const supportsGenericUrlParams = computed(() => form.value.db_type !== "manticoresearch"); diff --git a/apps/desktop/src/components/diff/DataCompareDialog.vue b/apps/desktop/src/components/diff/DataCompareDialog.vue index 7ad992c20b..9117cb0cd2 100644 --- a/apps/desktop/src/components/diff/DataCompareDialog.vue +++ b/apps/desktop/src/components/diff/DataCompareDialog.vue @@ -114,7 +114,7 @@ const showModified = ref(true); let syncPlanRequestId = 0; -const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "etcd"].includes(connection.db_type))); +const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd"].includes(connection.db_type))); const selectedSourceTableNames = computed(() => sourceTables.value.filter((table) => selectedSourceTables.value.has(table))); const isBatchCompare = computed(() => selectedSourceTableNames.value.length > 1); const filteredSourceTables = computed(() => { diff --git a/apps/desktop/src/components/export/DatabaseExportDialog.vue b/apps/desktop/src/components/export/DatabaseExportDialog.vue index acc59c5706..fc7f73fb91 100644 --- a/apps/desktop/src/components/export/DatabaseExportDialog.vue +++ b/apps/desktop/src/components/export/DatabaseExportDialog.vue @@ -65,7 +65,7 @@ const exportCancelled = ref(false); const pendingPrefillTable = ref(""); const pendingPrefillTables = ref([]); -const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "etcd"].includes(c.db_type))); +const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd"].includes(c.db_type))); const canExport = computed(() => connectionId.value && database.value && schema.value && !loadingTables.value && !tableError.value && (tables.value.length === 0 || selectedTables.value.length > 0) && (includeStructure.value || includeData.value || includeObjects.value) && !isExporting.value); diff --git a/apps/desktop/src/components/icons/DatabaseIcon.vue b/apps/desktop/src/components/icons/DatabaseIcon.vue index ec1bb00b04..82565275d9 100644 --- a/apps/desktop/src/components/icons/DatabaseIcon.vue +++ b/apps/desktop/src/components/icons/DatabaseIcon.vue @@ -76,6 +76,8 @@ const assetIcons: Record = { xugu: "xugu.png", iotdb: "iotdb", etcd: "etcd", + qdrant: "qdrant", + milvus: "milvus.png", mq: "pulsar", pulsar: "pulsar", iris: "iris.png", diff --git a/apps/desktop/src/components/layout/AppTabBar.vue b/apps/desktop/src/components/layout/AppTabBar.vue index 21837b5f97..b27605289f 100644 --- a/apps/desktop/src/components/layout/AppTabBar.vue +++ b/apps/desktop/src/components/layout/AppTabBar.vue @@ -216,7 +216,7 @@ function tabColorStyle(tab: QueryTab) { } function tabIconClass(tab: QueryTab) { - if (tab.mode === "data" || tab.mode === "mongo" || tab.mode === "redis" || tab.mode === "objects" || tab.mode === "structure") return "text-emerald-600 dark:text-emerald-400"; + if (tab.mode === "data" || tab.mode === "mongo" || tab.mode === "vector" || tab.mode === "redis" || tab.mode === "objects" || tab.mode === "structure") return "text-emerald-600 dark:text-emerald-400"; return "text-blue-600 dark:text-blue-400"; } @@ -234,6 +234,7 @@ const openTabMenuItems = computed(() => function tabMenuIcon(tab: QueryTab) { if (tab.mode === "data" || tab.mode === "mongo" || tab.mode === "redis") return Table2; + if (tab.mode === "vector") return TableProperties; if (tab.mode === "etcd") return KeyRound; if (tab.mode === "objects") return TableProperties; if (tab.mode === "structure") return PencilRuler; @@ -326,6 +327,7 @@ function activateTab(tabId: string) { > + diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index a6981f161d..0e6474e97d 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -36,6 +36,7 @@ const DataGrid = defineAsyncComponent(loadDataGridComponent); const RedisKeyBrowser = defineAsyncComponent(() => import("@/components/redis/RedisKeyBrowser.vue")); const EtcdKeyBrowser = defineAsyncComponent(() => import("@/components/etcd/EtcdKeyBrowser.vue")); const DocumentBrowser = defineAsyncComponent(() => import("@/components/document/DocumentBrowser.vue")); +const VectorBrowser = defineAsyncComponent(() => import("@/components/vector/VectorBrowser.vue")); const MqAdminConsole = defineAsyncComponent(() => import("@/components/mq/MqAdminConsole.vue")); const ObjectBrowser = defineAsyncComponent(() => import("@/components/objects/ObjectBrowser.vue")); const TableStructureEditor = defineAsyncComponent(() => import("@/components/structure/TableStructureEditor.vue")); @@ -993,6 +994,13 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); + + + -
+
= { connection: ["connection"], database: ["database", "redis-db", "mq-tenant", "mongo-db"], schema: ["schema"], - table: ["table", "mongo-collection", "elasticsearch-index"], + table: ["table", "mongo-collection", "vector-collection", "elasticsearch-index"], view: ["view"], }; @@ -336,6 +336,8 @@ async function ensureTreeLoadedForTarget(target: ActiveTabSidebarTarget, opts?: await store.loadMongoDatabases(connId); } else if (config.db_type === "elasticsearch") { await store.loadElasticsearchIndices(connId); + } else if (config.db_type === "qdrant" || config.db_type === "milvus") { + await store.loadVectorCollections(connId); } else if (config.db_type === "mq") { await store.loadMqTenants(connId, loadOptions); } else { diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index 45964cc876..dc7433e8a7 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -247,6 +247,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null { return { icon: Database, colorClass: "text-yellow-500" }; case "mongo-collection": return { icon: Table, colorClass: "text-green-400" }; + case "vector-collection": + return { icon: TableProperties, colorClass: "text-cyan-400" }; case "elasticsearch-index": return { icon: Table, colorClass: "text-emerald-400" }; case "procedure": @@ -283,7 +285,7 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null { } const groupTypes: Set = new Set(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages", "group-partitions"]); -const pinnableTypes: Set = new Set(["connection-group", "database", "linked-server", "linked-server-catalog", "linked-server-schema", "schema", "table", "view", "materialized_view", "redis-db", "mongo-db", "mongo-collection", "elasticsearch-index"]); +const pinnableTypes: Set = new Set(["connection-group", "database", "linked-server", "linked-server-catalog", "linked-server-schema", "schema", "table", "view", "materialized_view", "redis-db", "mongo-db", "mongo-collection", "vector-collection", "elasticsearch-index"]); function isGroupLabel(node: TreeNode): boolean { return groupTypes.has(node.type); @@ -299,7 +301,7 @@ function displayLabel(node: TreeNode): string { } function visibleLabel(node: TreeNode): string { - if (node.type === "table" || node.type === "view" || node.type === "materialized_view" || node.type === "mongo-collection" || node.type === "elasticsearch-index") { + if (node.type === "table" || node.type === "view" || node.type === "materialized_view" || node.type === "mongo-collection" || node.type === "vector-collection" || node.type === "elasticsearch-index") { return sidebarDisplayTableName(node.label, settingsStore.editorSettings.sidebarHiddenTablePrefixes); } return displayLabel(node); @@ -335,6 +337,8 @@ function connectionTooltipScheme(config: Pick { return t === "connection" || t === "database" || t === "schema" || t === "table" || t === "view" || t === "column" || t === "procedure" || t === "function" || t === "package" || t === "package-body" || isGroupLabel(props.node); }); const columnComment = computed(() => (!settingsStore.editorSettings.sidebarHideTableComments && props.node.type === "column" && props.node.meta && "comment" in props.node.meta ? (props.node.meta as any).comment : null)); -const tableComment = computed(() => (!settingsStore.editorSettings.sidebarHideTableComments && (props.node.type === "table" || props.node.type === "view" || props.node.type === "mongo-collection" || props.node.type === "elasticsearch-index") && props.node.comment ? props.node.comment : null)); +const tableComment = computed(() => + !settingsStore.editorSettings.sidebarHideTableComments && (props.node.type === "table" || props.node.type === "view" || props.node.type === "mongo-collection" || props.node.type === "vector-collection" || props.node.type === "elasticsearch-index") && props.node.comment ? props.node.comment : null, +); const paddingLeft = computed(() => treeItemPaddingLeft(props.depth)); const isConnected = computed(() => props.node.type === "connection" && !!props.node.connectionId && connectionStore.connectedIds.has(props.node.connectionId)); const isConnectionReadonly = computed(() => props.node.type === "connection" && !!props.node.connectionId && (connectionStore.getConfig(props.node.connectionId)?.read_only ?? false)); @@ -2735,7 +2746,7 @@ const nodeIconClass = computed(() => { const canConfigureVisibleDatabases = computed(() => { if (props.node.type !== "connection" || !props.node.connectionId) return false; const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type; - return dbType !== "elasticsearch" && dbType !== "etcd" && dbType !== "mq"; + return dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "etcd" && dbType !== "mq"; }); const canCopyFinalProxyPort = computed(() => { if (props.node.type !== "connection" || !props.node.connectionId) return false; @@ -3325,7 +3336,7 @@ function treeItemMenuItems(): ContextMenuItem[] { return items; } - if (node.type === "elasticsearch-index") { + if (node.type === "elasticsearch-index" || node.type === "vector-collection") { items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value }); items.push({ label: "", separator: true }); items.push({ label: t("contextMenu.viewData"), action: toggle, icon: TableProperties }); diff --git a/apps/desktop/src/components/sql-file/SqlFileExecutionDialog.vue b/apps/desktop/src/components/sql-file/SqlFileExecutionDialog.vue index f60e1d0181..a515027d50 100644 --- a/apps/desktop/src/components/sql-file/SqlFileExecutionDialog.vue +++ b/apps/desktop/src/components/sql-file/SqlFileExecutionDialog.vue @@ -49,7 +49,7 @@ const terminalStatus = ref("idle"); const terminalError = ref(""); const refreshedTarget = ref(false); -const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "etcd"].includes(c.db_type))); +const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd"].includes(c.db_type))); const selectedConnection = computed(() => sqlConnections.value.find((c) => c.id === connectionId.value)); diff --git a/apps/desktop/src/components/vector/VectorBrowser.vue b/apps/desktop/src/components/vector/VectorBrowser.vue new file mode 100644 index 0000000000..8a8a6daa81 --- /dev/null +++ b/apps/desktop/src/components/vector/VectorBrowser.vue @@ -0,0 +1,256 @@ + + +