fix(mysql): improve create database permission hint

This commit is contained in:
zipg 2026-07-21 11:22:51 +08:00 committed by GitHub
parent 5847f242de
commit 7d41e4123f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 63 additions and 5 deletions

View File

@ -91,6 +91,7 @@ import { dataTabOpenModeFromTreeClick, type DataTabOpenMode } from "@/lib/sideba
import { isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut } from "@/lib/editor/keyboardShortcuts";
import { canRefreshDataTableFromSingleActivationDoubleClick, dataTableDoubleClickAction } from "@/lib/tabs/dataTabActivation";
import { attachedDatabaseNameFromPath, buildCreateDatabaseSql, buildDuckDbAttachDatabaseSql, buildSqliteAttachDatabaseSql, supportsCreateDatabaseCharset, uniqueAttachedDatabaseName } from "@/lib/database/createDatabaseSql";
import { appendCreateDatabaseErrorHint } from "@/lib/database/createDatabaseErrorHints";
import { SQLITE_DATABASE_FILE_EXTENSIONS } from "@/lib/database/databaseFileDetection";
import {
buildCreateSchemaSql,
@ -2572,24 +2573,30 @@ async function applyCreateDatabaseAuthorizationPlan() {
if (!node.connectionId || !plan || createDatabaseAuthorizationApplying.value) return;
createDatabaseAuthorizationApplying.value = true;
try {
const config = connectionStore.getConfig(node.connectionId);
const results = await executeWithProductionSqlGuard({
connection: connectionStore.getConfig(node.connectionId),
connection: config,
database: "",
sql: createDatabasePreviewSql.value,
source: t("production.sourceSidebar"),
execute: () => executeAuthorizationPlan(plan, (step) => api.executeMulti(node.connectionId!, step.database, step.sql, undefined, undefined, { maxRows: 1000, continueOnError: true })),
});
if (!results) return;
createDatabaseAuthorizationResults.value = results;
const created = results.some((result) => result.step.id === "create-database" && result.status === "success");
const status = authorizationPlanStatus(results);
const displayResults = results.map((result) => ({
...result,
message: result.message && result.step.operation === "createDatabase" ? appendCreateDatabaseErrorHint(config?.db_type, result.message, t) : result.message,
}));
createDatabaseAuthorizationResults.value = displayResults;
const created = displayResults.some((result) => result.step.id === "create-database" && result.status === "success");
const status = authorizationPlanStatus(displayResults);
if (created) {
await connectionStore.ensureVisibleDatabase(node.connectionId, name);
await connectionStore.loadDatabases(node.connectionId, { force: true });
}
toast(t(status === "success" ? "contextMenu.createDatabaseSuccess" : status === "partial" ? "contextMenu.createDatabasePartial" : "contextMenu.createDatabaseFailed", { name }), status === "success" ? 3000 : 5000);
} catch (error: any) {
toast(t("contextMenu.tableOperationFailed", { message: error?.message || String(error) }), 5000);
const message = appendCreateDatabaseErrorHint(connectionStore.getConfig(node.connectionId)?.db_type, error?.message || String(error), t);
toast(t("contextMenu.tableOperationFailed", { message }), 8000);
} finally {
createDatabaseAuthorizationApplying.value = false;
}

View File

@ -1784,6 +1784,7 @@ export default {
confirmDropDatabaseTitle: "Drop Database",
confirmDropDatabaseMessage: 'Are you sure you want to drop database "{name}"? This will permanently delete the database and all its data.',
createDatabaseSuccess: 'Database "{name}" created',
mysqlCreateDatabasePermissionHint: "The current MySQL account cannot create this database. An account named root does not necessarily have administrator privileges. Use an administrator account with GRANT OPTION to grant CREATE permission, then try again.",
createDatabasePartial: 'Database "{name}" was created, but some grants failed',
createDatabaseFailed: 'Database "{name}" could not be created',
createDatabaseUsers: "Grant access to users",

View File

@ -1719,6 +1719,7 @@ export default withEnglishFallback({
confirmDropDatabaseTitle: "Eliminar base de datos",
confirmDropDatabaseMessage: '¿Estás seguro de que deseas eliminar la base de datos "{name}"? Esto borrará permanentemente la base de datos y todos sus datos.',
createDatabaseSuccess: 'Base de datos "{name}" creada',
mysqlCreateDatabasePermissionHint: "La cuenta MySQL actual no puede crear esta base de datos. Una cuenta llamada root no tiene necesariamente privilegios de administrador. Use una cuenta de administrador con GRANT OPTION para conceder el permiso CREATE y vuelva a intentarlo.",
createDatabasePartial: 'La base de datos "{name}" se creó, pero algunas concesiones fallaron',
createDatabaseFailed: 'No se pudo crear la base de datos "{name}"',
createDatabaseUsers: "Conceder acceso a usuarios",

View File

@ -1717,6 +1717,7 @@ export default withEnglishFallback({
confirmDropDatabaseTitle: "Elimina Database",
confirmDropDatabaseMessage: 'Sei sicuro di voler eliminare il database "{name}"? Questa operazione eliminerà permanentemente il database e tutti i suoi dati.',
createDatabaseSuccess: 'Database "{name}" creato',
mysqlCreateDatabasePermissionHint: "L'account MySQL corrente non può creare questo database. Un account denominato root non dispone necessariamente dei privilegi di amministratore. Usa un account amministratore con GRANT OPTION per concedere il privilegio CREATE, quindi riprova.",
createDatabasePartial: 'Il database "{name}" è stato creato, ma alcune concessioni non sono riuscite',
createDatabaseFailed: 'Impossibile creare il database "{name}"',
createDatabaseUsers: "Concedi accesso agli utenti",

View File

@ -1714,6 +1714,7 @@ export default withEnglishFallback({
confirmDropDatabaseTitle: "データベースを削除",
confirmDropDatabaseMessage: "本当にデータベース「{name}」をドロップ(削除)しますか?データベースとすべてのデータが永久に削除されます。",
createDatabaseSuccess: "データベース「{name}」を作成しました",
mysqlCreateDatabasePermissionHint: "現在の MySQL アカウントには、このデータベースを作成する権限がありません。root という名前のアカウントでも管理者権限があるとは限りません。GRANT OPTION を持つ管理者アカウントで CREATE 権限を付与してから、もう一度お試しください。",
createDatabasePartial: "データベース「{name}」は作成されましたが、一部のユーザー権限付与に失敗しました",
createDatabaseFailed: "データベース「{name}」を作成できませんでした",
createDatabaseUsers: "アクセスを付与するユーザー",

View File

@ -1719,6 +1719,7 @@ export default withEnglishFallback({
confirmDropDatabaseTitle: "Remover Banco de Dados",
confirmDropDatabaseMessage: 'Tem certeza de que deseja remover o banco de dados "{name}"? Isso excluirá permanentemente o banco de dados e todos os seus dados.',
createDatabaseSuccess: 'Banco de dados "{name}" criado',
mysqlCreateDatabasePermissionHint: "A conta MySQL atual não pode criar este banco de dados. Uma conta chamada root não possui necessariamente privilégios de administrador. Use uma conta de administrador com GRANT OPTION para conceder a permissão CREATE e tente novamente.",
createDatabasePartial: 'O banco de dados "{name}" foi criado, mas algumas concessões falharam',
createDatabaseFailed: 'Não foi possível criar o banco de dados "{name}"',
createDatabaseUsers: "Conceder acesso aos usuários",

View File

@ -1784,6 +1784,7 @@ export default withEnglishFallback({
confirmDropDatabaseTitle: "删除数据库",
confirmDropDatabaseMessage: "确定要删除数据库「{name}」吗?这将永久删除该数据库及其所有数据。",
createDatabaseSuccess: "数据库「{name}」已创建",
mysqlCreateDatabasePermissionHint: "当前 MySQL 账号没有创建此数据库的权限。账号名为 root 也不代表拥有管理员权限,请使用具备 GRANT OPTION 的管理员账号授予 CREATE 权限后重试。",
createDatabasePartial: "数据库「{name}」已创建,但部分用户授权失败",
createDatabaseFailed: "数据库「{name}」创建失败",
createDatabaseUsers: "关联用户授权",

View File

@ -1719,6 +1719,7 @@ export default withEnglishFallback({
confirmDropDatabaseTitle: "刪除資料庫",
confirmDropDatabaseMessage: "確定要刪除資料庫「{name}」嗎?這將永久刪除該資料庫及其所有資料。",
createDatabaseSuccess: "資料庫「{name}」已建立",
mysqlCreateDatabasePermissionHint: "目前的 MySQL 帳號沒有建立此資料庫的權限。帳號名稱為 root 也不代表擁有管理員權限,請使用具備 GRANT OPTION 的管理員帳號授予 CREATE 權限後再試。",
createDatabasePartial: "資料庫「{name}」已建立,但部分使用者授權失敗",
createDatabaseFailed: "資料庫「{name}」建立失敗",
createDatabaseUsers: "關聯使用者授權",

View File

@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest";
import { appendCreateDatabaseErrorHint } from "@/lib/database/createDatabaseErrorHints";
const hint = "Use an account with GRANT OPTION to grant CREATE permission.";
const t = (key: string) => (key === "contextMenu.mysqlCreateDatabasePermissionHint" ? hint : key);
describe("appendCreateDatabaseErrorHint", () => {
it("adds guidance for MySQL error 1044", () => {
const message = appendCreateDatabaseErrorHint("mysql", "ERROR 1044 (42000): Access denied for user 'root'@'%' to database 'pro'", t);
expect(message).toContain("ERROR 1044");
expect(message).toContain(hint);
});
it("recognizes access-denied messages when the driver omits the error code", () => {
const message = appendCreateDatabaseErrorHint("mysql", "Access denied for user 'app'@'%' to database 'pro'", t);
expect(message).toContain(hint);
});
it("does not alter unrelated or non-MySQL errors", () => {
expect(appendCreateDatabaseErrorHint("mysql", "ERROR 1007: Can't create database; database exists", t)).toBe("ERROR 1007: Can't create database; database exists");
expect(appendCreateDatabaseErrorHint("postgres", "ERROR 1044: permission denied", t)).toBe("ERROR 1044: permission denied");
});
it("does not append the same hint twice", () => {
const original = `ERROR 1044: Access denied\n\n${hint}`;
expect(appendCreateDatabaseErrorHint("mysql", original, t)).toBe(original);
});
});

View File

@ -0,0 +1,13 @@
import type { DatabaseType } from "@/types/database";
type Translate = (key: string) => string;
function isMysqlCreateDatabaseAccessDenied(message: string): boolean {
return /\b1044\b/i.test(message) || /access denied for user[\s\S]*to database/i.test(message);
}
export function appendCreateDatabaseErrorHint(databaseType: DatabaseType | undefined, message: string, t: Translate): string {
if (databaseType !== "mysql" || !isMysqlCreateDatabaseAccessDenied(message)) return message;
const hint = t("contextMenu.mysqlCreateDatabasePermissionHint");
return message.includes(hint) ? message : `${message}\n\n${hint}`;
}