fix(postgres): improve view column save guidance

This commit is contained in:
zipg authored and GitHub committed 2026-07-24 15:51:27 +08:00
1 parent 0992c52448
commit 6ddfa8bd24
11 files changed
+74 -9

No files matched your search

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import * as api from "@/lib/backend/api";
import { executeObjectSourceSave } from "@/lib/table/objectSourceEditor";
import { executeObjectSourceSave, formatObjectSourceSaveError } from "@/lib/table/objectSourceEditor";
vi.mock("@/lib/backend/api", () => ({
executeInTransaction: vi.fn().mockResolvedValue({}),
@@ -32,3 +32,33 @@ describe("executeObjectSourceSave", () => {
expect(api.executeScript).not.toHaveBeenCalled();
});
});
describe("formatObjectSourceSaveError", () => {
const hint = "Keep existing view columns unchanged and append new columns at the end.";
it.each(["ERROR: cannot drop columns from view", 'ERROR: cannot change name of view column "old_name" to "new_name"', 'ERROR: cannot change data type of view column "amount" from integer to bigint', "ERROR: ビューからは列を削除できません", "错误:无法从视图中删除列"])(
"appends guidance for PostgreSQL view column replacement errors: %s",
(message) => {
expect(formatObjectSourceSaveError(new Error(message), "postgres", "VIEW", hint)).toBe(`${message}\n\n${hint}`);
},
);
it("supports PostgreSQL-compatible databases", () => {
expect(formatObjectSourceSaveError("cannot change name of view column", "kingbase", "VIEW", hint)).toBe(`cannot change name of view column\n\n${hint}`);
});
it("leaves unrelated errors unchanged", () => {
expect(formatObjectSourceSaveError(new Error("syntax error at or near SELECT"), "postgres", "VIEW", hint)).toBe("syntax error at or near SELECT");
expect(formatObjectSourceSaveError(new Error("SQLSTATE 42P16"), "mysql", "VIEW", hint)).toBe("SQLSTATE 42P16");
expect(formatObjectSourceSaveError(new Error("SQLSTATE 42P16"), "postgres", "PROCEDURE", hint)).toBe("SQLSTATE 42P16");
});
it.each(["Agent RPC error: SQLSTATE 42P16", "ERROR: ON COMMIT can only be used on temporary tables (SQLSTATE 42P16)"])("does not treat unrelated invalid_table_definition errors as view column changes: %s", (message) => {
expect(formatObjectSourceSaveError(new Error(message), "postgres", "VIEW", hint)).toBe(message);
});
it("does not append the same guidance twice", () => {
const formatted = formatObjectSourceSaveError("ERROR: cannot drop columns from view", "postgres", "VIEW", hint);
expect(formatObjectSourceSaveError(formatted, "postgres", "VIEW", hint)).toBe(formatted);
});
});
@@ -16,9 +16,32 @@ export type BuildRoutineRenameObjectSourceInput = BuildEditableObjectSourceSqlIn
export type ObjectSourceSaveExecutionMode = "single" | "script";
const postgresLikeRoutineRenameTypes = new Set<DatabaseType>(["postgres", "redshift", "gaussdb", "kwdb", "kingbase", "highgo", "vastbase"]);
const postgresLikeViewTypes = new Set<DatabaseType>(["postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb", "kingbase", "highgo", "vastbase"]);
const mysqlLikeRoutineRenameTypes = new Set<DatabaseType>(["mysql", "goldendb"]);
const oracleLikeRoutineRenameTypes = new Set<DatabaseType>(["oracle", "dameng"]);
// SQLSTATE 42P16 covers unrelated invalid table definitions, so only match the
// confirmed PostgreSQL view-column errors and their localized equivalents.
const postgresViewColumnChangeErrorPatterns = [/cannot drop columns? from view/i, /cannot change name of view column/i, /cannot change data type of view column/i, /ビューからは列を削除できません/, /(?:无法|不能)从视图中删除列/];
function errorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (error && typeof error === "object" && "message" in error) {
const message = (error as { message?: unknown }).message;
if (message !== undefined && message !== null) return String(message);
}
return String(error);
}
export function formatObjectSourceSaveError(error: unknown, databaseType: DatabaseType, objectType: ObjectSourceKind, postgresViewColumnChangeHint: string): string {
const message = errorMessage(error);
if (objectType !== "VIEW" || !postgresLikeViewTypes.has(databaseType) || !postgresViewColumnChangeErrorPatterns.some((pattern) => pattern.test(message)) || message.includes(postgresViewColumnChangeHint)) {
return message;
}
return `${message}\n\n${postgresViewColumnChangeHint}`;
}
export function supportsSourceBackedRoutineRename(databaseType: DatabaseType | undefined, objectType: ObjectSourceKind): boolean {
if (objectType !== "FUNCTION" && objectType !== "PROCEDURE") return false;
if (!databaseType || databaseType === "sqlserver") return false;