🐛 fix(oracle): 修复裸表查询结果字段元数据缺失

- Oracle-like 裸表查询默认使用连接用户名作为 schema 加载字段元数据
- 修复未写 schema 时结果表头不显示字段类型和注释的问题
- 保持显式 schema.table 查询优先级不变
- 补充查询结果表引用解析和 QueryEditor 回归测试
This commit is contained in:
Syngnat
2026-06-16 14:05:28 +08:00
parent 54195e0591
commit 6421662f5d
4 changed files with 80 additions and 4 deletions

View File

@@ -3046,6 +3046,57 @@ describe('QueryEditor external SQL save', () => {
expect(messageApi.warning).not.toHaveBeenCalled();
});
it('uses Oracle login user as default schema for unqualified query result metadata', async () => {
storeState.connections[0].config.type = 'oracle';
storeState.connections[0].config.user = 'dev';
storeState.connections[0].config.database = 'ORCLPDB1';
backendApp.DBQueryMulti.mockResolvedValueOnce({
success: true,
data: [{
columns: ['PID', 'BUSILOG_ID', 'ZJLX_ID', ORACLE_ROWID_LOCATOR_COLUMN],
rows: [{
PID: '200005000000010',
BUSILOG_ID: '00000000000000000000',
ZJLX_ID: '01',
[ORACLE_ROWID_LOCATOR_COLUMN]: 'AAATestAABAAABrXAAA',
}],
}],
});
backendApp.DBGetColumns.mockResolvedValueOnce({
success: true,
data: [
{ name: 'PID', type: 'CHAR(15)', comment: '个人标识', key: '' },
{ name: 'BUSILOG_ID', type: 'VARCHAR2(20)', comment: '业务日志编号', key: '' },
{ name: 'ZJLX_ID', type: 'CHAR(2)', comment: '证件类型', key: '' },
],
});
let renderer: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ dbName: 'ORCLPDB1', query: 'select * from per_cert_info' })} />);
});
await act(async () => {
await findButton(renderer!, '运行').props.onClick();
});
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
expect(backendApp.DBGetColumns).toHaveBeenCalledWith(expect.anything(), 'DEV', 'PER_CERT_INFO');
expect(dataGridState.latestProps?.tableName).toBe('DEV.PER_CERT_INFO');
expect(dataGridState.latestProps?.editLocator).toMatchObject({
strategy: 'oracle-rowid',
columns: ['ROWID'],
valueColumns: [ORACLE_ROWID_LOCATOR_COLUMN],
readOnly: false,
});
expect(dataGridState.latestProps?.readOnly).toBe(false);
expect(String(backendApp.DBQueryMulti.mock.calls[0][2])).toContain('gonavi_query_source.ROWID');
expect(messageApi.warning).not.toHaveBeenCalled();
});
it('uses a unique index locator for query results without primary keys', async () => {
storeState.connections[0].config.type = 'oracle';
storeState.connections[0].config.database = 'ORCLPDB1';

View File

@@ -1794,7 +1794,8 @@ const resolveQueryLocatorPlan = async ({
};
if (forceReadOnly) return plan;
const tableRef = extractQueryResultTableRef(statement, dbType, currentDb);
const defaultSchema = isOracleLikeDialect(dbType) ? String(config?.user || '').trim() : '';
let tableRef = extractQueryResultTableRef(statement, dbType, currentDb, defaultSchema);
if (!tableRef) return plan;
plan.tableRef = tableRef;
if (isSystemMetadataQueryResult(tableRef, dbType)) {
@@ -1811,6 +1812,14 @@ const resolveQueryLocatorPlan = async ({
return plan;
}
if (isOracleLikeDialect(dbType) && defaultSchema && !String(tableRef.tableName || '').includes('.')) {
tableRef = {
...tableRef,
tableName: `${tableRef.metadataDbName}.${tableRef.metadataTableName}`,
};
plan.tableRef = tableRef;
}
try {
const [resCols, resIndexes] = await Promise.all([
DBGetColumns(buildRpcConnectionConfig(config) as any, tableRef.metadataDbName, tableRef.metadataTableName),

View File

@@ -39,6 +39,15 @@ describe('extractQueryResultTableRef', () => {
});
});
it('uses the login user as Oracle default schema when the current db is a service name', () => {
expect(extractQueryResultTableRef('SELECT * FROM per_cert_info', 'oracle', 'ORCLPDB1', 'dev'))
.toEqual({
tableName: 'PER_CERT_INFO',
metadataDbName: 'DEV',
metadataTableName: 'PER_CERT_INFO',
});
});
it('keeps existing simple table behavior for MySQL-style qualified names', () => {
expect(extractQueryResultTableRef('SELECT * FROM app.users LIMIT 500', 'mysql', 'app'))
.toEqual({

View File

@@ -61,6 +61,7 @@ export const extractQueryResultTableRef = (
sql: string,
dialect: string,
currentDb: string,
defaultSchema = '',
): QueryResultTableRef | undefined => {
const text = String(sql || '').trim();
if (!text) return undefined;
@@ -77,10 +78,16 @@ export const extractQueryResultTableRef = (
if (!metadataTableName) return undefined;
const owner = parts.length >= 2 ? parts[parts.length - 2] : '';
const metadataDbName = owner || normalizeCurrentDbName(currentDb, dialect);
const tableName = (isOracleLikeDialect(dialect) || keepsQualifiedTableNameForMetadata(dialect)) && owner
const defaultOracleSchema = isOracleLikeDialect(dialect)
? normalizeCurrentDbName(defaultSchema, dialect)
: '';
const fallbackSchema = isOracleLikeDialect(dialect)
? defaultOracleSchema || normalizeCurrentDbName(currentDb, dialect)
: normalizeCurrentDbName(currentDb, dialect);
const metadataDbName = owner || fallbackSchema;
const tableName = isOracleLikeDialect(dialect) && owner
? `${owner}.${metadataTableName}`
: metadataTableName;
: (keepsQualifiedTableNameForMetadata(dialect) && owner ? `${owner}.${metadataTableName}` : metadataTableName);
const resolvedMetadataTableName = keepsQualifiedTableNameForMetadata(dialect) && owner
? `${owner}.${metadataTableName}`
: metadataTableName;