From e6fe6eb0265bc8533cfb74dc05933a320351a56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E9=94=8B?= Date: Tue, 10 Feb 2026 20:12:25 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(sphinx):=20=E6=96=B0=E5=A2=9ES?= =?UTF-8?q?phinx=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B9=B6=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E8=83=BD=E5=8A=9B=E5=85=BC=E5=AE=B9=E9=93=BE?= =?UTF-8?q?=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 SphinxDB 驱动注册并复用 MySQL 协议连接 - 前端新增 sphinx 连接类型与默认端口 9306 - 函数/视图/触发器改为多语句回退查询与版本探测提示 - 后端对不支持能力返回稳定降级结果 --- frontend/src/components/ConnectionModal.tsx | 30 +- frontend/src/components/DefinitionViewer.tsx | 163 ++++++-- frontend/src/components/QueryEditor.tsx | 2 +- frontend/src/components/Sidebar.tsx | 371 +++++++++++++------ frontend/src/components/TableDesigner.tsx | 2 +- frontend/src/components/TriggerViewer.tsx | 130 ++++++- frontend/src/utils/sql.ts | 2 +- internal/app/db_context.go | 2 +- internal/app/methods_db.go | 22 +- internal/app/methods_file.go | 2 +- internal/db/database.go | 2 + internal/db/sphinx_impl.go | 103 +++++ internal/sync/sql_helpers.go | 4 +- 13 files changed, 641 insertions(+), 194 deletions(-) create mode 100644 internal/db/sphinx_impl.go diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index ff0fac7..5e97986 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -11,6 +11,7 @@ const { Text } = Typography; const getDefaultPortByType = (type: string) => { switch (type) { case 'mysql': return 3306; + case 'sphinx': return 9306; case 'postgres': return 5432; case 'redis': return 6379; case 'tdengine': return 6041; @@ -187,17 +188,18 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal return null; } - if (type === 'mysql' || type === 'mariadb') { + if (type === 'mysql' || type === 'mariadb' || type === 'sphinx') { + const mysqlDefaultPort = getDefaultPortByType(type); const parsed = parseMultiHostUri(trimmedUri, 'mysql'); if (!parsed) { return null; } - const hostList = normalizeAddressList(parsed.hosts, 3306); - const primary = parseHostPort(hostList[0] || 'localhost:3306', 3306); + const hostList = normalizeAddressList(parsed.hosts, mysqlDefaultPort); + const primary = parseHostPort(hostList[0] || `localhost:${mysqlDefaultPort}`, mysqlDefaultPort); const timeoutValue = Number(parsed.params.get('timeout')); return { host: primary?.host || 'localhost', - port: primary?.port || 3306, + port: primary?.port || mysqlDefaultPort, user: parsed.username, password: parsed.password, database: parsed.database || '', @@ -259,8 +261,9 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal }); const getUriPlaceholder = () => { - if (dbType === 'mysql' || dbType === 'mariadb') { - return 'mysql://user:pass@127.0.0.1:3306,127.0.0.2:3306/db_name?topology=replica'; + if (dbType === 'mysql' || dbType === 'mariadb' || dbType === 'sphinx') { + const defaultPort = getDefaultPortByType(dbType); + return `mysql://user:pass@127.0.0.1:${defaultPort},127.0.0.2:${defaultPort}/db_name?topology=replica`; } if (dbType === 'mongodb') { return 'mongodb+srv://user:pass@cluster0.example.com/db_name?authSource=admin&authMechanism=SCRAM-SHA-256'; @@ -281,12 +284,12 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal ? `${encodeURIComponent(user)}${password ? `:${encodeURIComponent(password)}` : ''}@` : ''; - if (type === 'mysql' || type === 'mariadb') { - const primary = toAddress(host, port, 3306); + if (type === 'mysql' || type === 'mariadb' || type === 'sphinx') { + const primary = toAddress(host, port, defaultPort); const replicas = values.mysqlTopology === 'replica' - ? normalizeAddressList(values.mysqlReplicaHosts, 3306) + ? normalizeAddressList(values.mysqlReplicaHosts, defaultPort) : []; - const hosts = normalizeAddressList([primary, ...replicas], 3306); + const hosts = normalizeAddressList([primary, ...replicas], defaultPort); const params = new URLSearchParams(); if (hosts.length > 1 || values.mysqlTopology === 'replica') { params.set('topology', 'replica'); @@ -410,7 +413,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal ); const primaryHost = primaryAddress?.host || String(config.host || 'localhost'); const primaryPort = primaryAddress?.port || Number(config.port || defaultPort); - const mysqlReplicaHosts = (configType === 'mysql' || configType === 'mariadb') ? normalizedHosts.slice(1) : []; + const mysqlReplicaHosts = (configType === 'mysql' || configType === 'mariadb' || configType === 'sphinx') ? normalizedHosts.slice(1) : []; const mongoHosts = configType === 'mongodb' ? normalizedHosts.slice(1) : []; const mysqlIsReplica = String(config.topology || '').toLowerCase() === 'replica' || mysqlReplicaHosts.length > 0; const mongoIsReplica = String(config.topology || '').toLowerCase() === 'replica' || mongoHosts.length > 0 || !!config.replicaSet; @@ -638,7 +641,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal ? mergedValues.savePassword !== false : true; - if (type === 'mysql' || type === 'mariadb') { + if (type === 'mysql' || type === 'mariadb' || type === 'sphinx') { const replicas = mergedValues.mysqlTopology === 'replica' ? normalizeAddressList(mergedValues.mysqlReplicaHosts, defaultPort) : []; @@ -753,6 +756,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal { label: '关系型数据库', items: [ { key: 'mysql', name: 'MySQL', icon: }, { key: 'mariadb', name: 'MariaDB', icon: }, + { key: 'sphinx', name: 'Sphinx', icon: }, { key: 'postgres', name: 'PostgreSQL', icon: }, { key: 'sqlserver', name: 'SQL Server', icon: }, { key: 'sqlite', name: 'SQLite', icon: }, @@ -920,7 +924,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal )} - {(dbType === 'mysql' || dbType === 'mariadb') && ( + {(dbType === 'mysql' || dbType === 'mariadb' || dbType === 'sphinx') && ( <>