feat(completion): support OceanBase Oracle alias columns

Closes #7126
This commit is contained in:
t8y2 committed 2026-08-25 05:33:12 +00:00
1 parent e977e886c3
commit cf652f212e
2 files changed
+86 -2

No files matched your search

@@ -59,6 +59,16 @@ function oracleConnection(): ConnectionConfig {
} as ConnectionConfig;
}
function oceanBaseOracleConnection(): ConnectionConfig {
return {
...oracleConnection(),
id: "oceanbase-oracle-1",
name: "OceanBase Oracle",
db_type: "oceanbase-oracle",
database: "OBORCL",
} as ConnectionConfig;
}
function sapHanaConnection(): ConnectionConfig {
return {
...postgresConnection(),
@@ -678,6 +688,80 @@ describe("connectionStore completion assistant", () => {
expect(completionAssistantSearch).not.toHaveBeenCalled();
});
it.each(["empty", "error"] as const)("normalizes unquoted OceanBase Oracle identifiers after an %s assistant result", async (assistantResult) => {
const completionAssistantSearch = assistantResult === "empty" ? vi.fn().mockResolvedValue({ candidates: [], incomplete: false, fallback_used: false }) : vi.fn().mockRejectedValue(new Error("assistant unavailable"));
const getColumns = vi.fn().mockResolvedValue([{ name: "ORDER_ID", data_type: "NUMBER", is_nullable: false, column_default: null, is_primary_key: true, extra: null, comment: null }]);
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
completionAssistantSearch,
getColumns,
}));
const { useConnectionStore } = await import("@/stores/connectionStore");
const store = useConnectionStore();
store.connections = [oceanBaseOracleConnection()];
store.connectedIds.add("oceanbase-oracle-1");
const lower = await store.listCompletionColumns("oceanbase-oracle-1", "OBORCL", "orders_alias", "app", { tableQuoted: false, schemaQuoted: false });
const upper = await store.listCompletionColumns("oceanbase-oracle-1", "OBORCL", "ORDERS_ALIAS", "APP", { tableQuoted: false, schemaQuoted: false });
expect(completionAssistantSearch).toHaveBeenCalledOnce();
expect(completionAssistantSearch).toHaveBeenCalledWith(expect.objectContaining({ schema: "APP", parent_schema: "APP", parent_name: "ORDERS_ALIAS" }));
expect(getColumns).toHaveBeenCalledOnce();
expect(getColumns).toHaveBeenCalledWith("oceanbase-oracle-1", "OBORCL", "APP", "ORDERS_ALIAS", undefined, undefined);
expect(lower).toEqual([expect.objectContaining({ name: "ORDER_ID", table: "ORDERS_ALIAS", schema: "APP" })]);
expect(upper).toEqual(lower);
});
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: [
{
name: request.parent_name === "ORDERS_ALIAS" ? "UPPER_ID" : "MixedId",
kind: "column",
schema: request.parent_schema,
parent_schema: request.parent_schema,
parent_name: request.parent_name,
data_type: "NUMBER",
},
],
incomplete: false,
fallback_used: false,
}));
const getColumns = vi.fn();
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
completionAssistantSearch,
getColumns,
}));
const { useConnectionStore } = await import("@/stores/connectionStore");
const store = useConnectionStore();
store.connections = [oceanBaseOracleConnection()];
store.connectedIds.add("oceanbase-oracle-1");
const unquoted = await store.listCompletionColumns("oceanbase-oracle-1", "OBORCL", "orders_alias", "app", { tableQuoted: false, schemaQuoted: false });
const unquotedCached = await store.listCompletionColumns("oceanbase-oracle-1", "OBORCL", "ORDERS_ALIAS", "APP", { tableQuoted: false, schemaQuoted: false });
const quoted = await store.listCompletionColumns("oceanbase-oracle-1", "OBORCL", "Orders_Alias", "AppSchema", { tableQuoted: true, schemaQuoted: true });
const quotedCached = await store.listCompletionColumns("oceanbase-oracle-1", "OBORCL", "Orders_Alias", "AppSchema", { tableQuoted: true, schemaQuoted: true });
expect(completionAssistantSearch.mock.calls.map(([request]) => ({ schema: request.schema, parent_schema: request.parent_schema, parent_name: request.parent_name }))).toEqual([
{ schema: "APP", parent_schema: "APP", parent_name: "ORDERS_ALIAS" },
{ schema: "AppSchema", parent_schema: "AppSchema", parent_name: "Orders_Alias" },
]);
expect(getColumns).not.toHaveBeenCalled();
expect(unquoted).toEqual([expect.objectContaining({ name: "UPPER_ID", table: "ORDERS_ALIAS", schema: "APP" })]);
expect(unquotedCached).toEqual(unquoted);
expect(quoted).toEqual([expect.objectContaining({ name: "MixedId", table: "Orders_Alias", schema: "AppSchema" })]);
expect(quotedCached).toEqual(quoted);
expect(store.lookupLocalCompletionColumns("oceanbase-oracle-1", "OBORCL", "orders_alias", "app", undefined, { tableQuoted: false, schemaQuoted: false })).toEqual(unquoted);
expect(store.lookupLocalCompletionColumns("oceanbase-oracle-1", "OBORCL", "Orders_Alias", "AppSchema", undefined, { tableQuoted: true, schemaQuoted: true })).toEqual(quoted);
});
it("normalizes unquoted SAP HANA table and schema names before column lookup", async () => {
const completionAssistantSearch = vi.fn(async (request: { parent_name?: string | null; parent_schema?: string | null }) => ({
candidates: [
+2 -2
View File
@@ -6634,7 +6634,7 @@ export const useConnectionStore = defineStore("connection", () => {
function completionColumnsKey(connectionId: string, database: string, table: string, schema?: string, catalog?: string, context?: { tableQuoted?: boolean; schemaQuoted?: boolean }): string {
const databaseType = getConfig(connectionId)?.db_type;
if (databaseType === "oracle") {
if (databaseType === "oracle" || databaseType === "oceanbase-oracle") {
const normalizedTable = context?.tableQuoted === false ? table.toUpperCase() : table;
const normalizedSchema = schema && context?.schemaQuoted === false ? schema.toUpperCase() : (schema ?? "");
return `${connectionId}:${database}:${catalog ?? ""}:${normalizedSchema}:${normalizedTable}`;
@@ -7736,7 +7736,7 @@ export const useConnectionStore = defineStore("connection", () => {
async function listCompletionColumns(connectionId: string, database: string, table: string, schema?: string, context?: { clientSessionId?: string; version?: number; tableQuoted?: boolean; schemaQuoted?: boolean }, catalog?: string): Promise<SqlCompletionColumn[]> {
const config = getConfig(connectionId);
const oracleIdentifier = config?.db_type === "oracle";
const oracleIdentifier = config?.db_type === "oracle" || config?.db_type === "oceanbase-oracle";
const uppercaseUnquotedIdentifier = oracleIdentifier || config?.db_type === "saphana";
const completionTable = uppercaseUnquotedIdentifier && context?.tableQuoted === false ? table.toUpperCase() : table;
const rawCompletionSchema = schema?.trim() || (config?.db_type === "dameng" ? config.username?.trim() || undefined : undefined);