fix(editor): prevent stale completion metadata after refresh
This commit is contained in:
4 files changed
+275
-37
No files matched your search
@@ -5522,6 +5522,16 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => connectionStore.completionCacheRevision(props.connectionId, props.database),
|
||||
() => {
|
||||
completionEpoch++;
|
||||
refreshCompletionCache();
|
||||
setSemanticDiagnostics([]);
|
||||
scheduleSemanticDiagnostics();
|
||||
},
|
||||
);
|
||||
|
||||
watch([() => props.clientSessionId, () => props.completionContextVersion], () => {
|
||||
completionEpoch++;
|
||||
refreshCompletionCache();
|
||||
|
||||
@@ -682,6 +682,144 @@ describe("connectionStore completion assistant", () => {
|
||||
expect(getColumns.mock.calls.map((call) => call[3])).toEqual(["users", "orders", "users"]);
|
||||
});
|
||||
|
||||
it("does not let an invalidated column request overwrite fresh metadata", async () => {
|
||||
const staleColumns = deferred<unknown[]>();
|
||||
const freshColumns = deferred<unknown[]>();
|
||||
const getColumns = vi.fn().mockReturnValueOnce(staleColumns.promise).mockReturnValueOnce(freshColumns.promise);
|
||||
const column = (name: string) => ({
|
||||
name,
|
||||
data_type: "integer",
|
||||
is_nullable: false,
|
||||
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: vi.fn().mockRejectedValue(new Error("assistant unavailable")),
|
||||
getColumns,
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.connections = [sqlServerConnection()];
|
||||
store.connectedIds.add("sqlserver-1");
|
||||
|
||||
const staleRequest = store.listCompletionColumns("sqlserver-1", "app", "users", "dbo");
|
||||
await vi.waitFor(() => expect(getColumns).toHaveBeenCalledTimes(1));
|
||||
store.invalidateCompletionTableCache("sqlserver-1", "app", "users", "dbo");
|
||||
const freshRequest = store.listCompletionColumns("sqlserver-1", "app", "users", "dbo");
|
||||
freshColumns.resolve([column("fresh_column")]);
|
||||
await expect(freshRequest).resolves.toEqual([expect.objectContaining({ name: "fresh_column" })]);
|
||||
|
||||
staleColumns.resolve([column("stale_column")]);
|
||||
await expect(staleRequest).resolves.toEqual([expect.objectContaining({ name: "fresh_column" })]);
|
||||
await expect(store.listCompletionColumns("sqlserver-1", "app", "users", "dbo")).resolves.toEqual([expect.objectContaining({ name: "fresh_column" })]);
|
||||
expect(getColumns).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("does not reuse or index a stale assistant column response after invalidation", async () => {
|
||||
const staleResponse = deferred<any>();
|
||||
const freshResponse = deferred<any>();
|
||||
const completionAssistantSearch = vi.fn().mockReturnValueOnce(staleResponse.promise).mockReturnValueOnce(freshResponse.promise);
|
||||
const response = (name: string) => ({
|
||||
candidates: [{ name, kind: "column", parent_schema: "dbo", parent_name: "users", data_type: "integer" }],
|
||||
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 = [sqlServerConnection()];
|
||||
store.connectedIds.add("sqlserver-1");
|
||||
|
||||
const staleRequest = store.listCompletionColumns("sqlserver-1", "app", "users", "dbo");
|
||||
await vi.waitFor(() => expect(completionAssistantSearch).toHaveBeenCalledTimes(1));
|
||||
store.invalidateCompletionTableCache("sqlserver-1", "app", "users", "dbo");
|
||||
const freshRequest = store.listCompletionColumns("sqlserver-1", "app", "users", "dbo");
|
||||
await vi.waitFor(() => expect(completionAssistantSearch).toHaveBeenCalledTimes(2));
|
||||
|
||||
freshResponse.resolve(response("fresh_column"));
|
||||
await expect(freshRequest).resolves.toEqual([expect.objectContaining({ name: "fresh_column" })]);
|
||||
|
||||
staleResponse.resolve(response("stale_column"));
|
||||
await expect(staleRequest).resolves.toEqual([expect.objectContaining({ name: "fresh_column" })]);
|
||||
expect(store.lookupLocalCompletionColumns("sqlserver-1", "app", "users", "dbo")).toEqual([expect.objectContaining({ name: "fresh_column" })]);
|
||||
});
|
||||
|
||||
it("does not let an invalidated table request overwrite fresh metadata", async () => {
|
||||
const staleTables = deferred<any[]>();
|
||||
const freshTables = deferred<any[]>();
|
||||
const listTables = vi.fn().mockReturnValueOnce(staleTables.promise).mockReturnValueOnce(freshTables.promise);
|
||||
const table = (name: string) => ({ name, table_type: "BASE TABLE", comment: null });
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
listTables,
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.connections = [postgresConnection()];
|
||||
store.connectedIds.add("pg-1");
|
||||
|
||||
const staleRequest = store.listCompletionTables("pg-1", "app", "", undefined, "public");
|
||||
await vi.waitFor(() => expect(listTables).toHaveBeenCalledTimes(1));
|
||||
store.invalidateCompletionCache("pg-1", "app");
|
||||
const freshRequest = store.listCompletionTables("pg-1", "app", "", undefined, "public");
|
||||
await vi.waitFor(() => expect(listTables).toHaveBeenCalledTimes(2));
|
||||
|
||||
freshTables.resolve([table("fresh_table")]);
|
||||
await expect(freshRequest).resolves.toEqual([expect.objectContaining({ name: "fresh_table" })]);
|
||||
|
||||
staleTables.resolve([table("stale_table")]);
|
||||
await expect(staleRequest).resolves.toEqual([expect.objectContaining({ name: "fresh_table" })]);
|
||||
await expect(store.listCompletionTables("pg-1", "app", "", undefined, "public")).resolves.toEqual([expect.objectContaining({ name: "fresh_table" })]);
|
||||
expect(listTables).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("does not let an invalidated foreign key request overwrite fresh metadata", async () => {
|
||||
const staleForeignKeys = deferred<any[]>();
|
||||
const freshForeignKeys = deferred<any[]>();
|
||||
const listForeignKeys = vi.fn().mockReturnValueOnce(staleForeignKeys.promise).mockReturnValueOnce(freshForeignKeys.promise);
|
||||
const foreignKey = (name: string) => ({ name, column: "customer_id", ref_schema: "public", ref_table: "customers", ref_column: "id" });
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
listForeignKeys,
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.connections = [postgresConnection()];
|
||||
store.connectedIds.add("pg-1");
|
||||
|
||||
const staleRequest = store.listCompletionForeignKeys("pg-1", "app", "orders", "public");
|
||||
await vi.waitFor(() => expect(listForeignKeys).toHaveBeenCalledTimes(1));
|
||||
store.invalidateCompletionTableCache("pg-1", "app", "orders", "public");
|
||||
const freshRequest = store.listCompletionForeignKeys("pg-1", "app", "orders", "public");
|
||||
await vi.waitFor(() => expect(listForeignKeys).toHaveBeenCalledTimes(2));
|
||||
|
||||
freshForeignKeys.resolve([foreignKey("fresh_fk")]);
|
||||
await expect(freshRequest).resolves.toEqual([expect.objectContaining({ name: "fresh_fk" })]);
|
||||
|
||||
staleForeignKeys.resolve([foreignKey("stale_fk")]);
|
||||
await expect(staleRequest).resolves.toEqual([expect.objectContaining({ name: "fresh_fk" })]);
|
||||
await expect(store.listCompletionForeignKeys("pg-1", "app", "orders", "public")).resolves.toEqual([expect.objectContaining({ name: "fresh_fk" })]);
|
||||
expect(listForeignKeys).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("keeps the same table cached in other catalogs", async () => {
|
||||
const getColumns = vi.fn(async (_connectionId: string, _database: string, _schema: string, table: string, catalog?: string) => [
|
||||
{
|
||||
|
||||
@@ -51,11 +51,27 @@ function objectGroup(type: "group-views" | "group-procedures", key: string, chil
|
||||
};
|
||||
}
|
||||
|
||||
function installApiMocks(options?: { listTables?: ReturnType<typeof vi.fn>; listObjects?: ReturnType<typeof vi.fn> }) {
|
||||
function column(name: string) {
|
||||
return {
|
||||
name,
|
||||
data_type: "VARCHAR",
|
||||
is_nullable: true,
|
||||
column_default: null,
|
||||
is_primary_key: false,
|
||||
extra: null,
|
||||
comment: null,
|
||||
numeric_precision: null,
|
||||
numeric_scale: null,
|
||||
character_maximum_length: 255,
|
||||
};
|
||||
}
|
||||
|
||||
function installApiMocks(options?: { getColumns?: ReturnType<typeof vi.fn>; listTables?: ReturnType<typeof vi.fn>; listObjects?: ReturnType<typeof vi.fn> }) {
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined),
|
||||
getColumns: options?.getColumns ?? vi.fn().mockResolvedValue([]),
|
||||
listInstalledAgents: vi.fn().mockResolvedValue([]),
|
||||
listObjects: options?.listObjects ?? vi.fn().mockResolvedValue([]),
|
||||
listTables: options?.listTables ?? vi.fn().mockResolvedValue([]),
|
||||
@@ -131,4 +147,23 @@ describe("connectionStore tree refresh state", () => {
|
||||
expect(store.isTreeNodeChildrenLoaded(viewsGroupId)).toBe(false);
|
||||
expect(store.isTreeNodeChildrenLoaded(proceduresGroupId)).toBe(false);
|
||||
});
|
||||
|
||||
it("reloads completion columns after refreshing their schema tree", async () => {
|
||||
const getColumns = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce([column("id")])
|
||||
.mockResolvedValueOnce([column("id"), column("new_column")]);
|
||||
const listTables = vi.fn().mockResolvedValue([{ name: "users", table_type: "BASE TABLE", comment: null }]);
|
||||
installApiMocks({ getColumns, listTables });
|
||||
const root = schemaNode([]);
|
||||
const store = await createStore(root);
|
||||
|
||||
await expect(store.listCompletionColumns("pg-1", "app", "users", "public")).resolves.toEqual([expect.objectContaining({ name: "id" })]);
|
||||
|
||||
await store.refreshTreeNode(root);
|
||||
|
||||
await expect(store.listCompletionColumns("pg-1", "app", "users", "public")).resolves.toEqual([expect.objectContaining({ name: "id" }), expect.objectContaining({ name: "new_column" })]);
|
||||
expect(getColumns).toHaveBeenCalledTimes(2);
|
||||
expect(store.completionCacheRevision("pg-1", "app")).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -461,6 +461,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const completionColumnIndex = new Map<string, { touched: number; columns: SqlCompletionColumn[] }>();
|
||||
const completionForeignKeyIndex = new Map<string, { touched: number; foreignKeys: SqlCompletionForeignKey[] }>();
|
||||
const completionInFlight = new Map<string, Promise<unknown>>();
|
||||
const completionCacheRevisions = ref<Record<string, number>>({});
|
||||
const completionMetadataLimiter = new MetadataTaskLimiter(COMPLETION_METADATA_CONCURRENCY, (event) => {
|
||||
console.debug("[DBX][completion-metadata:limit]", event);
|
||||
});
|
||||
@@ -2841,7 +2842,20 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return pastedCount;
|
||||
}
|
||||
|
||||
function completionCacheRevision(connectionId?: string, database?: string): number {
|
||||
if (!connectionId) return 0;
|
||||
const connectionRevision = completionCacheRevisions.value[connectionId] ?? 0;
|
||||
if (database == null) return connectionRevision;
|
||||
return connectionRevision + (completionCacheRevisions.value[`${connectionId}:${database}`] ?? 0);
|
||||
}
|
||||
|
||||
function bumpCompletionCacheRevision(connectionId: string, database?: string) {
|
||||
const key = database == null ? connectionId : `${connectionId}:${database}`;
|
||||
completionCacheRevisions.value[key] = (completionCacheRevisions.value[key] ?? 0) + 1;
|
||||
}
|
||||
|
||||
function invalidateCompletionCache(connectionId: string, database?: string) {
|
||||
bumpCompletionCacheRevision(connectionId, database);
|
||||
invalidateMetadataCaches({ connectionId, database });
|
||||
if (database == null) delete completionDatabasesCache.value[connectionId];
|
||||
const cachePrefix = database == null ? `${connectionId}:` : `${connectionId}:${database}:`;
|
||||
@@ -6208,6 +6222,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
|
||||
async function refreshTreeNode(node: TreeNode) {
|
||||
invalidateCompletionCachesForNode(node);
|
||||
invalidateMetadataCachesForNode(node);
|
||||
if (objectTypesForGroupNode(node.type)) {
|
||||
clearLoadedChildrenCache(node.id, { deletePersisted: false });
|
||||
@@ -6378,6 +6393,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
|
||||
function invalidateCompletionTableCache(connectionId: string, database: string, tableName: string, schema?: string, catalog?: string): number {
|
||||
bumpCompletionCacheRevision(connectionId, database);
|
||||
const matches = (key: string) => completionTableCacheKeyMatches(key, connectionId, database, tableName, schema, catalog);
|
||||
let removed = 0;
|
||||
for (const cache of [completionColumnsCache.value, completionForeignKeysCache.value]) {
|
||||
@@ -6397,6 +6413,19 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return removed;
|
||||
}
|
||||
|
||||
function invalidateCompletionCachesForNode(node: TreeNode) {
|
||||
if (!node.connectionId) return;
|
||||
if (typeof node.database !== "string") {
|
||||
invalidateCompletionCache(node.connectionId);
|
||||
return;
|
||||
}
|
||||
if (node.tableName) {
|
||||
invalidateCompletionTableCache(node.connectionId, node.database, node.tableName, node.schema, node.catalog);
|
||||
return;
|
||||
}
|
||||
invalidateCompletionCache(node.connectionId, node.database);
|
||||
}
|
||||
|
||||
function touchCompletionIndex<T>(index: Map<string, { touched: number } & T>, key: string, value: T, max = COMPLETION_CACHE_MAX) {
|
||||
index.set(key, { ...value, touched: Date.now() });
|
||||
if (index.size <= max) return;
|
||||
@@ -6437,8 +6466,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
});
|
||||
}
|
||||
|
||||
async function completionAssistantSearch(request: CompletionAssistantRequest) {
|
||||
return withCompletionInFlight(`assistant:${completionAssistantRequestKey(request)}`, async () => {
|
||||
async function completionAssistantSearch(request: CompletionAssistantRequest, requestRevision = completionCacheRevision(request.connection_id, request.database)) {
|
||||
return withCompletionInFlight(`${request.connection_id}:${request.database}:assistant:${requestRevision}:${completionAssistantRequestKey(request)}`, async () => {
|
||||
await ensureConnected(request.connection_id);
|
||||
return api.completionAssistantSearch(request);
|
||||
});
|
||||
@@ -6668,23 +6697,26 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}));
|
||||
}
|
||||
|
||||
async function listCompletionAssistantTables(connectionId: string, database: string, filter: string, limit?: number, schema?: string, globalSearch = false, currentSchema?: string): Promise<SqlCompletionTable[]> {
|
||||
async function listCompletionAssistantTables(connectionId: string, database: string, filter: string, limit?: number, schema?: string, globalSearch = false, currentSchema?: string, requestRevision = completionCacheRevision(connectionId, database)): Promise<SqlCompletionTable[]> {
|
||||
const oracleAssistant = getConfig(connectionId)?.db_type === "oracle";
|
||||
const preferredSchema = oracleAssistant ? completionPreferredSchema(connectionId, globalSearch ? currentSchema : (schema ?? currentSchema)) : schema?.trim() || undefined;
|
||||
const objectKinds: CompletionAssistantObjectKind[] = ["table", "view"];
|
||||
const response = await completionAssistantSearch({
|
||||
connection_id: connectionId,
|
||||
database,
|
||||
schema: preferredSchema ?? null,
|
||||
object_kinds: objectKinds,
|
||||
mask: filter.trim(),
|
||||
max_results: limit ?? 200,
|
||||
global_search: globalSearch,
|
||||
parent_schema: globalSearch ? null : (schema ?? null),
|
||||
match_mode: "prefix",
|
||||
});
|
||||
const response = await completionAssistantSearch(
|
||||
{
|
||||
connection_id: connectionId,
|
||||
database,
|
||||
schema: preferredSchema ?? null,
|
||||
object_kinds: objectKinds,
|
||||
mask: filter.trim(),
|
||||
max_results: limit ?? 200,
|
||||
global_search: globalSearch,
|
||||
parent_schema: globalSearch ? null : (schema ?? null),
|
||||
match_mode: "prefix",
|
||||
},
|
||||
requestRevision,
|
||||
);
|
||||
const tables = completionAssistantTables(response.candidates, preferredSchema, oracleAssistant);
|
||||
indexCompletionTables(connectionId, database, schema, tables);
|
||||
if (requestRevision === completionCacheRevision(connectionId, database)) indexCompletionTables(connectionId, database, schema, tables);
|
||||
return tables;
|
||||
}
|
||||
|
||||
@@ -6726,20 +6758,23 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return objects;
|
||||
}
|
||||
|
||||
async function listCompletionAssistantColumns(connectionId: string, database: string, table: string, schema?: string, context?: { tableQuoted?: boolean; schemaQuoted?: boolean }): Promise<SqlCompletionColumn[]> {
|
||||
const response = await completionAssistantSearch({
|
||||
connection_id: connectionId,
|
||||
database,
|
||||
schema: schema ?? null,
|
||||
object_kinds: ["column"],
|
||||
mask: "",
|
||||
max_results: 500,
|
||||
parent_schema: schema ?? null,
|
||||
parent_name: table,
|
||||
match_mode: "prefix",
|
||||
});
|
||||
async function listCompletionAssistantColumns(connectionId: string, database: string, table: string, schema?: string, context?: { tableQuoted?: boolean; schemaQuoted?: boolean }, requestRevision = completionCacheRevision(connectionId, database)): Promise<SqlCompletionColumn[]> {
|
||||
const response = await completionAssistantSearch(
|
||||
{
|
||||
connection_id: connectionId,
|
||||
database,
|
||||
schema: schema ?? null,
|
||||
object_kinds: ["column"],
|
||||
mask: "",
|
||||
max_results: 500,
|
||||
parent_schema: schema ?? null,
|
||||
parent_name: table,
|
||||
match_mode: "prefix",
|
||||
},
|
||||
requestRevision,
|
||||
);
|
||||
const columns = completionAssistantColumns(response.candidates, table, schema, context);
|
||||
if (columns.length > 0) indexCompletionColumns(connectionId, database, table, schema, columns);
|
||||
if (columns.length > 0 && requestRevision === completionCacheRevision(connectionId, database)) indexCompletionColumns(connectionId, database, table, schema, columns);
|
||||
return columns;
|
||||
}
|
||||
|
||||
@@ -7108,6 +7143,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (completionTablesCache.value[cacheKey]) {
|
||||
return completionTablesCache.value[cacheKey];
|
||||
}
|
||||
const requestRevision = completionCacheRevision(connectionId, database);
|
||||
|
||||
return withCompletionInFlight(
|
||||
`${cacheKey}:tables`,
|
||||
@@ -7118,7 +7154,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (normalizedFilter || limit) {
|
||||
let results: SqlCompletionTable[] = [];
|
||||
try {
|
||||
results = await listCompletionAssistantTables(connectionId, database, trimmedFilter, limit, schema, globalSearch, currentSchema);
|
||||
results = await listCompletionAssistantTables(connectionId, database, trimmedFilter, limit, schema, globalSearch, currentSchema, requestRevision);
|
||||
} catch {
|
||||
if (schema) {
|
||||
const tables = await listCompletionTableMetadata(connectionId, database, schema, trimmedFilter, limit, catalog);
|
||||
@@ -7136,7 +7172,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (results.length === 0 && relaxedFilter) {
|
||||
if (globalSearch) {
|
||||
try {
|
||||
results = await listCompletionAssistantTables(connectionId, database, relaxedFilter, expandedCompletionLimit(limit), schema, true, currentSchema);
|
||||
results = await listCompletionAssistantTables(connectionId, database, relaxedFilter, expandedCompletionLimit(limit), schema, true, currentSchema, requestRevision);
|
||||
} catch {
|
||||
results = [];
|
||||
}
|
||||
@@ -7158,15 +7194,17 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
const limitedTables = limit ? dedupeCompletionTables(results).slice(0, limit) : results;
|
||||
if (requestRevision !== completionCacheRevision(connectionId, database)) return listCompletionTables(connectionId, database, filter, limit, schema, globalSearch, currentSchema, catalog, options);
|
||||
completionTablesCache.value[cacheKey] = limitedTables;
|
||||
indexCompletionTables(connectionId, database, undefined, limitedTables, catalog);
|
||||
evictOldestCacheEntries(completionTablesCache.value, COMPLETION_CACHE_MAX);
|
||||
return completionTablesCache.value[cacheKey];
|
||||
}
|
||||
|
||||
let scopedTables: SqlCompletionTable[];
|
||||
if (schema) {
|
||||
const tables = await listCompletionTableMetadata(connectionId, database, schema, undefined, undefined, catalog);
|
||||
completionTablesCache.value[cacheKey] = tables.map((table) => ({
|
||||
scopedTables = tables.map((table) => ({
|
||||
name: table.name,
|
||||
catalog,
|
||||
schema,
|
||||
@@ -7174,8 +7212,10 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
...completionStableTableType(table.table_type),
|
||||
}));
|
||||
} else {
|
||||
completionTablesCache.value[cacheKey] = lookupLocalCompletionTables(connectionId, database, normalizedFilter, limit, undefined, catalog);
|
||||
scopedTables = lookupLocalCompletionTables(connectionId, database, normalizedFilter, limit, undefined, catalog);
|
||||
}
|
||||
if (requestRevision !== completionCacheRevision(connectionId, database)) return listCompletionTables(connectionId, database, filter, limit, schema, globalSearch, currentSchema, catalog, options);
|
||||
completionTablesCache.value[cacheKey] = scopedTables;
|
||||
indexCompletionTables(connectionId, database, undefined, completionTablesCache.value[cacheKey], catalog);
|
||||
evictOldestCacheEntries(completionTablesCache.value, COMPLETION_CACHE_MAX);
|
||||
return completionTablesCache.value[cacheKey];
|
||||
@@ -7186,6 +7226,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (tables.length === 0 && relaxedFilter) {
|
||||
tables = await listCompletionTableMetadata(connectionId, database, querySchema, relaxedFilter, expandedCompletionLimit(limit), catalog);
|
||||
}
|
||||
if (requestRevision !== completionCacheRevision(connectionId, database)) return listCompletionTables(connectionId, database, filter, limit, schema, globalSearch, currentSchema, catalog, options);
|
||||
completionTablesCache.value[cacheKey] = tables.map((table) => ({
|
||||
name: table.name,
|
||||
catalog,
|
||||
@@ -7363,15 +7404,16 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const sessionCacheScope = usesOracleCurrentSchema && context?.clientSessionId ? `:${context.clientSessionId}:${context.version ?? 0}` : "";
|
||||
const cacheKey = `${connectionId}:${database}:${catalog ?? ""}:${completionSchema || ""}:${completionTable}${sessionCacheScope}`;
|
||||
if (!completionColumnsCache.value[cacheKey]) {
|
||||
const requestRevision = completionCacheRevision(connectionId, database);
|
||||
await withCompletionInFlight(
|
||||
`${cacheKey}:columns`,
|
||||
async () => {
|
||||
await ensureConnected(connectionId);
|
||||
if (!usesOracleCurrentSchema && !catalog) {
|
||||
try {
|
||||
const assistantColumns = await listCompletionAssistantColumns(connectionId, database, completionTable, completionSchema, context);
|
||||
const assistantColumns = await listCompletionAssistantColumns(connectionId, database, completionTable, completionSchema, context, requestRevision);
|
||||
if (assistantColumns.length > 0) {
|
||||
completionColumnsCache.value[cacheKey] = assistantColumns.map((column) => ({
|
||||
const columns = assistantColumns.map((column) => ({
|
||||
name: column.name,
|
||||
data_type: column.dataType ?? "",
|
||||
is_nullable: column.isNullable ?? true,
|
||||
@@ -7383,6 +7425,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
numeric_scale: null,
|
||||
character_maximum_length: null,
|
||||
}));
|
||||
if (requestRevision !== completionCacheRevision(connectionId, database)) return;
|
||||
completionColumnsCache.value[cacheKey] = columns;
|
||||
evictOldestCacheEntries(completionColumnsCache.value, COMPLETION_CACHE_MAX);
|
||||
return;
|
||||
}
|
||||
@@ -7391,13 +7435,19 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
const querySchema = usesOracleCurrentSchema ? "" : metadataQuerySchema(connectionId, database, completionSchema);
|
||||
completionColumnsCache.value[cacheKey] = await api.getColumns(connectionId, database, querySchema, completionTable, catalog, usesOracleCurrentSchema ? context?.clientSessionId : undefined);
|
||||
const columns = await api.getColumns(connectionId, database, querySchema, completionTable, catalog, usesOracleCurrentSchema ? context?.clientSessionId : undefined);
|
||||
if (requestRevision !== completionCacheRevision(connectionId, database)) return;
|
||||
completionColumnsCache.value[cacheKey] = columns;
|
||||
evictOldestCacheEntries(completionColumnsCache.value, COMPLETION_CACHE_MAX);
|
||||
},
|
||||
{ scope: completionLimiterScope(connectionId, database), kind: "columns" },
|
||||
);
|
||||
}
|
||||
|
||||
if (!completionColumnsCache.value[cacheKey]) {
|
||||
return listCompletionColumns(connectionId, database, table, schema, context, catalog);
|
||||
}
|
||||
|
||||
const columns = completionColumnsCache.value[cacheKey].map((column) => ({
|
||||
name: column.name,
|
||||
table: completionTable,
|
||||
@@ -7419,17 +7469,21 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
|
||||
const cacheKey = `${connectionId}:${database}:${schema || ""}:${table}`;
|
||||
if (!completionForeignKeysCache.value[cacheKey]) {
|
||||
const requestRevision = completionCacheRevision(connectionId, database);
|
||||
await withCompletionInFlight(
|
||||
`${cacheKey}:fkeys`,
|
||||
async () => {
|
||||
await ensureConnected(connectionId);
|
||||
const querySchema = metadataQuerySchema(connectionId, database, schema);
|
||||
completionForeignKeysCache.value[cacheKey] = await api.listForeignKeys(connectionId, database, querySchema, table);
|
||||
const foreignKeys = await api.listForeignKeys(connectionId, database, querySchema, table);
|
||||
if (requestRevision !== completionCacheRevision(connectionId, database)) return;
|
||||
completionForeignKeysCache.value[cacheKey] = foreignKeys;
|
||||
evictOldestCacheEntries(completionForeignKeysCache.value, COMPLETION_CACHE_MAX);
|
||||
},
|
||||
{ scope: completionLimiterScope(connectionId, database), kind: "foreignKeys" },
|
||||
);
|
||||
}
|
||||
if (!completionForeignKeysCache.value[cacheKey]) return listCompletionForeignKeys(connectionId, database, table, schema);
|
||||
|
||||
const foreignKeys = sqlCompletionForeignKeys(completionForeignKeysCache.value[cacheKey]);
|
||||
indexCompletionForeignKeys(connectionId, database, table, schema, foreignKeys);
|
||||
@@ -8365,6 +8419,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
listMongoCompletionFields,
|
||||
invalidateCompletionCache,
|
||||
invalidateCompletionTableCache,
|
||||
completionCacheRevision,
|
||||
invalidateMetadataCache,
|
||||
exportConnectionsToFile,
|
||||
readImportFile,
|
||||
|
||||
Reference in new issue
Block a user