mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-06 06:29:35 +08:00
🐛 fix(redis/kingbase): Redis数据库选择优化与金仓标识符引号修复
- Redis配置优化:移除固定数据库输入框,改为测试连接后多选数据库 - 数据库筛选:支持选择显示的Redis数据库(0-15),留空显示全部 - 类型扩展:SavedConnection新增includeRedisDatabases字段存储用户选择 - 侧边栏过滤:根据配置过滤显示的Redis数据库列表 - 金仓修复:KingBase/PostgreSQL标识符仅在必要时加双引号 - 保留字检测:新增needsQuote函数识别特殊字符和SQL保留字
This commit is contained in:
@@ -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<string[]>([]);
|
||||
const [redisDbList, setRedisDbList] = useState<number[]>([]); // 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 && (
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item name="password" label="密码 (可选)" style={{ flex: 1 }}>
|
||||
<>
|
||||
<Form.Item name="password" label="密码 (可选)">
|
||||
<Input.Password placeholder="Redis 密码(如果设置了 requirepass)" />
|
||||
</Form.Item>
|
||||
<Form.Item name="redisDB" label="数据库 (0-15)" style={{ width: 120 }}>
|
||||
<InputNumber style={{ width: '100%' }} min={0} max={15} />
|
||||
<Form.Item name="includeRedisDatabases" label="显示数据库 (留空显示全部)" help="连接测试成功后可选择">
|
||||
<Select mode="multiple" placeholder="选择显示的数据库 (0-15)" allowClear>
|
||||
{redisDbList.map(db => <Select.Option key={db} value={db}>db{db}</Select.Option>)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Non-Redis, non-SQLite: username and password */}
|
||||
|
||||
@@ -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: <DatabaseOutlined style={{ color: '#DC382D' }} />,
|
||||
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' }));
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface SavedConnection {
|
||||
name: string;
|
||||
config: ConnectionConfig;
|
||||
includeDatabases?: string[];
|
||||
includeRedisDatabases?: number[]; // Redis databases to show (0-15)
|
||||
}
|
||||
|
||||
export interface ColumnDefinition {
|
||||
|
||||
@@ -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, '""')}"`;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user