diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx
index 2183cb9c..69a1e6a2 100644
--- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx
+++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx
@@ -8138,6 +8138,10 @@ describe('QueryEditor external SQL save', () => {
storeState.connections[0].config.user = 'SBDEVREAD';
storeState.connections[0].config.database = 'ORCLPDB1';
(storeState.connections[0].config as any).readOnly = true;
+ backendApp.DBGetTables.mockResolvedValueOnce({
+ success: true,
+ data: [],
+ });
backendApp.DBGetTables.mockResolvedValueOnce({
success: true,
data: [{ Table: 'SBDEV.PERSON_INFO' }],
@@ -8161,7 +8165,8 @@ describe('QueryEditor external SQL save', () => {
});
const executedSql = String(backendApp.DBQueryMulti.mock.calls[0][2]);
- expect(backendApp.DBGetTables).toHaveBeenCalledWith(expect.anything(), 'SBDEV');
+ expect(backendApp.DBGetTables).toHaveBeenNthCalledWith(1, expect.anything(), 'SBDEVREAD');
+ expect(backendApp.DBGetTables).toHaveBeenNthCalledWith(2, expect.anything(), 'SBDEV');
expect(backendApp.DBGetColumns).not.toHaveBeenCalled();
expect(executedSql).toMatch(/from\s+"SBDEV"\."PERSON_INFO"\s+where\s+zjjhm=''/i);
expect(executedSql).not.toContain('SBDEVREAD.PERSON_INFO');
@@ -8173,6 +8178,46 @@ describe('QueryEditor external SQL save', () => {
renderer?.unmount();
});
+ it('keeps qualifying OceanBase Oracle read-only queries when config.database already equals the selected schema', async () => {
+ storeState.connections[0].config.type = 'oceanbase';
+ (storeState.connections[0].config as any).oceanBaseProtocol = 'oracle';
+ storeState.connections[0].config.user = 'SBDEVREAD';
+ storeState.connections[0].config.database = 'SBDEV';
+ (storeState.connections[0].config as any).readOnly = true;
+ backendApp.DBGetTables.mockResolvedValueOnce({
+ success: true,
+ data: [],
+ });
+ backendApp.DBGetTables.mockResolvedValueOnce({
+ success: true,
+ data: [{ Table: 'SBDEV.SYSM_USER' }],
+ });
+ backendApp.DBQueryMulti.mockResolvedValueOnce({
+ success: true,
+ data: [{ columns: ['USER_ID'], rows: [{ USER_ID: '0001477884' }] }],
+ });
+
+ 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();
+ });
+
+ const executedSql = String(backendApp.DBQueryMulti.mock.calls[0][2]);
+ expect(backendApp.DBGetTables).toHaveBeenNthCalledWith(1, expect.anything(), 'SBDEVREAD');
+ expect(backendApp.DBGetTables).toHaveBeenNthCalledWith(2, expect.anything(), 'SBDEV');
+ expect(executedSql).toMatch(/from\s+"SBDEV"\."SYSM_USER"\s+where\s+user_id='0001477884'/i);
+ expect(executedSql).not.toContain('SBDEVREAD.SYSM_USER');
+ 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';
diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx
index 5cede146..de2a2c43 100644
--- a/frontend/src/components/QueryEditor.tsx
+++ b/frontend/src/components/QueryEditor.tsx
@@ -132,6 +132,7 @@ import {
resolveOracleExactCaseTableReference,
resolveOracleLikeDefaultSchemaName,
resolveOracleLikeExecutionSchemaName,
+ resolveOracleLikeLookupSchemaCandidates,
resolveQueryEditorFormatterLanguage,
resolveQueryEditorHoverTarget,
resolveQueryEditorNavigationDecorations,
@@ -6012,13 +6013,6 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const defaultOracleSchema = isOracleLikeDialect(normalizedDbType)
? resolveOracleLikeDefaultSchemaName(config)
: '';
- const oracleExecutionSchema = isOracleLikeDialect(normalizedDbType)
- ? resolveOracleLikeExecutionSchemaName(config, currentDb)
- : '';
- const shouldQualifyOracleUnqualifiedTables = Boolean(
- oracleExecutionSchema
- && oracleExecutionSchema.toLowerCase() !== String(defaultOracleSchema || '').trim().toLowerCase(),
- );
const oracleTableCache = new Map();
const getOracleTablesForDb = async (dbName: string): Promise => {
const normalizedDbName = String(dbName || '').trim();
@@ -6073,16 +6067,23 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const leadingTable = matchLeadingSelectTableReference(statement);
if (leadingTable) {
const leadingSegments = splitQueryIdentifierPathSegments(leadingTable.tableText);
- const oracleLookupDbName = String(
- (leadingSegments.length >= 2 ? leadingSegments[0]?.value : '')
- || oracleExecutionSchema
- || currentDb
- || '',
- ).trim();
- const oracleTables = oracleLookupDbName ? await getOracleTablesForDb(oracleLookupDbName) : [];
- const exactQualifiedTable = resolveOracleExactCaseTableReference(statement, oracleLookupDbName, oracleTables, {
- qualifyUnqualified: shouldQualifyOracleUnqualifiedTables,
- });
+ const oracleLookupDbCandidates = leadingSegments.length >= 2
+ ? [String(leadingSegments[0]?.value || '').trim()].filter(Boolean)
+ : resolveOracleLikeLookupSchemaCandidates(config, currentDb);
+ let exactQualifiedTable: string | undefined;
+ for (const oracleLookupDbName of oracleLookupDbCandidates) {
+ const oracleTables = oracleLookupDbName ? await getOracleTablesForDb(oracleLookupDbName) : [];
+ exactQualifiedTable = resolveOracleExactCaseTableReference(statement, oracleLookupDbName, oracleTables, {
+ qualifyUnqualified: Boolean(
+ leadingSegments.length === 1
+ && oracleLookupDbName
+ && oracleLookupDbName.toLowerCase() !== String(defaultOracleSchema || '').trim().toLowerCase(),
+ ),
+ });
+ if (exactQualifiedTable) {
+ break;
+ }
+ }
if (exactQualifiedTable) {
executableStatement = rewriteLeadingSelectTableReference(statement, exactQualifiedTable) || statement;
}
diff --git a/frontend/src/components/queryEditor/QueryEditorHelpers.test.ts b/frontend/src/components/queryEditor/QueryEditorHelpers.test.ts
new file mode 100644
index 00000000..7634a44e
--- /dev/null
+++ b/frontend/src/components/queryEditor/QueryEditorHelpers.test.ts
@@ -0,0 +1,33 @@
+import { describe, expect, it } from 'vitest';
+
+import {
+ resolveOracleLikeDefaultSchemaName,
+ resolveOracleLikeExecutionSchemaName,
+ resolveOracleLikeLookupSchemaCandidates,
+} from './QueryEditorHelpers';
+
+describe('QueryEditorHelpers Oracle-like execution schema', () => {
+ it('uses the selected schema when it differs from the login user', () => {
+ const config = {
+ type: 'oceanbase',
+ oceanBaseProtocol: 'oracle',
+ user: 'SBDEVREAD',
+ database: 'SBDEV',
+ };
+
+ expect(resolveOracleLikeDefaultSchemaName(config)).toBe('SBDEVREAD');
+ expect(resolveOracleLikeExecutionSchemaName(config, 'SBDEV')).toBe('SBDEVREAD');
+ expect(resolveOracleLikeLookupSchemaCandidates(config, 'SBDEV')).toEqual(['SBDEVREAD', 'SBDEV']);
+ });
+
+ it('keeps the login user schema when the selected schema is the same owner', () => {
+ const config = {
+ type: 'oracle',
+ user: 'APP_OWNER',
+ database: 'ORCLPDB1',
+ };
+
+ expect(resolveOracleLikeExecutionSchemaName(config, 'APP_OWNER')).toBe('APP_OWNER');
+ expect(resolveOracleLikeLookupSchemaCandidates(config, 'APP_OWNER')).toEqual(['APP_OWNER']);
+ });
+});
diff --git a/frontend/src/components/queryEditor/QueryEditorHelpers.ts b/frontend/src/components/queryEditor/QueryEditorHelpers.ts
index c14c17ac..1e52453e 100644
--- a/frontend/src/components/queryEditor/QueryEditorHelpers.ts
+++ b/frontend/src/components/queryEditor/QueryEditorHelpers.ts
@@ -1305,6 +1305,27 @@ export const resolveOracleLikeExecutionSchemaName = (config: any, currentDb: str
return resolveOracleLikeDefaultSchemaName(config) || selectedDb;
};
+export const resolveOracleLikeLookupSchemaCandidates = (config: any, currentDb: string): string[] => {
+ const candidates: string[] = [];
+ const seen = new Set();
+ const push = (value: string) => {
+ const normalized = String(value || '').trim();
+ if (!normalized) return;
+ const key = normalized.toLowerCase();
+ if (seen.has(key)) return;
+ seen.add(key);
+ candidates.push(normalized);
+ };
+
+ const defaultSchema = resolveOracleLikeDefaultSchemaName(config);
+ const selectedDb = String(currentDb || '').trim();
+ push(defaultSchema);
+ if (selectedDb && selectedDb.toLowerCase() !== String(defaultSchema || '').trim().toLowerCase()) {
+ push(selectedDb);
+ }
+ return candidates;
+};
+
export const getQueryEditorModelTextIfWithinLimit = (model: any, maxTextLength: number): string | null => {
const modelLength = getQueryEditorModelValueLength(model);
if (modelLength !== null && modelLength > maxTextLength) {