🐛 fix(query-editor): 修复外部 SQL 标签状态与 OceanBase 查询改写

- 修复外部 SQL 文件删除后标签残留及关闭异常
- 修复 OceanBase Oracle 查询注入隐藏 ROWID 时的表名改写
- 修复小写表名执行时的精确引用并保留日志中的原始 SQL
- 补充查询编辑器相关回归测试
This commit is contained in:
Syngnat
2026-06-17 09:49:39 +08:00
parent 0632c5242c
commit 7ff3e00759
3 changed files with 345 additions and 6 deletions

View File

@@ -3258,6 +3258,100 @@ describe('QueryEditor external SQL save', () => {
renderer?.unmount();
});
it('rewrites OceanBase Oracle SELECT * queries before injecting hidden ROWID locator columns', async () => {
storeState.connections[0].config.type = 'oceanbase';
(storeState.connections[0].config as any).oceanBaseProtocol = 'oracle';
storeState.connections[0].config.user = 'dev';
storeState.connections[0].config.database = 'ORCLPDB1';
backendApp.DBQueryMulti.mockResolvedValueOnce({
success: true,
data: [{ columns: ['WAFER_ID', ORACLE_ROWID_LOCATOR_COLUMN], rows: [{ WAFER_ID: 'R015Z10F08', [ORACLE_ROWID_LOCATOR_COLUMN]: 'AAAA' }] }],
});
backendApp.DBGetColumns.mockResolvedValueOnce({
success: true,
data: [{ name: 'WAFER_ID', key: '' }],
});
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ dbName: 'ORCLPDB1', query: 'SELECT * FROM EDC_LOG' })} />);
});
await act(async () => {
await findButton(renderer!, '运行').props.onClick();
});
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
const executedSql = String(backendApp.DBQueryMulti.mock.calls[0][2]);
expect(backendApp.DBGetColumns).toHaveBeenCalledWith(expect.anything(), 'DEV', 'EDC_LOG');
expect(executedSql).toContain('FROM EDC_LOG gonavi_query_source');
expect(executedSql).toMatch(/SELECT\s+gonavi_query_source\.\*\s*,\s+gonavi_query_source\.ROWID\s+AS\s+"__gonavi_oracle_rowid__"/i);
expect(dataGridState.latestProps?.tableName).toBe('DEV.EDC_LOG');
expect(dataGridState.latestProps?.editLocator).toMatchObject({
strategy: 'oracle-rowid',
columns: ['ROWID'],
valueColumns: [ORACLE_ROWID_LOCATOR_COLUMN],
hiddenColumns: [ORACLE_ROWID_LOCATOR_COLUMN],
readOnly: false,
});
expect(dataGridState.latestProps?.readOnly).toBe(false);
expect(dataGridState.latestProps?.showRowNumberColumn).toBe(true);
expect(storeState.addSqlLog).toHaveBeenCalledWith(expect.objectContaining({
sql: 'SELECT * FROM EDC_LOG',
status: 'success',
}));
expect(messageApi.warning).not.toHaveBeenCalled();
renderer?.unmount();
});
it('quotes exact-case OceanBase Oracle lowercase tables for execution while keeping sql logs unchanged', async () => {
storeState.connections[0].config.type = 'oceanbase';
(storeState.connections[0].config as any).oceanBaseProtocol = 'oracle';
storeState.connections[0].config.user = 'SYS@oracle_tenant#cluster';
storeState.connections[0].config.database = 'ORCLPDB1';
backendApp.DBGetTables.mockResolvedValueOnce({
success: true,
data: [{ Table: 'SYS.test' }],
});
backendApp.DBQueryMulti.mockResolvedValueOnce({
success: true,
data: [{ columns: ['NAME', ORACLE_ROWID_LOCATOR_COLUMN], rows: [{ NAME: 'demo', [ORACLE_ROWID_LOCATOR_COLUMN]: 'AAAA' }] }],
});
backendApp.DBGetColumns.mockResolvedValueOnce({
success: true,
data: [{ name: 'NAME', key: '' }],
});
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ dbName: 'ORCLPDB1', query: 'select * from test' })} />);
});
await act(async () => {
await findButton(renderer!, '运行').props.onClick();
});
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
const executedSql = String(backendApp.DBQueryMulti.mock.calls[0][2]);
expect(backendApp.DBGetTables).toHaveBeenCalledWith(expect.anything(), 'SYS');
expect(backendApp.DBGetColumns).toHaveBeenCalledWith(expect.anything(), 'SYS', 'test');
expect(executedSql).toMatch(/from\s+"test"\s+gonavi_query_source/i);
expect(executedSql).toMatch(/SELECT\s+gonavi_query_source\.\*\s*,\s+gonavi_query_source\.ROWID\s+AS\s+"__gonavi_oracle_rowid__"/i);
expect(dataGridState.latestProps?.tableName).toBe('SYS.test');
expect(storeState.addSqlLog).toHaveBeenCalledWith(expect.objectContaining({
sql: 'select * from test',
status: 'success',
}));
expect(messageApi.warning).not.toHaveBeenCalled();
renderer?.unmount();
});
it('keeps Oracle anonymous PL/SQL blocks intact when running from the editor', async () => {
storeState.connections[0].config.type = 'oracle';
storeState.connections[0].config.database = 'ORCLPDB1';