fix(postgres): improve view column save guidance
This commit is contained in:
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;
|
||||
|
||||
Reference in new issue
Block a user