fix(oracle): refresh table hover metadata from local index
Closes #8056
This commit is contained in:
4 files changed
+109
-10
No files matched your search
@@ -2896,15 +2896,14 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number)
|
||||
|
||||
try {
|
||||
let hoverTables = cachedTables.filter((table) => hoverTableMatchesScope(table, hoverScope));
|
||||
if (hoverTables.length === 0) {
|
||||
const loadedTables = usesLocalOnlyCompletionMetadata()
|
||||
? connectionStore.lookupLocalCompletionTables(props.connectionId, hoverScope.database, tableLookupName, MAX_COMPLETION_TABLES, hoverScope.schema, hoverScope.catalog)
|
||||
: await connectionStore.listCompletionTables(props.connectionId, hoverScope.database, tableLookupName, MAX_COMPLETION_TABLES, hoverScope.schema, false, hoverScope.schema, hoverScope.catalog);
|
||||
hoverTables = scopeHoverTables(loadedTables, hoverScope);
|
||||
cachedTables = mergeCompletionTables(cachedTables, hoverTables);
|
||||
}
|
||||
|
||||
let table = matchTable(qualifiedTableLookup, hoverTables) ?? matchTable(tableLookupName, hoverTables) ?? matchTable(identifier, hoverTables) ?? matchTable(name, hoverTables);
|
||||
if (!table) {
|
||||
const localTables = connectionStore.lookupLocalCompletionTables(props.connectionId, hoverScope.database, tableLookupName, MAX_COMPLETION_TABLES, hoverScope.schema, hoverScope.catalog);
|
||||
const localHoverTables = scopeHoverTables(localTables, hoverScope);
|
||||
hoverTables = mergeCompletionTables(localHoverTables, hoverTables);
|
||||
cachedTables = mergeCompletionTables(localHoverTables, cachedTables);
|
||||
table = matchTable(qualifiedTableLookup, hoverTables) ?? matchTable(tableLookupName, hoverTables) ?? matchTable(identifier, hoverTables) ?? matchTable(name, hoverTables);
|
||||
}
|
||||
if (!table && !usesLocalOnlyCompletionMetadata()) {
|
||||
const loadedTables = await connectionStore.listCompletionTables(props.connectionId, hoverScope.database, tableLookupName, MAX_COMPLETION_TABLES, hoverScope.schema, false, hoverScope.schema, hoverScope.catalog);
|
||||
const remoteHoverTables = scopeHoverTables(loadedTables, hoverScope);
|
||||
|
||||
@@ -4,6 +4,21 @@ import { describe, expect, it } from "vitest";
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("QueryEditor hover DDL copy", () => {
|
||||
it("reconciles the local sidebar index before remote hover fallback", () => {
|
||||
const hoverStart = queryEditorSource.indexOf("async function resolveSqlHoverTooltip");
|
||||
const hoverEnd = queryEditorSource.indexOf("\n}\n\nfunction sqlErrorDecorationRange", hoverStart);
|
||||
const hoverSource = queryEditorSource.slice(hoverStart, hoverEnd);
|
||||
const localLookup = hoverSource.indexOf("connectionStore.lookupLocalCompletionTables");
|
||||
const remoteLookup = hoverSource.indexOf("connectionStore.listCompletionTables");
|
||||
|
||||
expect(hoverStart).toBeGreaterThanOrEqual(0);
|
||||
expect(hoverEnd).toBeGreaterThan(hoverStart);
|
||||
expect(localLookup).toBeGreaterThanOrEqual(0);
|
||||
expect(remoteLookup).toBeGreaterThan(localLookup);
|
||||
expect(hoverSource).toContain("hoverTables = mergeCompletionTables(localHoverTables, hoverTables);");
|
||||
expect(hoverSource).toContain("cachedTables = mergeCompletionTables(localHoverTables, cachedTables);");
|
||||
});
|
||||
|
||||
it("gates only the table DDL branch on the preference, keeping column hover", () => {
|
||||
expect(queryEditorSource).not.toContain("if (!settingsStore.editorSettings.showTableDdlHoverPreview) return null;");
|
||||
expect(queryEditorSource).toContain("if (table && settingsStore.editorSettings.showTableDdlHoverPreview && !semanticQualifierIsRowSource");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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";
|
||||
import type { ConnectionConfig, TreeNode } from "@/types/database";
|
||||
|
||||
function installLocalStorage() {
|
||||
const data = new Map<string, string>();
|
||||
@@ -471,6 +471,85 @@ describe("connectionStore completion assistant", () => {
|
||||
expect(store.lookupLocalCompletionTables("oracle-1", "ORCL", "", 20, "scott")).toEqual(tables);
|
||||
});
|
||||
|
||||
it("reconciles a non-empty filtered cache with a table added to the sidebar tree", async () => {
|
||||
const completionAssistantSearch = vi.fn().mockResolvedValue({
|
||||
candidates: [{ name: "REPORT_0001", kind: "table", schema: "APP" }],
|
||||
incomplete: false,
|
||||
fallback_used: false,
|
||||
});
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
completionAssistantSearch,
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.connections = [oracleConnection()];
|
||||
store.connectedIds.add("oracle-1");
|
||||
|
||||
const first = await store.listCompletionTables("oracle-1", "ORCL", "REPORT", 200, "APP");
|
||||
const assistantCallsBeforeSidebarLoad = completionAssistantSearch.mock.calls.length;
|
||||
store.treeNodes = [
|
||||
{
|
||||
id: "oracle-1:ORCL:APP:tables",
|
||||
label: "Tables",
|
||||
type: "group-tables",
|
||||
connectionId: "oracle-1",
|
||||
database: "ORCL",
|
||||
schema: "APP",
|
||||
children: [
|
||||
{ id: "report-0001", label: "REPORT_0001", type: "table", connectionId: "oracle-1", database: "ORCL", schema: "APP" },
|
||||
{ id: "report-1001", label: "REPORT_1001", type: "table", connectionId: "oracle-1", database: "ORCL", schema: "APP" },
|
||||
{ id: "other-schema", label: "REPORT_1001", type: "table", connectionId: "oracle-1", database: "ORCL", schema: "OTHER" },
|
||||
],
|
||||
},
|
||||
] as TreeNode[];
|
||||
|
||||
const merged = await store.listCompletionTables("oracle-1", "ORCL", "REPORT", 200, "APP");
|
||||
|
||||
expect(first).toEqual([expect.objectContaining({ name: "REPORT_0001", schema: "APP" })]);
|
||||
expect(merged.map((table) => table.name)).toEqual(["REPORT_0001", "REPORT_1001"]);
|
||||
expect(merged.every((table) => table.schema === "APP")).toBe(true);
|
||||
expect(completionAssistantSearch).toHaveBeenCalledTimes(assistantCallsBeforeSidebarLoad);
|
||||
});
|
||||
|
||||
it("reconciles an empty filtered cache after the sidebar loads a late table", async () => {
|
||||
const completionAssistantSearch = vi.fn().mockResolvedValue({ candidates: [], incomplete: false, fallback_used: false });
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
completionAssistantSearch,
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.connections = [oracleConnection()];
|
||||
store.connectedIds.add("oracle-1");
|
||||
|
||||
const first = await store.listCompletionTables("oracle-1", "ORCL", "REPORT_1001", 200, "APP");
|
||||
const assistantCallsBeforeSidebarLoad = completionAssistantSearch.mock.calls.length;
|
||||
store.treeNodes = [
|
||||
{
|
||||
id: "oracle-1:ORCL:APP:tables",
|
||||
label: "Tables",
|
||||
type: "group-tables",
|
||||
connectionId: "oracle-1",
|
||||
database: "ORCL",
|
||||
schema: "APP",
|
||||
children: [{ id: "report-1001", label: "REPORT_1001", type: "table", connectionId: "oracle-1", database: "ORCL", schema: "APP" }],
|
||||
},
|
||||
] as TreeNode[];
|
||||
|
||||
const merged = await store.listCompletionTables("oracle-1", "ORCL", "REPORT_1001", 200, "APP");
|
||||
|
||||
expect(first).toEqual([]);
|
||||
expect(merged).toEqual([expect.objectContaining({ name: "REPORT_1001", schema: "APP", type: "table" })]);
|
||||
expect(completionAssistantSearch).toHaveBeenCalledTimes(assistantCallsBeforeSidebarLoad);
|
||||
});
|
||||
|
||||
it("maps global Oracle tables with safe qualification and schema priority", async () => {
|
||||
const completionAssistantSearch = vi.fn().mockResolvedValue({
|
||||
candidates: [
|
||||
|
||||
@@ -7711,7 +7711,13 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
// case-insensitive because tableMatchScore normalizes internally.
|
||||
const relaxedFilter = relaxedCompletionTableFilter(trimmedFilter);
|
||||
const cacheKey = `${connectionId}:${database}:${catalog ?? ""}:${trimmedFilter}:${limit ?? ""}:${schema ?? ""}:${globalSearch ? "global" : "scoped"}:${currentSchema ?? ""}`;
|
||||
if (completionTablesCache.value[cacheKey]) {
|
||||
const cachedTables = completionTablesCache.value[cacheKey];
|
||||
if (cachedTables) {
|
||||
const localTables = lookupLocalCompletionTables(connectionId, database, trimmedFilter, limit, schema, catalog);
|
||||
if (localTables.length === 0) return cachedTables;
|
||||
const mergedTables = dedupeCompletionTables([...localTables, ...cachedTables]);
|
||||
completionTablesCache.value[cacheKey] = limit ? mergedTables.slice(0, limit) : mergedTables;
|
||||
indexCompletionTables(connectionId, database, schema, completionTablesCache.value[cacheKey], catalog);
|
||||
return completionTablesCache.value[cacheKey];
|
||||
}
|
||||
const requestRevision = completionCacheRevision(connectionId, database);
|
||||
|
||||
Reference in new issue
Block a user