diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx
index 2570da8..c580dc4 100644
--- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx
+++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx
@@ -5541,6 +5541,47 @@ describe('QueryEditor external SQL save', () => {
renderer?.unmount();
});
+ it('qualifies OceanBase Oracle read-only queries with the selected schema instead of the login user', 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 = 'ORCLPDB1';
+ (storeState.connections[0].config as any).readOnly = true;
+ backendApp.DBGetTables.mockResolvedValueOnce({
+ success: true,
+ data: [{ Table: 'SBDEV.PERSON_INFO' }],
+ });
+ backendApp.DBQueryMulti.mockResolvedValueOnce({
+ success: true,
+ data: [{ columns: ['ZJJHM'], rows: [{ ZJJHM: '' }] }],
+ });
+
+ 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).toHaveBeenCalledWith(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');
+ expect(dataGridState.latestProps?.readOnly).toBe(true);
+ expect(storeState.addSqlLog).toHaveBeenCalledWith(expect.objectContaining({
+ sql: "select * from person_info where zjjhm=''",
+ status: 'success',
+ }));
+ 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 adb22c9..f34d737 100644
--- a/frontend/src/components/QueryEditor.tsx
+++ b/frontend/src/components/QueryEditor.tsx
@@ -109,6 +109,7 @@ import {
resolveNextResultSetIndex,
resolveOracleExactCaseTableReference,
resolveOracleLikeDefaultSchemaName,
+ resolveOracleLikeExecutionSchemaName,
resolveQueryEditorFormatterLanguage,
resolveQueryEditorHoverTarget,
resolveQueryEditorNavigationDecorations,
@@ -3353,6 +3354,13 @@ 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();
@@ -3409,12 +3417,14 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const leadingSegments = splitQueryIdentifierPathSegments(leadingTable.tableText);
const oracleLookupDbName = String(
(leadingSegments.length >= 2 ? leadingSegments[0]?.value : '')
- || defaultOracleSchema
+ || oracleExecutionSchema
|| currentDb
|| '',
).trim();
const oracleTables = oracleLookupDbName ? await getOracleTablesForDb(oracleLookupDbName) : [];
- const exactQualifiedTable = resolveOracleExactCaseTableReference(statement, oracleLookupDbName, oracleTables);
+ const exactQualifiedTable = resolveOracleExactCaseTableReference(statement, oracleLookupDbName, oracleTables, {
+ qualifyUnqualified: shouldQualifyOracleUnqualifiedTables,
+ });
if (exactQualifiedTable) {
executableStatement = rewriteLeadingSelectTableReference(statement, exactQualifiedTable) || statement;
}
diff --git a/frontend/src/components/queryEditor/QueryEditorHelpers.ts b/frontend/src/components/queryEditor/QueryEditorHelpers.ts
index da72521..7f2770d 100644
--- a/frontend/src/components/queryEditor/QueryEditorHelpers.ts
+++ b/frontend/src/components/queryEditor/QueryEditorHelpers.ts
@@ -1147,6 +1147,7 @@ export const resolveOracleExactCaseTableReference = (
statement: string,
currentDb: string,
tables: CompletionTableMeta[],
+ options?: { qualifyUnqualified?: boolean },
): string | undefined => {
const leadingTable = matchLeadingSelectTableReference(statement);
if (!leadingTable) return undefined;
@@ -1155,7 +1156,8 @@ export const resolveOracleExactCaseTableReference = (
if (segments.length === 0 || segments.length > 2 || segments.some((segment) => segment.quoted)) {
return undefined;
}
- if (!segments.some((segment) => /[a-z]/.test(segment.value))) {
+ const shouldQualifyUnqualified = Boolean(options?.qualifyUnqualified && segments.length === 1);
+ if (!segments.some((segment) => /[a-z]/.test(segment.value)) && !shouldQualifyUnqualified) {
return undefined;
}
@@ -1170,7 +1172,7 @@ export const resolveOracleExactCaseTableReference = (
const parsed = splitSidebarQualifiedName(String(table.tableName || ''));
const objectName = String(parsed.objectName || table.tableName || '').trim();
const schemaName = String(parsed.schemaName || table.dbName || '').trim();
- if (objectName !== rawObjectName) return false;
+ if (objectName !== rawObjectName && objectName.toLowerCase() !== rawObjectName.toLowerCase()) return false;
if (!rawSchemaName) return true;
return schemaName.toLowerCase() === rawSchemaName.toLowerCase();
});
@@ -1181,6 +1183,8 @@ export const resolveOracleExactCaseTableReference = (
const exactSchemaName = String(matchedParsed.schemaName || matched.dbName || rawSchemaName).trim();
const quotedParts = rawSchemaName
? [exactSchemaName, exactObjectName]
+ : shouldQualifyUnqualified
+ ? [exactSchemaName || targetDbName, exactObjectName]
: [exactObjectName];
if (quotedParts.some((part) => !String(part || '').trim())) {
return undefined;
@@ -1195,6 +1199,15 @@ export const resolveOracleLikeDefaultSchemaName = (config: any): string => {
return String(userPart || '').trim();
};
+export const resolveOracleLikeExecutionSchemaName = (config: any, currentDb: string): string => {
+ const selectedDb = String(currentDb || '').trim();
+ const configuredDb = String(config?.database || '').trim();
+ if (selectedDb && (!configuredDb || selectedDb.toLowerCase() !== configuredDb.toLowerCase())) {
+ return selectedDb;
+ }
+ return resolveOracleLikeDefaultSchemaName(config) || selectedDb;
+};
+
export const getQueryEditorModelTextIfWithinLimit = (model: any, maxTextLength: number): string | null => {
const modelLength = getQueryEditorModelValueLength(model);
if (modelLength !== null && modelLength > maxTextLength) {
@@ -2053,7 +2066,9 @@ export const resolveQueryLocatorPlan = async ({
};
if (forceReadOnly) return plan;
- const defaultSchema = isOracleLikeDialect(dbType) ? resolveOracleLikeDefaultSchemaName(config) : '';
+ const defaultSchema = isOracleLikeDialect(dbType)
+ ? resolveOracleLikeExecutionSchemaName(config, currentDb)
+ : '';
let tableRef = extractQueryResultTableRef(statement, dbType, currentDb, defaultSchema);
if (!tableRef) return plan;
plan.tableRef = tableRef;