fix(editor): verify current-schema tables in semantic diagnosis

This commit is contained in:
zipg authored and GitHub committed 2026-09-09 16:05:43 +08:00
1 parent 3b46109d36
commit fd44331876
5 files changed
+53 -8

No files matched your search

@@ -2624,7 +2624,9 @@ async function findExactSemanticDiagnosticTable(table: SqlTableReference, scope?
return localExact;
}
const remoteMatches = await connectionStore.listCompletionTables(props.connectionId, target.database, table.name, MAX_COMPLETION_TABLES, target.schema, false, scope?.schema ?? props.schema, target.catalog);
const remoteMatches = await connectionStore.listCompletionTables(props.connectionId, target.database, table.name, MAX_COMPLETION_TABLES, target.schema, false, scope?.schema ?? props.schema, target.catalog, {
verifySchemaMetadata: !!target.schema,
});
cachedTables = mergeCompletionTables(cachedTables, remoteMatches);
return remoteMatches.find((item) => completionTablesMatch(item, table)) ?? null;
}
@@ -364,8 +364,8 @@ describe("query execution schema", () => {
expect(connectionQueryExecutionSchema({ db_type: dbType }, "ai_test", undefined, false)).toBe("ai_test");
});
it("prefers an explicit schema for PostgreSQL", () => {
expect(connectionQueryExecutionSchema({ db_type: "postgres" }, "app", "reporting", false)).toBe("reporting");
it.each(["postgres", "gaussdb", "opengauss"] as const)("prefers an explicit schema for %s", (dbType) => {
expect(connectionQueryExecutionSchema({ db_type: dbType }, "app", "reporting", false)).toBe("reporting");
});
it("prefers an explicit schema for Kingbase query execution", () => {
@@ -403,6 +403,34 @@ describe("connectionStore completion assistant", () => {
expect(tables).toEqual([{ name: "accounts", schema: "public", type: "table", detail: "→ Customer accounts" }]);
});
it("verifies selected-schema tables when the completion index is stale", async () => {
const completionAssistantSearch = vi.fn().mockResolvedValue({
candidates: [],
incomplete: false,
fallback_used: false,
});
const listTables = vi.fn().mockResolvedValue([{ name: "accounts", table_type: "BASE TABLE", comment: "Customer accounts" }]);
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
completionAssistantSearch,
listSchemas: vi.fn().mockResolvedValue(["reporting"]),
listTables,
}));
const { useConnectionStore } = await import("@/stores/connectionStore");
const store = useConnectionStore();
store.connections = [postgresConnection()];
store.connectedIds.add("pg-1");
const tables = await store.listCompletionTables("pg-1", "app", "accounts", 200, "reporting", false, "reporting", undefined, { verifySchemaMetadata: true });
expect(completionAssistantSearch).toHaveBeenCalledOnce();
expect(listTables).toHaveBeenCalledWith("pg-1", "app", "reporting", "accounts", 200);
expect(tables).toEqual([{ name: "accounts", schema: "reporting", type: "table", detail: "→ Customer accounts" }]);
});
it("keeps schema-qualified local table completion scoped to the selected schema", async () => {
const completionAssistantSearch = vi.fn().mockRejectedValue(new Error("assistant unavailable"));
const listTables = vi.fn(async (_connectionId: string, _database: string, schema: string, filter: string) => {
+13 -2
View File
@@ -7793,7 +7793,7 @@ export const useConnectionStore = defineStore("connection", () => {
return api.listTables(connectionId, database, schema, filter, limit);
}
async function listCompletionTables(connectionId: string, database: string, filter = "", limit?: number, schema?: string, globalSearch = false, currentSchema?: string, catalog?: string, options: { activateConnection?: boolean } = {}): Promise<SqlCompletionTable[]> {
async function listCompletionTables(connectionId: string, database: string, filter = "", limit?: number, schema?: string, globalSearch = false, currentSchema?: string, catalog?: string, options: { activateConnection?: boolean; verifySchemaMetadata?: boolean } = {}): Promise<SqlCompletionTable[]> {
const trimmedFilter = filter.trim();
const normalizedFilter = trimmedFilter.toLowerCase();
// Remote queries (Dameng/Oracle) are case-sensitive, so the cache key must
@@ -7801,7 +7801,7 @@ export const useConnectionStore = defineStore("connection", () => {
// second lookup returns the first's stale results. Local lookups below stay
// case-insensitive because tableMatchScore normalizes internally.
const relaxedFilter = relaxedCompletionTableFilter(trimmedFilter);
const cacheKey = `${connectionId}:${database}:${catalog ?? ""}:${trimmedFilter}:${limit ?? ""}:${schema ?? ""}:${globalSearch ? "global" : "scoped"}:${currentSchema ?? ""}`;
const cacheKey = `${connectionId}:${database}:${catalog ?? ""}:${trimmedFilter}:${limit ?? ""}:${schema ?? ""}:${globalSearch ? "global" : "scoped"}:${currentSchema ?? ""}:${options.verifySchemaMetadata ? "verified" : "indexed"}`;
const cachedTables = completionTablesCache.value[cacheKey];
if (cachedTables) {
const localTables = lookupLocalCompletionTables(connectionId, database, trimmedFilter, limit, schema, catalog);
@@ -7821,8 +7821,10 @@ export const useConnectionStore = defineStore("connection", () => {
if (isSchemaAwareDatabase(connectionId)) {
if (normalizedFilter || limit) {
let results: SqlCompletionTable[] = [];
let assistantCompleted = false;
try {
results = await listCompletionAssistantTables(connectionId, database, trimmedFilter, limit, schema, globalSearch, currentSchema, requestRevision);
assistantCompleted = true;
} catch {
if (schema) {
const tables = await listCompletionTableMetadata(connectionId, database, schema, trimmedFilter, limit, catalog);
@@ -7831,6 +7833,15 @@ export const useConnectionStore = defineStore("connection", () => {
results = lookupLocalCompletionTables(connectionId, database, normalizedFilter, limit, undefined, catalog);
}
}
if (assistantCompleted && schema && options.verifySchemaMetadata) {
try {
const tables = await listCompletionTableMetadata(connectionId, database, schema, trimmedFilter, limit, catalog);
results = dedupeCompletionTables([...tableInfosToCompletionTables(tables, schema, catalog), ...results]);
} catch {
// The completion index still provides a best-effort result when
// authoritative metadata is temporarily unavailable.
}
}
if (results.length === 0 && relaxedFilter) {
if (globalSearch) {
try {