mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-05 18:11:32 +08:00
🐛 fix(driver): 修复驱动代理校验与 DuckDB 表预览超时
- 校验可选 driver-agent revision,避免重装后复用旧代理 - DuckDB 表预览默认不再追加兜底 ORDER BY - 优化 DuckDB 超时中断提示并补充回归测试
This commit is contained in:
@@ -176,6 +176,46 @@ describe('DataViewer safe editing locator', () => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
it('does not add fallback ORDER BY for DuckDB table preview when a primary key is available', async () => {
|
||||
storeState.connections[0].config.type = 'duckdb';
|
||||
storeState.connections[0].config.database = 'main';
|
||||
backendApp.DBGetColumns.mockResolvedValue({
|
||||
success: true,
|
||||
data: [{ name: 'ID', key: 'PRI' }, { name: 'NAME', key: '' }],
|
||||
});
|
||||
|
||||
const renderer = await renderAndReload(createTab({ id: 'tab-duckdb-order', dbName: 'main', tableName: 'events', title: 'events' }));
|
||||
|
||||
const tableQueries = backendApp.DBQuery.mock.calls
|
||||
.map((call: any[]) => String(call[2] || ''))
|
||||
.filter((sql: string) => sql.includes('FROM "events"'));
|
||||
expect(tableQueries.length).toBeGreaterThan(0);
|
||||
expect(tableQueries.every((sql: string) => !/\border\s+by\b/i.test(sql))).toBe(true);
|
||||
expect(tableQueries[tableQueries.length - 1]).toContain('LIMIT 101 OFFSET 0');
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
it('shows an actionable message for DuckDB timeout interruption errors', async () => {
|
||||
storeState.connections[0].config.type = 'duckdb';
|
||||
storeState.connections[0].config.database = 'main';
|
||||
backendApp.DBGetColumns.mockResolvedValue({
|
||||
success: true,
|
||||
data: [{ name: 'ID', key: '' }, { name: 'NAME', key: '' }],
|
||||
});
|
||||
backendApp.DBQuery.mockResolvedValue({
|
||||
success: false,
|
||||
message: 'context deadline exceeded INTERRUPT Error: Interrupted!',
|
||||
fields: [],
|
||||
data: [],
|
||||
});
|
||||
|
||||
const renderer = await renderAndReload(createTab({ id: 'tab-duckdb-timeout', dbName: 'main', tableName: 'events', title: 'events' }));
|
||||
|
||||
expect(messageApi.error).toHaveBeenCalledWith('DuckDB 查询超过连接超时时间,已中断。请调大连接超时时间,或减少排序/筛选范围后重试。');
|
||||
expect(storeState.addSqlLog.mock.calls.some((call: any[]) => String(call[0]?.message || '').includes('context deadline exceeded'))).toBe(true);
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
it('keeps non-Oracle table preview read-only when no safe locator exists', async () => {
|
||||
storeState.connections[0].config.type = 'mysql';
|
||||
storeState.connections[0].config.database = 'main';
|
||||
|
||||
@@ -165,6 +165,20 @@ const isDuckDBComplexColumnType = (columnType?: string): boolean => {
|
||||
return raw.includes('map') || raw.includes('struct') || raw.includes('union') || raw.includes('array') || raw.includes('list');
|
||||
};
|
||||
|
||||
const formatDataViewerQueryError = (dbType: string, messageText: unknown): string => {
|
||||
const rawMessage = String(messageText || '查询失败').trim() || '查询失败';
|
||||
const lower = rawMessage.toLowerCase();
|
||||
const isTimeout = lower.includes('context deadline exceeded') || lower.includes('deadline exceeded') || lower.includes('timeout') || lower.includes('timed out') || lower.includes('超时');
|
||||
const isDuckDBInterrupted = String(dbType || '').trim().toLowerCase() === 'duckdb' && (lower.includes('interrupt error') || lower.includes('interrupted'));
|
||||
if (isTimeout || isDuckDBInterrupted) {
|
||||
if (String(dbType || '').trim().toLowerCase() === 'duckdb') {
|
||||
return 'DuckDB 查询超过连接超时时间,已中断。请调大连接超时时间,或减少排序/筛选范围后重试。';
|
||||
}
|
||||
return '查询超过连接超时时间,已中断。请调大连接超时时间,或减少查询范围后重试。';
|
||||
}
|
||||
return rawMessage;
|
||||
};
|
||||
|
||||
const reverseOrderBySQL = (orderBySQL: string): string => {
|
||||
const raw = String(orderBySQL || '').trim();
|
||||
if (!raw) return '';
|
||||
@@ -929,11 +943,11 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAct
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error(String(resData.message || '查询失败'));
|
||||
message.error(formatDataViewerQueryError(dbTypeLower, resData.message));
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (fetchSeqRef.current !== seq) return;
|
||||
message.error("Error fetching data: " + e.message);
|
||||
message.error(formatDataViewerQueryError(dbTypeLower, e?.message || e));
|
||||
addSqlLog({
|
||||
id: `log-${Date.now()}-error`,
|
||||
timestamp: Date.now(),
|
||||
|
||||
Reference in New Issue
Block a user