mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
fix(editor): verify current-schema tables in semantic diagnosis
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+7
-3
@@ -7563,15 +7563,19 @@ test("Spark query execution applies the selected database as schema context", as
|
||||
}
|
||||
});
|
||||
|
||||
test("Kingbase query execution sends the selected schema context", async () => {
|
||||
test.each([
|
||||
{ label: "PostgreSQL", connection: conn("postgres-1"), connectionId: "postgres-1" },
|
||||
{ label: "GaussDB", connection: clearableQuerySchemaConn("gaussdb-1", "gaussdb"), connectionId: "gaussdb-1" },
|
||||
{ label: "Kingbase", connection: kingbaseConn("kingbase-1"), connectionId: "kingbase-1" },
|
||||
])("$label query execution sends the selected schema context", async ({ connection, connectionId }) => {
|
||||
const restoreStorage = installMemoryStorage();
|
||||
setActivePinia(createPinia());
|
||||
const connectionStore = useConnectionStore();
|
||||
const store = useQueryStore();
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
connectionStore.addEphemeralConnection(kingbaseConn("kingbase-1"));
|
||||
const tabId = store.createTab("kingbase-1", "qinzhou", "Query", "query", "sdy_smartsite");
|
||||
connectionStore.addEphemeralConnection(connection);
|
||||
const tabId = store.createTab(connectionId, "qinzhou", "Query", "query", "sdy_smartsite");
|
||||
let executeBody: any;
|
||||
|
||||
globalThis.fetch = withConnectionHealthMock(async (input, init) => {
|
||||
|
||||
Reference in New Issue
Block a user