From 49c7620bdd2f2ea2eb707b4a3330c89f607641f6 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 4 Feb 2026 17:00:51 +0800 Subject: [PATCH] =?UTF-8?q?=20=F0=9F=90=9B=20fix(redis/kingbase):=20Redis?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E9=80=89=E6=8B=A9=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=B8=8E=E9=87=91=E4=BB=93=E6=A0=87=E8=AF=86=E7=AC=A6=E5=BC=95?= =?UTF-8?q?=E5=8F=B7=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Redis配置优化:移除固定数据库输入框,改为测试连接后多选数据库 - 数据库筛选:支持选择显示的Redis数据库(0-15),留空显示全部 - 类型扩展:SavedConnection新增includeRedisDatabases字段存储用户选择 - 侧边栏过滤:根据配置过滤显示的Redis数据库列表 - 金仓修复:KingBase/PostgreSQL标识符仅在必要时加双引号 - 保留字检测:新增needsQuote函数识别特殊字符和SQL保留字 --- frontend/src/components/ConnectionModal.tsx | 39 ++++++++++++--------- frontend/src/components/Sidebar.tsx | 7 +++- frontend/src/types.ts | 1 + frontend/src/utils/sql.ts | 27 +++++++++++++- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index 21c97bd..f986b31 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -16,6 +16,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal const [step, setStep] = useState(1); // 1: Select Type, 2: Configure const [testResult, setTestResult] = useState<{ type: 'success' | 'error', message: string } | null>(null); const [dbList, setDbList] = useState([]); + const [redisDbList, setRedisDbList] = useState([]); // Redis databases 0-15 const addConnection = useStore((state) => state.addConnection); const updateConnection = useStore((state) => state.updateConnection); @@ -23,6 +24,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal if (open) { setTestResult(null); // Reset test result setDbList([]); + setRedisDbList([]); if (initialValues) { // Edit mode: Go directly to step 2 setStep(2); @@ -35,6 +37,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal password: initialValues.config.password, database: initialValues.config.database, includeDatabases: initialValues.includeDatabases, + includeRedisDatabases: initialValues.includeRedisDatabases, useSSH: initialValues.config.useSSH, sshHost: initialValues.config.ssh?.host, sshPort: initialValues.config.ssh?.port, @@ -43,11 +46,14 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal sshKeyPath: initialValues.config.ssh?.keyPath, driver: (initialValues.config as any).driver, dsn: (initialValues.config as any).dsn, - timeout: (initialValues.config as any).timeout || 30, - redisDB: (initialValues.config as any).redisDB || 0 + timeout: (initialValues.config as any).timeout || 30 }); setUseSSH(initialValues.config.useSSH || false); setDbType(initialValues.config.type); + // 如果是 Redis 编辑模式,设置已保存的 Redis 数据库列表 + if (initialValues.config.type === 'redis') { + setRedisDbList(Array.from({ length: 16 }, (_, i) => i)); + } } else { // Create mode: Start at step 1 setStep(1); @@ -78,7 +84,8 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal id: initialValues ? initialValues.id : Date.now().toString(), name: values.name || (values.type === 'sqlite' ? 'SQLite DB' : (values.type === 'redis' ? `Redis ${values.host}` : values.host)), config: config, - includeDatabases: values.includeDatabases + includeDatabases: values.includeDatabases, + includeRedisDatabases: isRedisType ? values.includeRedisDatabases : undefined }; if (initialValues) { @@ -118,8 +125,11 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal setLoading(false); if (res.success) { setTestResult({ type: 'success', message: res.message }); - // Only fetch database list for non-Redis connections - if (!isRedisType) { + if (isRedisType) { + // Redis: generate database list 0-15 + setRedisDbList(Array.from({ length: 16 }, (_, i) => i)); + } else { + // Other databases: fetch database list const dbRes = await DBGetDatabases(config as any); if (dbRes.success) { const dbs = (dbRes.data as any[]).map((row: any) => row.Database || row.database); @@ -154,8 +164,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal ssh: sshConfig, driver: values.driver, dsn: values.dsn, - timeout: Number(values.timeout || 30), - redisDB: Number(values.redisDB || 0) + timeout: Number(values.timeout || 30) }; }; @@ -177,10 +186,6 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal if (type !== 'sqlite' && type !== 'custom') { form.setFieldsValue({ port: defaultPort }); } - // Set default redisDB for Redis - if (type === 'redis') { - form.setFieldsValue({ redisDB: 0 }); - } setStep(2); }; @@ -260,14 +265,16 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal {/* Redis specific: password only, no username */} {isRedis && ( -
- + <> + - - + + -
+ )} {/* Non-Redis, non-SQLite: username and password */} diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 637d942..e9e39af 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -136,14 +136,19 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> const res = await (window as any).go.app.App.RedisGetDatabases(config); if (res.success) { setConnectionStates(prev => ({ ...prev, [conn.id]: 'success' })); - const dbs = (res.data as any[]).map((db: any) => ({ + let dbs = (res.data as any[]).map((db: any) => ({ title: `db${db.index}${db.keys > 0 ? ` (${db.keys})` : ''}`, key: `${conn.id}-db${db.index}`, icon: , type: 'redis-db' as const, dataRef: { ...conn, redisDB: db.index }, isLeaf: true, + dbIndex: db.index, })); + // Filter Redis databases if configured + if (conn.includeRedisDatabases && conn.includeRedisDatabases.length > 0) { + dbs = dbs.filter(db => conn.includeRedisDatabases!.includes(db.dbIndex)); + } setTreeData(origin => updateTreeData(origin, node.key, dbs)); } else { setConnectionStates(prev => ({ ...prev, [conn.id]: 'error' })); diff --git a/frontend/src/types.ts b/frontend/src/types.ts index be972cd..a4a8b66 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -23,6 +23,7 @@ export interface SavedConnection { name: string; config: ConnectionConfig; includeDatabases?: string[]; + includeRedisDatabases?: number[]; // Redis databases to show (0-15) } export interface ColumnDefinition { diff --git a/frontend/src/utils/sql.ts b/frontend/src/utils/sql.ts index fc458dd..6a20d57 100644 --- a/frontend/src/utils/sql.ts +++ b/frontend/src/utils/sql.ts @@ -18,10 +18,35 @@ const normalizeIdentPart = (ident: string) => { return raw; }; +// 检查标识符是否需要引号(包含特殊字符或是保留字) +const needsQuote = (ident: string): boolean => { + if (!ident) return false; + // 如果包含特殊字符(非字母、数字、下划线)则需要引号 + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(ident)) return true; + // 常见 SQL 保留字列表(简化版) + const reserved = ['select', 'from', 'where', 'table', 'index', 'user', 'order', 'group', 'by', 'limit', 'offset', 'and', 'or', 'not', 'null', 'true', 'false', 'key', 'primary', 'foreign', 'references', 'default', 'constraint', 'create', 'drop', 'alter', 'insert', 'update', 'delete', 'set', 'values', 'into', 'join', 'left', 'right', 'inner', 'outer', 'on', 'as', 'is', 'in', 'like', 'between', 'case', 'when', 'then', 'else', 'end', 'having', 'distinct', 'all', 'any', 'exists', 'union', 'except', 'intersect']; + return reserved.includes(ident.toLowerCase()); +}; + export const quoteIdentPart = (dbType: string, ident: string) => { const raw = normalizeIdentPart(ident); if (!raw) return raw; - if ((dbType || '').toLowerCase() === 'mysql') return `\`${raw.replace(/`/g, '``')}\``; + const dbTypeLower = (dbType || '').toLowerCase(); + + if (dbTypeLower === 'mysql') { + return `\`${raw.replace(/`/g, '``')}\``; + } + + // 对于 KingBase/PostgreSQL,只在必要时加引号 + if (dbTypeLower === 'kingbase' || dbTypeLower === 'postgres') { + if (needsQuote(raw)) { + return `"${raw.replace(/"/g, '""')}"`; + } + // 不加引号,保持原样(数据库会自动转小写处理) + return raw; + } + + // 其他数据库默认加双引号 return `"${raw.replace(/"/g, '""')}"`; };