fix(completion): resolve ob-oracle schema for alias column completion

This commit is contained in:
zipg authored and GitHub committed 2026-08-26 15:07:37 +08:00
1 parent 1e9babca45
commit 6686d1628e
5 files changed
+112 -8

No files matched your search

@@ -8,8 +8,11 @@ const contentAreaSource = readFileSync(new URL("../../../components/layout/Conte
describe("Oracle session-scoped completion", () => {
it("uses the tab session only for unqualified references without a selected schema", () => {
expect(usesOracleSessionCompletionColumns({ databaseType: "oracle", clientSessionId: "tab-a" })).toBe(true);
expect(usesOracleSessionCompletionColumns({ databaseType: "oceanbase-oracle", clientSessionId: "tab-a" })).toBe(true);
expect(usesOracleSessionCompletionColumns({ databaseType: "oracle", clientSessionId: "tab-a", referenceSchema: "REPORTING" })).toBe(false);
expect(usesOracleSessionCompletionColumns({ databaseType: "oceanbase-oracle", clientSessionId: "tab-a", referenceSchema: "REPORTING" })).toBe(false);
expect(usesOracleSessionCompletionColumns({ databaseType: "oracle", clientSessionId: "tab-a", selectedSchema: "REPORTING" })).toBe(false);
expect(usesOracleSessionCompletionColumns({ databaseType: "oceanbase-oracle", clientSessionId: "tab-a", selectedSchema: "REPORTING" })).toBe(false);
expect(usesOracleSessionCompletionColumns({ databaseType: "postgres", clientSessionId: "tab-a" })).toBe(false);
expect(usesOracleSessionCompletionColumns({ databaseType: "oracle" })).toBe(false);
});
@@ -1,5 +1,9 @@
import type { DatabaseType } from "@/types/database";
export function usesOracleCurrentSchemaCompletion(databaseType?: DatabaseType, schema?: string | null): boolean {
return (databaseType === "oracle" || databaseType === "oceanbase-oracle") && !schema;
}
export function usesOracleSessionCompletionColumns(options: { databaseType?: DatabaseType; selectedSchema?: string; referenceSchema?: string | null; clientSessionId?: string }): boolean {
return options.databaseType === "oracle" && !options.selectedSchema && !options.referenceSchema && !!options.clientSessionId;
return usesOracleCurrentSchemaCompletion(options.databaseType, options.referenceSchema || options.selectedSchema) && !!options.clientSessionId;
}
@@ -1,5 +1,6 @@
import { createPinia, setActivePinia } from "pinia";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { getSqlCompletionContext } from "@/lib/sql/sqlCompletion";
import type { ConnectionConfig } from "@/types/database";
function installLocalStorage() {
@@ -715,6 +716,40 @@ describe("connectionStore completion assistant", () => {
expect(upper).toEqual(lower);
});
it("uses the OceanBase Oracle session schema for an unqualified aliased table", async () => {
const completionAssistantSearch = vi.fn();
const getColumns = vi.fn().mockResolvedValue([{ name: "PARAM_VALUE", data_type: "VARCHAR2(100)", is_nullable: true, column_default: null, is_primary_key: false, extra: null, comment: null }]);
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
completionAssistantSearch,
getColumns,
}));
const sql = "select a. from tbparam a";
const completion = getSqlCompletionContext(sql, sql.indexOf("a.") + 2, { databaseType: "oceanbase-oracle" });
const reference = completion.referencedTables[0];
expect(reference).toEqual(expect.objectContaining({ name: "tbparam", alias: "a", schema: undefined, nameQuoted: false }));
const { useConnectionStore } = await import("@/stores/connectionStore");
const store = useConnectionStore();
store.connections = [oceanBaseOracleConnection()];
store.connectedIds.add("oceanbase-oracle-1");
const columns = await store.listCompletionColumns("oceanbase-oracle-1", "OBORCL", reference!.name, reference!.schema, {
clientSessionId: "tab-a",
version: 0,
tableQuoted: reference!.nameQuoted,
schemaQuoted: reference!.schemaQuoted,
});
expect(completionAssistantSearch).not.toHaveBeenCalled();
expect(getColumns).toHaveBeenCalledWith("oceanbase-oracle-1", "OBORCL", "", "TBPARAM", undefined, "tab-a");
expect(columns).toEqual([expect.objectContaining({ name: "PARAM_VALUE", table: "TBPARAM", schema: undefined, dataType: "VARCHAR2(100)" })]);
expect(store.lookupLocalCompletionColumns("oceanbase-oracle-1", "OBORCL", "TBPARAM")).toEqual([]);
});
it("keeps quoted OceanBase Oracle identifiers exact and isolated from unquoted cache entries", async () => {
const completionAssistantSearch = vi.fn(async (request: { parent_name?: string | null; parent_schema?: string | null }) => ({
candidates: [
+8 -7
View File
@@ -74,6 +74,7 @@ import {
type ConnectionExportProtection,
} from "@/lib/connection/connectionConfigTransfer";
import type { SqlCompletionColumn, SqlCompletionForeignKey, SqlCompletionObject, SqlCompletionTable } from "@/lib/sql/sqlCompletion";
import { usesOracleCurrentSchemaCompletion } from "@/lib/sql/oracleCompletionSession";
import { mergeSqlObjectNavigationType, sqlObjectNavigationTypeFromTableType } from "@/lib/sql/sqlNavigation";
import * as api from "@/lib/backend/api";
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
@@ -7807,11 +7808,11 @@ export const useConnectionStore = defineStore("connection", () => {
const completionTable = uppercaseUnquotedIdentifier && context?.tableQuoted === false ? table.toUpperCase() : table;
const rawCompletionSchema = schema?.trim() || (config?.db_type === "dameng" ? config.username?.trim() || undefined : undefined);
const completionSchema = uppercaseUnquotedIdentifier && rawCompletionSchema && context?.schemaQuoted === false ? rawCompletionSchema.toUpperCase() : rawCompletionSchema;
const usesOracleCurrentSchema = config?.db_type === "oracle" && !completionSchema;
if (isSchemaAwareDatabase(connectionId) && !connectionUsesDatabaseObjectTreeMode(config) && !completionSchema && !usesOracleCurrentSchema) {
const usesCurrentSchema = usesOracleCurrentSchemaCompletion(config?.db_type, completionSchema);
if (isSchemaAwareDatabase(connectionId) && !connectionUsesDatabaseObjectTreeMode(config) && !completionSchema && !usesCurrentSchema) {
return [];
}
const sessionCacheScope = usesOracleCurrentSchema && context?.clientSessionId ? `:${context.clientSessionId}:${context.version ?? 0}` : "";
const sessionCacheScope = usesCurrentSchema && context?.clientSessionId ? `:${context.clientSessionId}:${context.version ?? 0}` : "";
const cacheKey = `${completionColumnsKey(connectionId, database, completionTable, completionSchema, catalog, context)}${sessionCacheScope}`;
if (!completionColumnsCache.value[cacheKey]) {
const requestRevision = completionCacheRevision(connectionId, database);
@@ -7820,7 +7821,7 @@ export const useConnectionStore = defineStore("connection", () => {
async () => {
await ensureConnected(connectionId);
// Use assistant metadata opportunistically, then fall back to canonical metadata.
if (!usesOracleCurrentSchema && !catalog) {
if (!usesCurrentSchema && !catalog) {
try {
const assistantColumns = await listCompletionAssistantColumns(connectionId, database, completionTable, completionSchema, context, requestRevision);
if (assistantColumns.length > 0) {
@@ -7845,8 +7846,8 @@ export const useConnectionStore = defineStore("connection", () => {
// Fall back to the existing metadata path below.
}
}
const querySchema = usesOracleCurrentSchema ? "" : metadataQuerySchema(connectionId, database, completionSchema);
const columns = await api.getColumns(connectionId, database, querySchema, completionTable, catalog, usesOracleCurrentSchema ? context?.clientSessionId : undefined);
const querySchema = usesCurrentSchema ? "" : metadataQuerySchema(connectionId, database, completionSchema);
const columns = await api.getColumns(connectionId, database, querySchema, completionTable, catalog, usesCurrentSchema ? context?.clientSessionId : undefined);
if (requestRevision !== completionCacheRevision(connectionId, database)) return;
completionColumnsCache.value[cacheKey] = columns;
evictOldestCacheEntries(completionColumnsCache.value, COMPLETION_CACHE_MAX);
@@ -7867,7 +7868,7 @@ export const useConnectionStore = defineStore("connection", () => {
isNullable: column.is_nullable,
comment: column.comment,
}));
if (!usesOracleCurrentSchema) indexCompletionColumns(connectionId, database, completionTable, completionSchema, columns, catalog, context);
if (!usesCurrentSchema) indexCompletionColumns(connectionId, database, completionTable, completionSchema, columns, catalog, context);
return columns;
}