feat(postgres): complete sequence names in regclass literals
Closes #4005
This commit is contained in:
1 parent
a67bcb09db
commit
91e7463ee5
11 files changed
+580
-24
No files matched your search
@@ -883,6 +883,86 @@ describe("connectionStore completion assistant", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("loads PostgreSQL sequences without falling back to routine metadata", async () => {
|
||||
const completionAssistantSearch = vi.fn().mockResolvedValue({
|
||||
candidates: [{ name: "OrderSequence", kind: "sequence", schema: "App" }],
|
||||
incomplete: false,
|
||||
fallback_used: false,
|
||||
});
|
||||
const listCompletionObjects = vi.fn().mockResolvedValue([{ name: "not_a_sequence", object_type: "FUNCTION" }]);
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
completionAssistantSearch,
|
||||
listCompletionObjects,
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.connections = [postgresConnection()];
|
||||
store.connectedIds.add("pg-1");
|
||||
|
||||
const objects = await store.listCompletionObjects("pg-1", "app", "Order", 20, "App", undefined, false, undefined, ["sequence"], true);
|
||||
await store.listCompletionObjects("pg-1", "app", "Order", 20, undefined, undefined, false, undefined, ["sequence"]);
|
||||
await store.listCompletionObjects("pg-1", "app", "order", 20, "App", undefined, false, undefined, ["sequence"], true);
|
||||
|
||||
expect(completionAssistantSearch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
object_kinds: ["sequence"],
|
||||
mask: "Order",
|
||||
case_sensitive: true,
|
||||
schema: "App",
|
||||
parent_schema: null,
|
||||
}),
|
||||
);
|
||||
expect(completionAssistantSearch).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
object_kinds: ["sequence"],
|
||||
mask: "Order",
|
||||
case_sensitive: false,
|
||||
schema: null,
|
||||
parent_schema: null,
|
||||
}),
|
||||
);
|
||||
expect(completionAssistantSearch).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.objectContaining({
|
||||
object_kinds: ["sequence"],
|
||||
mask: "order",
|
||||
case_sensitive: true,
|
||||
schema: "App",
|
||||
parent_schema: null,
|
||||
}),
|
||||
);
|
||||
expect(listCompletionObjects).not.toHaveBeenCalled();
|
||||
expect(objects).toEqual([expect.objectContaining({ name: "OrderSequence", schema: "App", type: "sequence" })]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["empty", vi.fn().mockResolvedValue({ candidates: [], incomplete: false, fallback_used: false })],
|
||||
["permission error", vi.fn().mockRejectedValue(new Error("permission denied for sequence"))],
|
||||
])("returns an empty sequence fallback for %s metadata", async (_caseName, completionAssistantSearch) => {
|
||||
const listCompletionObjects = vi.fn().mockResolvedValue([{ name: "not_a_sequence", object_type: "FUNCTION" }]);
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
completionAssistantSearch,
|
||||
listCompletionObjects,
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
store.connections = [postgresConnection()];
|
||||
store.connectedIds.add("pg-1");
|
||||
|
||||
await expect(store.listCompletionObjects("pg-1", "app", "missing", 20, undefined, undefined, false, undefined, ["sequence"])).resolves.toEqual([]);
|
||||
expect(listCompletionObjects).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("searches the server-reported SQL Server default schema without treating the username as a schema", async () => {
|
||||
const completionAssistantSearch = vi.fn().mockResolvedValue({
|
||||
candidates: [{ name: "st_area", kind: "function", schema: "app_user", data_type: "float" }],
|
||||
|
||||
@@ -5921,7 +5921,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return candidates
|
||||
.map((candidate): SqlCompletionObject | null => {
|
||||
const candidateType = candidate.data_type?.toUpperCase();
|
||||
const type = candidate.kind === "procedure" ? "procedure" : candidate.kind === "function" ? "function" : candidate.kind === "object" && candidateType === "PACKAGE" ? "package" : null;
|
||||
const type = candidate.kind === "procedure" ? "procedure" : candidate.kind === "function" ? "function" : candidate.kind === "sequence" ? "sequence" : candidate.kind === "object" && candidateType === "PACKAGE" ? "package" : null;
|
||||
if (!type) return null;
|
||||
const dataType = candidate.data_type && !["FUNCTION", "PROCEDURE", "PACKAGE"].includes(candidateType ?? "") ? candidate.data_type : undefined;
|
||||
return {
|
||||
@@ -6000,20 +6000,23 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
globalSearch: boolean,
|
||||
currentSchema: string | undefined,
|
||||
objectKinds: CompletionAssistantObjectKind[],
|
||||
caseSensitive: boolean,
|
||||
): Promise<SqlCompletionObject[]> {
|
||||
const databaseType = getConfig(connectionId)?.db_type;
|
||||
const oracleAssistant = databaseType === "oracle";
|
||||
const requestedSchema = schema?.trim() || currentSchema?.trim() || undefined;
|
||||
const preferredSchema = oracleAssistant ? completionPreferredSchema(connectionId, currentSchema) : requestedSchema || (databaseType === "postgres" ? "public" : databaseType === "mysql" ? database : undefined);
|
||||
const sequenceOnly = objectKinds.length === 1 && objectKinds[0] === "sequence";
|
||||
const preferredSchema = oracleAssistant ? completionPreferredSchema(connectionId, currentSchema) : requestedSchema || (!sequenceOnly && databaseType === "postgres" ? "public" : databaseType === "mysql" ? database : undefined);
|
||||
const response = await completionAssistantSearch({
|
||||
connection_id: connectionId,
|
||||
database,
|
||||
schema: oracleAssistant ? (preferredSchema ?? null) : (requestedSchema ?? null),
|
||||
object_kinds: objectKinds,
|
||||
mask: filter.trim(),
|
||||
case_sensitive: caseSensitive,
|
||||
max_results: limit ?? 200,
|
||||
global_search: globalSearch,
|
||||
parent_schema: globalSearch ? null : (schema ?? null),
|
||||
parent_schema: globalSearch || sequenceOnly ? null : (schema ?? null),
|
||||
parent_name: parentName ?? null,
|
||||
match_mode: "prefix",
|
||||
});
|
||||
@@ -6528,11 +6531,25 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return deduped;
|
||||
}
|
||||
|
||||
async function listCompletionObjects(connectionId: string, database: string, filter = "", limit?: number, schema?: string, parentName?: string, globalSearch = false, currentSchema?: string, objectKinds: CompletionAssistantObjectKind[] = ["routine"]): Promise<SqlCompletionObject[]> {
|
||||
async function listCompletionObjects(
|
||||
connectionId: string,
|
||||
database: string,
|
||||
filter = "",
|
||||
limit?: number,
|
||||
schema?: string,
|
||||
parentName?: string,
|
||||
globalSearch = false,
|
||||
currentSchema?: string,
|
||||
objectKinds: CompletionAssistantObjectKind[] = ["routine"],
|
||||
caseSensitive = false,
|
||||
): Promise<SqlCompletionObject[]> {
|
||||
const normalizedFilter = filter.trim().toLowerCase();
|
||||
const cacheFilter = caseSensitive ? filter.trim() : normalizedFilter;
|
||||
const databaseType = getConfig(connectionId)?.db_type;
|
||||
const filteredRoutineAssistant = !!databaseType && FILTERED_ROUTINE_COMPLETION_DATABASES.has(databaseType) && (!!normalizedFilter || typeof limit === "number" || !!parentName || globalSearch);
|
||||
const cacheKey = filteredRoutineAssistant ? `${connectionId}:${database}:${schema ?? ""}:${parentName ?? ""}:${normalizedFilter}:${limit ?? ""}:${globalSearch ? "global" : "scoped"}:${currentSchema ?? ""}:${[...objectKinds].sort().join(",")}` : `${connectionId}:${database}:${schema ?? ""}`;
|
||||
const cacheKey = filteredRoutineAssistant
|
||||
? `${connectionId}:${database}:${schema ?? ""}:${parentName ?? ""}:${cacheFilter}:${limit ?? ""}:${globalSearch ? "global" : "scoped"}:${currentSchema ?? ""}:${[...objectKinds].sort().join(",")}:${caseSensitive ? "case-sensitive" : "case-insensitive"}`
|
||||
: `${connectionId}:${database}:${schema ?? ""}`;
|
||||
if (!completionObjectsCache.value[cacheKey]) {
|
||||
await withCompletionInFlight(
|
||||
`${cacheKey}:objects`,
|
||||
@@ -6540,10 +6557,14 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
await ensureConnected(connectionId);
|
||||
if (filteredRoutineAssistant) {
|
||||
try {
|
||||
completionObjectsCache.value[cacheKey] = dedupeCompletionObjects(await listCompletionAssistantObjects(connectionId, database, filter, limit, schema, parentName, globalSearch, currentSchema, objectKinds));
|
||||
completionObjectsCache.value[cacheKey] = dedupeCompletionObjects(await listCompletionAssistantObjects(connectionId, database, filter, limit, schema, parentName, globalSearch, currentSchema, objectKinds, caseSensitive));
|
||||
} catch {
|
||||
const objects = isSchemaAwareDatabase(connectionId) ? await listSchemaAwareCompletionObjects(connectionId, database, schema) : await api.listCompletionObjects(connectionId, database, schema || database);
|
||||
completionObjectsCache.value[cacheKey] = dedupeCompletionObjects(objects.map(toSqlCompletionObject).filter((object): object is SqlCompletionObject => object != null));
|
||||
if (objectKinds.length === 1 && objectKinds[0] === "sequence") {
|
||||
completionObjectsCache.value[cacheKey] = [];
|
||||
} else {
|
||||
const objects = isSchemaAwareDatabase(connectionId) ? await listSchemaAwareCompletionObjects(connectionId, database, schema) : await api.listCompletionObjects(connectionId, database, schema || database);
|
||||
completionObjectsCache.value[cacheKey] = dedupeCompletionObjects(objects.map(toSqlCompletionObject).filter((object): object is SqlCompletionObject => object != null));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const objects = isSchemaAwareDatabase(connectionId) ? await listSchemaAwareCompletionObjects(connectionId, database, schema) : await api.listCompletionObjects(connectionId, database, schema || database);
|
||||
|
||||
Reference in new issue
Block a user