mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-16 19:49:51 +08:00
🐛 fix(tdengine): 修复旧版 TDengine 元数据查询与驱动版本选择异常
- 放开 TDengine 已安装驱动的历史版本切换入口 - 兼容低版本 SHOW TABLES FROM 语法差异 - 修复表概览加载时报 [0x2600] syntax error near - 新增后端兼容与前端交互回归测试 - Close #531
This commit is contained in:
@@ -71,7 +71,32 @@ vi.mock('antd', () => {
|
||||
<input value={value} onChange={onChange} placeholder={placeholder} />
|
||||
);
|
||||
|
||||
const Select = () => null;
|
||||
const Select = ({ value, options, disabled, loading, placeholder, onOpenChange, onChange }: any) => (
|
||||
<select
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
data-select-loading={String(loading)}
|
||||
data-select-placeholder={placeholder}
|
||||
onFocus={() => onOpenChange?.(true)}
|
||||
onChange={(event) => onChange?.(event.target.value)}
|
||||
>
|
||||
<option value="">{placeholder || ''}</option>
|
||||
{(options || []).flatMap((item: any) => {
|
||||
if (Array.isArray(item?.options)) {
|
||||
return item.options.map((grouped: any) => (
|
||||
<option key={grouped.value} value={grouped.value}>
|
||||
{String(grouped.label || grouped.value)}
|
||||
</option>
|
||||
));
|
||||
}
|
||||
return (
|
||||
<option key={item.value} value={item.value}>
|
||||
{String(item.label || item.value)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
);
|
||||
const Progress = () => <div data-progress="true" />;
|
||||
const Tag = ({ children }: any) => <span>{children}</span>;
|
||||
const Switch = ({ checked, onChange, disabled }: any) => (
|
||||
@@ -288,4 +313,76 @@ describe('DriverManagerModal toolbar actions', () => {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('allows switching installed TDengine drivers to a historical compatible version', async () => {
|
||||
backendApp.GetDriverStatusList.mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
downloadDir: 'D:/drivers',
|
||||
drivers: [
|
||||
{
|
||||
type: 'tdengine',
|
||||
name: 'TDengine',
|
||||
builtIn: false,
|
||||
pinnedVersion: '3.7.8',
|
||||
installedVersion: '3.7.8',
|
||||
runtimeAvailable: true,
|
||||
packageInstalled: true,
|
||||
connectable: true,
|
||||
defaultDownloadUrl: 'builtin://activate/tdengine',
|
||||
message: '已启用',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
backendApp.GetDriverVersionList.mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
versions: [
|
||||
{ version: '3.7.8', downloadUrl: 'builtin://activate/tdengine', recommended: true },
|
||||
{ version: '3.3.1', downloadUrl: 'builtin://activate/tdengine?channel=history&version=3.3.1' },
|
||||
],
|
||||
},
|
||||
});
|
||||
backendApp.DownloadDriverPackage.mockResolvedValue({ success: true });
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<DriverManagerModal open onClose={vi.fn()} />);
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const refreshButton = findButton(renderer!, '刷新');
|
||||
await act(async () => {
|
||||
await refreshButton.props.onClick();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const versionSelect = renderer!.root.findByType('select');
|
||||
await act(async () => {
|
||||
versionSelect.props.onFocus();
|
||||
});
|
||||
await flushPromises();
|
||||
expect(backendApp.GetDriverVersionList).toHaveBeenCalledWith('tdengine', '');
|
||||
|
||||
const reloadedVersionSelect = renderer!.root.findByType('select');
|
||||
await act(async () => {
|
||||
reloadedVersionSelect.props.onChange({ target: { value: '3.3.1@@builtin://activate/tdengine?channel=history&version=3.3.1' } });
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const switchButtons = renderer!.root.findAll((node) => node.type === 'button' && textContent(node).includes('切换版本'));
|
||||
expect(switchButtons).toHaveLength(1);
|
||||
const switchButton = switchButtons[0];
|
||||
await act(async () => {
|
||||
await switchButton.props.onClick();
|
||||
});
|
||||
|
||||
expect(backendApp.DownloadDriverPackage).toHaveBeenCalledWith(
|
||||
'tdengine',
|
||||
'3.3.1',
|
||||
'builtin://activate/tdengine?channel=history&version=3.3.1',
|
||||
'D:/drivers',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -695,6 +695,29 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
return selectedOption?.version || row.pinnedVersion || '';
|
||||
}, [selectedVersionMap, versionMap]);
|
||||
|
||||
const resolveSelectedVersionOption = useCallback((row: DriverStatusRow) => {
|
||||
const options = versionMap[row.type] || [];
|
||||
const selectedKey = selectedVersionMap[row.type];
|
||||
return (
|
||||
options.find((item) => buildVersionOptionKey(item) === selectedKey) ||
|
||||
options.find((item) => item.recommended) ||
|
||||
options[0]
|
||||
);
|
||||
}, [selectedVersionMap, versionMap]);
|
||||
|
||||
const resolveInstalledDriverVersion = useCallback((row: DriverStatusRow) => (
|
||||
String(row.installedVersion || '').trim() || String(row.pinnedVersion || '').trim()
|
||||
), []);
|
||||
|
||||
const isDriverVersionSwitchPending = useCallback((row: DriverStatusRow) => {
|
||||
if (row.builtIn || (!row.packageInstalled && !row.connectable)) {
|
||||
return false;
|
||||
}
|
||||
const selectedVersion = String(resolveSelectedVersionOption(row)?.version || '').trim();
|
||||
const installedVersion = resolveInstalledDriverVersion(row);
|
||||
return !!selectedVersion && !!installedVersion && selectedVersion !== installedVersion;
|
||||
}, [resolveInstalledDriverVersion, resolveSelectedVersionOption]);
|
||||
|
||||
const installDriver = useCallback(async (
|
||||
row: DriverStatusRow,
|
||||
actionOptions?: { silentToast?: boolean; skipRefresh?: boolean },
|
||||
@@ -714,9 +737,9 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
}
|
||||
const selectedKey = selectedVersionMap[row.type];
|
||||
const selectedOption =
|
||||
versionOptions.find((item) => buildVersionOptionKey(item) === selectedKey) ||
|
||||
(row.needsUpdate ? versionOptions.find((item) => item.version === row.pinnedVersion) : undefined) ||
|
||||
(row.needsUpdate ? versionOptions.find((item) => item.recommended) : undefined) ||
|
||||
versionOptions.find((item) => buildVersionOptionKey(item) === selectedKey) ||
|
||||
versionOptions.find((item) => item.recommended) ||
|
||||
versionOptions[0];
|
||||
const selectedVersion = selectedOption?.version || row.pinnedVersion || '';
|
||||
@@ -1022,20 +1045,12 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
return <Text type="secondary">内置驱动无需安装</Text>;
|
||||
}
|
||||
|
||||
const versionLocked = row.packageInstalled || row.connectable;
|
||||
if (versionLocked) {
|
||||
const installedVersion = String(row.installedVersion || '').trim();
|
||||
const revisionHint = row.needsUpdate ? ',需重装' : '';
|
||||
return (
|
||||
<Text type="secondary" className="driver-manager-version-lock">
|
||||
{installedVersion ? `${installedVersion}(已安装${revisionHint})` : `已安装${row.needsUpdate ? ',需重装' : ''}`}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
const options = versionMap[row.type] || [];
|
||||
const selectedKey = selectedVersionMap[row.type];
|
||||
const selectOptions = buildVersionSelectOptions(options);
|
||||
const installedVersion = resolveInstalledDriverVersion(row);
|
||||
const versionSwitchPending = isDriverVersionSwitchPending(row);
|
||||
const selectedOption = resolveSelectedVersionOption(row);
|
||||
const mongoHint = row.type === 'mongodb'
|
||||
? 'MongoDB 4.0 请使用 1.17.x 兼容驱动;2.x 驱动要求 MongoDB 4.2+。'
|
||||
: '';
|
||||
@@ -1063,6 +1078,13 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
void loadVersionPackageSize(row, value);
|
||||
}}
|
||||
/>
|
||||
{(row.packageInstalled || row.connectable) ? (
|
||||
<Text type="secondary" className="driver-manager-small-text">
|
||||
{versionSwitchPending
|
||||
? `当前已安装 ${installedVersion || '当前版本'},已选择 ${selectedOption?.version || '目标版本'},点击“切换版本”生效`
|
||||
: `${installedVersion ? `${installedVersion}(已安装` : '已安装'}${row.needsUpdate ? ',需重装' : ''}${installedVersion ? ')' : ''}`}
|
||||
</Text>
|
||||
) : null}
|
||||
{mongoHint ? <Text type="secondary" className="driver-manager-small-text">{mongoHint}</Text> : null}
|
||||
</div>
|
||||
);
|
||||
@@ -1078,6 +1100,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
const loadingLocal = actionState.driverType === row.type && actionState.kind === 'local';
|
||||
const logs = operationLogMap[row.type] || [];
|
||||
const hasLogs = logs.length > 0;
|
||||
const versionSwitchPending = isDriverVersionSwitchPending(row);
|
||||
|
||||
if (isSlimBuildUnavailable && !row.packageInstalled) {
|
||||
return <Text type="secondary">当前精简版不可安装,请使用 Full 版</Text>;
|
||||
@@ -1087,6 +1110,10 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG
|
||||
<Button type="primary" icon={<DownloadOutlined />} disabled={batchBusy} loading={loadingInstallOrRemove} onClick={() => installDriver(row)}>
|
||||
重装驱动
|
||||
</Button>
|
||||
) : versionSwitchPending ? (
|
||||
<Button type="primary" icon={<DownloadOutlined />} disabled={batchBusy} loading={loadingInstallOrRemove} onClick={() => installDriver(row)}>
|
||||
切换版本
|
||||
</Button>
|
||||
) : row.connectable ? (
|
||||
<Button danger icon={<DeleteOutlined />} disabled={batchBusy} loading={loadingInstallOrRemove} onClick={() => removeDriver(row)}>
|
||||
移除
|
||||
|
||||
162
frontend/src/components/TableOverview.tdengine.test.tsx
Normal file
162
frontend/src/components/TableOverview.tdengine.test.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import React from 'react';
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import TableOverview from './TableOverview';
|
||||
|
||||
const storeState = vi.hoisted(() => ({
|
||||
theme: 'light',
|
||||
appearance: { uiVersion: 'legacy' as const },
|
||||
connections: [
|
||||
{
|
||||
id: 'conn-1',
|
||||
config: {
|
||||
type: 'tdengine',
|
||||
host: '127.0.0.1',
|
||||
port: 6041,
|
||||
user: 'root',
|
||||
password: 'taosdata',
|
||||
database: 'metrics',
|
||||
useSSH: false,
|
||||
ssh: { host: '', port: 22, user: '', password: '', keyPath: '' },
|
||||
},
|
||||
},
|
||||
],
|
||||
addTab: vi.fn(),
|
||||
setActiveContext: vi.fn(),
|
||||
setAIPanelVisible: vi.fn(),
|
||||
addAIContext: vi.fn(),
|
||||
pinnedSidebarTables: [] as string[],
|
||||
setSidebarTablePinned: vi.fn(),
|
||||
}));
|
||||
|
||||
const backendApp = vi.hoisted(() => ({
|
||||
DBGetTables: vi.fn(),
|
||||
DBQuery: vi.fn(),
|
||||
DBShowCreateTable: vi.fn(),
|
||||
ExportTable: vi.fn(),
|
||||
DropTable: vi.fn(),
|
||||
RenameTable: vi.fn(),
|
||||
}));
|
||||
|
||||
const messageApi = vi.hoisted(() => ({
|
||||
error: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../store', () => ({
|
||||
useStore: (selector: (state: typeof storeState) => any) => selector(storeState),
|
||||
buildSidebarTablePinKey: (connectionId: string, dbName: string, tableName: string, schemaName: string) =>
|
||||
`${connectionId}:${dbName}:${schemaName}:${tableName}`,
|
||||
}));
|
||||
|
||||
vi.mock('../../wailsjs/go/app/App', () => backendApp);
|
||||
vi.mock('../utils/autoFetchVisibility', () => ({
|
||||
useAutoFetchVisibility: () => true,
|
||||
}));
|
||||
vi.mock('../utils/connectionRpcConfig', () => ({
|
||||
buildRpcConnectionConfig: (config: unknown) => config,
|
||||
}));
|
||||
vi.mock('./V2TableContextMenu', () => ({
|
||||
V2TableContextMenuView: () => null,
|
||||
}));
|
||||
|
||||
vi.mock('@ant-design/icons', () => {
|
||||
const Icon = () => <span />;
|
||||
return {
|
||||
TableOutlined: Icon,
|
||||
SearchOutlined: Icon,
|
||||
ReloadOutlined: Icon,
|
||||
SortAscendingOutlined: Icon,
|
||||
DatabaseOutlined: Icon,
|
||||
ConsoleSqlOutlined: Icon,
|
||||
EditOutlined: Icon,
|
||||
CopyOutlined: Icon,
|
||||
SaveOutlined: Icon,
|
||||
DeleteOutlined: Icon,
|
||||
ExportOutlined: Icon,
|
||||
AppstoreOutlined: Icon,
|
||||
UnorderedListOutlined: Icon,
|
||||
WarningOutlined: Icon,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('antd', () => {
|
||||
const Button = ({ children, onClick, ...rest }: any) => <button type="button" onClick={onClick} {...rest}>{children}</button>;
|
||||
const Input: any = ({ value, onChange, ...rest }: any) => <input value={value} onChange={onChange} {...rest} />;
|
||||
Input.Search = ({ value, onChange, ...rest }: any) => <input value={value} onChange={onChange} {...rest} />;
|
||||
const Spin = ({ children }: any) => <div>{children}</div>;
|
||||
const Empty = ({ description }: any) => <div>{description}</div>;
|
||||
const Dropdown = ({ children }: any) => <div>{children}</div>;
|
||||
const Tooltip = ({ children }: any) => <div>{children}</div>;
|
||||
const Modal: any = ({ children }: any) => <div>{children}</div>;
|
||||
Modal.confirm = vi.fn();
|
||||
return {
|
||||
Button,
|
||||
Dropdown,
|
||||
Empty,
|
||||
Input,
|
||||
Modal,
|
||||
Spin,
|
||||
Tooltip,
|
||||
message: messageApi,
|
||||
};
|
||||
});
|
||||
|
||||
const flushPromises = async () => {
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const collectText = (node: any): string => {
|
||||
if (!node) {
|
||||
return '';
|
||||
}
|
||||
if (typeof node === 'string') {
|
||||
return node;
|
||||
}
|
||||
if (Array.isArray(node)) {
|
||||
return node.map((item) => collectText(item)).join('');
|
||||
}
|
||||
return collectText(node.children || []);
|
||||
};
|
||||
|
||||
describe('TableOverview tdengine compatibility', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
backendApp.DBGetTables.mockResolvedValue({
|
||||
success: true,
|
||||
data: [
|
||||
{ Table: 'd001' },
|
||||
{ Table: 'meters' },
|
||||
],
|
||||
});
|
||||
backendApp.DBQuery.mockResolvedValue({
|
||||
success: false,
|
||||
message: '[0x2600] syntax error near',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads tdengine overview rows through DBGetTables instead of direct metadata SQL', async () => {
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<TableOverview tab={{
|
||||
id: 'tab-1',
|
||||
title: '表概览 - metrics',
|
||||
type: 'table-overview',
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'metrics',
|
||||
} as any} />);
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(backendApp.DBGetTables).toHaveBeenCalledWith(expect.any(Object), 'metrics');
|
||||
expect(backendApp.DBQuery).not.toHaveBeenCalled();
|
||||
expect(messageApi.error).not.toHaveBeenCalled();
|
||||
const renderedText = collectText(renderer!.toJSON());
|
||||
expect(renderedText).toContain('meters');
|
||||
expect(renderedText).toContain('d001');
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import { Input, Spin, Empty, Dropdown, message, Tooltip, Modal, Button } from 'a
|
||||
import type { MenuProps } from 'antd';
|
||||
import { TableOutlined, SearchOutlined, ReloadOutlined, SortAscendingOutlined, DatabaseOutlined, ConsoleSqlOutlined, EditOutlined, CopyOutlined, SaveOutlined, DeleteOutlined, ExportOutlined, AppstoreOutlined, UnorderedListOutlined, WarningOutlined } from '@ant-design/icons';
|
||||
import { buildSidebarTablePinKey, useStore } from '../store';
|
||||
import { DBQuery, DBShowCreateTable, ExportTable, DropTable, RenameTable } from '../../wailsjs/go/app/App';
|
||||
import { DBGetTables, DBQuery, DBShowCreateTable, ExportTable, DropTable, RenameTable } from '../../wailsjs/go/app/App';
|
||||
import type { TabData } from '../types';
|
||||
import { useAutoFetchVisibility } from '../utils/autoFetchVisibility';
|
||||
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
|
||||
@@ -231,7 +231,7 @@ const parseTableStats = (dialect: string, rows: Record<string, any>[]): TableSta
|
||||
};
|
||||
|
||||
return {
|
||||
name: strVal(['Name', 'name', 'table_name', 'tablename', 'TABLE_NAME', 'Device', 'device']),
|
||||
name: strVal(['Name', 'name', 'table_name', 'tablename', 'TABLE_NAME', 'Table', 'table', 'Device', 'device']),
|
||||
comment: strVal(['Comment', 'table_comment', 'TABLE_COMMENT', 'comments']),
|
||||
rows: numVal(['Rows', 'table_rows', 'TABLE_ROWS', 'num_rows', 'reltuples', 'total_rows']),
|
||||
dataSize: numVal(['Data_length', 'data_length', 'DATA_LENGTH', 'total_bytes']),
|
||||
@@ -290,6 +290,15 @@ const TableOverview: React.FC<TableOverviewProps> = ({ tab }) => {
|
||||
useSSH: connection.config.useSSH || false,
|
||||
ssh: connection.config.ssh || { host: '', port: 22, user: '', password: '', keyPath: '' },
|
||||
};
|
||||
if (metadataDialect === 'tdengine') {
|
||||
const res = await DBGetTables(buildRpcConnectionConfig(config) as any, tab.dbName || '');
|
||||
if (res.success && Array.isArray(res.data)) {
|
||||
setTables(parseTableStats(metadataDialect, res.data));
|
||||
} else {
|
||||
message.error('获取表信息失败: ' + (res.message || '未知错误'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const sql = buildTableStatusSQL(metadataDialect, tab.dbName || '', schemaName);
|
||||
const res = await DBQuery(buildRpcConnectionConfig(config) as any, tab.dbName || '', sql);
|
||||
if (res.success && Array.isArray(res.data)) {
|
||||
|
||||
1
frontend/wailsjs/go/aiservice/Service.d.ts
vendored
1
frontend/wailsjs/go/aiservice/Service.d.ts
vendored
@@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {ai} from '../models';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-nocheck
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
|
||||
1
frontend/wailsjs/go/app/App.d.ts
vendored
1
frontend/wailsjs/go/app/App.d.ts
vendored
@@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {connection} from '../models';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-nocheck
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
|
||||
@@ -252,6 +252,56 @@ func TestTDengineGetTablesIncludesSuperTables(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTDengineGetTablesFallsBackToLegacyFromSyntax(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbConn, state := openTDengineRecordingDB(t)
|
||||
state.mu.Lock()
|
||||
state.queryResults["SHOW TABLES FROM `metrics`"] = tdengineQueryResult{
|
||||
err: fmt.Errorf("[0x2600] syntax error near '`metrics`'"),
|
||||
}
|
||||
state.queryResults["SHOW STABLES FROM `metrics`"] = tdengineQueryResult{
|
||||
err: fmt.Errorf("[0x2600] syntax error near '`metrics`'"),
|
||||
}
|
||||
state.queryResults["SHOW TABLES FROM metrics"] = tdengineQueryResult{
|
||||
columns: []string{"name"},
|
||||
rows: [][]driver.Value{
|
||||
{"d001"},
|
||||
},
|
||||
}
|
||||
state.queryResults["SHOW STABLES FROM metrics"] = tdengineQueryResult{
|
||||
columns: []string{"name"},
|
||||
rows: [][]driver.Value{
|
||||
{"meters"},
|
||||
},
|
||||
}
|
||||
state.mu.Unlock()
|
||||
|
||||
td := &TDengineDB{conn: dbConn}
|
||||
tables, err := td.GetTables("metrics")
|
||||
if err != nil {
|
||||
t.Fatalf("GetTables returned error: %v", err)
|
||||
}
|
||||
|
||||
wantTables := []string{"d001", "meters"}
|
||||
if !reflect.DeepEqual(tables, wantTables) {
|
||||
t.Fatalf("unexpected tables: got=%v want=%v", tables, wantTables)
|
||||
}
|
||||
|
||||
queries := state.snapshotQueries()
|
||||
wantQueries := []string{
|
||||
"SHOW TABLES FROM `metrics`",
|
||||
"SHOW STABLES FROM `metrics`",
|
||||
"SHOW TABLES FROM metrics",
|
||||
"SHOW STABLES FROM metrics",
|
||||
"SHOW TABLES",
|
||||
"SHOW STABLES",
|
||||
}
|
||||
if !reflect.DeepEqual(queries, wantQueries) {
|
||||
t.Fatalf("unexpected query sequence: got=%v want=%v", queries, wantQueries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTDengineGetColumnsFallsBackToLegacyDescribeSyntax(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -211,13 +211,7 @@ func (t *TDengineDB) GetDatabases() ([]string, error) {
|
||||
}
|
||||
|
||||
func (t *TDengineDB) GetTables(dbName string) ([]string, error) {
|
||||
queries := make([]string, 0, 4)
|
||||
if strings.TrimSpace(dbName) != "" {
|
||||
queries = append(queries, fmt.Sprintf("SHOW TABLES FROM `%s`", escapeBacktickIdent(dbName)))
|
||||
queries = append(queries, fmt.Sprintf("SHOW STABLES FROM `%s`", escapeBacktickIdent(dbName)))
|
||||
}
|
||||
queries = append(queries, "SHOW TABLES")
|
||||
queries = append(queries, "SHOW STABLES")
|
||||
queries := tdengineShowTablesQueries(dbName)
|
||||
|
||||
var lastErr error
|
||||
tableSet := make(map[string]struct{})
|
||||
@@ -515,6 +509,35 @@ func escapeBacktickIdent(ident string) string {
|
||||
return strings.ReplaceAll(strings.TrimSpace(ident), "`", "``")
|
||||
}
|
||||
|
||||
func tdengineShowTablesQueries(dbName string) []string {
|
||||
queries := make([]string, 0, 6)
|
||||
appendQuery := func(query string) {
|
||||
query = strings.TrimSpace(query)
|
||||
if query == "" {
|
||||
return
|
||||
}
|
||||
for _, existing := range queries {
|
||||
if existing == query {
|
||||
return
|
||||
}
|
||||
}
|
||||
queries = append(queries, query)
|
||||
}
|
||||
|
||||
db := strings.TrimSpace(dbName)
|
||||
if db != "" {
|
||||
escaped := escapeBacktickIdent(db)
|
||||
appendQuery(fmt.Sprintf("SHOW TABLES FROM `%s`", escaped))
|
||||
appendQuery(fmt.Sprintf("SHOW STABLES FROM `%s`", escaped))
|
||||
appendQuery(fmt.Sprintf("SHOW TABLES FROM %s", db))
|
||||
appendQuery(fmt.Sprintf("SHOW STABLES FROM %s", db))
|
||||
}
|
||||
|
||||
appendQuery("SHOW TABLES")
|
||||
appendQuery("SHOW STABLES")
|
||||
return queries
|
||||
}
|
||||
|
||||
func tdengineDescribeQueries(dbName, tableName string) []string {
|
||||
qualified := quoteTDengineTable(dbName, tableName)
|
||||
legacyQualified := quoteTDengineTableLegacy(dbName, tableName)
|
||||
|
||||
Reference in New Issue
Block a user