From 6421662f5df417548ff209ee7e055b32839578c1 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Tue, 16 Jun 2026 14:05:28 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(oracle):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=A3=B8=E8=A1=A8=E6=9F=A5=E8=AF=A2=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=85=83=E6=95=B0=E6=8D=AE=E7=BC=BA=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Oracle-like 裸表查询默认使用连接用户名作为 schema 加载字段元数据 - 修复未写 schema 时结果表头不显示字段类型和注释的问题 - 保持显式 schema.table 查询优先级不变 - 补充查询结果表引用解析和 QueryEditor 回归测试 --- .../QueryEditor.external-sql-save.test.tsx | 51 +++++++++++++++++++ frontend/src/components/QueryEditor.tsx | 11 +++- frontend/src/utils/queryResultTable.test.ts | 9 ++++ frontend/src/utils/queryResultTable.ts | 13 +++-- 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx index 075d0ef4..eb697756 100644 --- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx +++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx @@ -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(); + }); + + 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'; diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index c8e83062..57f38b23 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -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), diff --git a/frontend/src/utils/queryResultTable.test.ts b/frontend/src/utils/queryResultTable.test.ts index 5726ff62..20da643d 100644 --- a/frontend/src/utils/queryResultTable.test.ts +++ b/frontend/src/utils/queryResultTable.test.ts @@ -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({ diff --git a/frontend/src/utils/queryResultTable.ts b/frontend/src/utils/queryResultTable.ts index 0d5548f8..753f56d1 100644 --- a/frontend/src/utils/queryResultTable.ts +++ b/frontend/src/utils/queryResultTable.ts @@ -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;