Merge pull request #212 from databasus/develop

FIX (mariadb): Add events exclusion for MariaDB
This commit is contained in:
Rostislav Dugin
2026-01-04 22:16:24 +03:00
committed by GitHub
7 changed files with 202 additions and 10 deletions

View File

@@ -108,11 +108,14 @@ func (uc *CreateMariadbBackupUsecase) buildMariadbDumpArgs(
"--single-transaction",
"--routines",
"--triggers",
"--events",
"--quick",
"--verbose",
}
if !mdb.IsExcludeEvents {
args = append(args, "--events")
}
args = append(args, "--compress")
if mdb.IsHttps {

View File

@@ -29,6 +29,9 @@ type MariadbDatabase struct {
Password string `json:"password" gorm:"type:text;not null"`
Database *string `json:"database" gorm:"type:text"`
IsHttps bool `json:"isHttps" gorm:"type:boolean;default:false"`
// advanced
IsExcludeEvents bool `json:"isExcludeEvents" gorm:"column:is_exclude_events;type:boolean;default:false"`
}
func (m *MariadbDatabase) TableName() string {
@@ -111,6 +114,7 @@ func (m *MariadbDatabase) Update(incoming *MariadbDatabase) {
m.Username = incoming.Username
m.Database = incoming.Database
m.IsHttps = incoming.IsHttps
m.IsExcludeEvents = incoming.IsExcludeEvents
if incoming.Password != "" {
m.Password = incoming.Password

View File

@@ -149,6 +149,101 @@ func Test_BackupAndRestoreMariadb_WithReadOnlyUser_RestoreIsSuccessful(t *testin
}
}
func Test_BackupAndRestoreMariadb_WithExcludeEvents_RestoreIsSuccessful(t *testing.T) {
env := config.GetEnv()
container, err := connectToMariadbContainer(tools.MariadbVersion120, env.TestMariadb120Port)
if err != nil {
t.Skipf("Skipping MariaDB 12.0 IsExcludeEvents test: %v", err)
return
}
defer func() {
if container.DB != nil {
container.DB.Close()
}
}()
setupMariadbTestData(t, container.DB)
router := createTestRouter()
user := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace := workspaces_testing.CreateTestWorkspace(
"MariaDB ExcludeEvents Test Workspace",
user,
router,
)
storage := storages.CreateTestStorage(workspace.ID)
database := createMariadbDatabaseWithExcludeEventsViaAPI(
t, router, "MariaDB ExcludeEvents Test Database", workspace.ID,
container.Host, container.Port,
container.Username, container.Password, container.Database,
container.Version,
true,
user.Token,
)
enableBackupsViaAPI(
t, router, database.ID, storage.ID,
backups_config.BackupEncryptionNone, user.Token,
)
createBackupViaAPI(t, router, database.ID, user.Token)
backup := waitForBackupCompletion(t, router, database.ID, user.Token, 5*time.Minute)
assert.Equal(t, backups.BackupStatusCompleted, backup.Status)
newDBName := "restoreddb_mariadb_excludeevents"
_, err = container.DB.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s;", newDBName))
assert.NoError(t, err)
_, err = container.DB.Exec(fmt.Sprintf("CREATE DATABASE %s;", newDBName))
assert.NoError(t, err)
newDSN := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true",
container.Username, container.Password, container.Host, container.Port, newDBName)
newDB, err := sqlx.Connect("mysql", newDSN)
assert.NoError(t, err)
defer newDB.Close()
createMariadbRestoreViaAPI(
t, router, backup.ID,
container.Host, container.Port,
container.Username, container.Password, newDBName,
container.Version,
user.Token,
)
restore := waitForMariadbRestoreCompletion(t, router, backup.ID, user.Token, 5*time.Minute)
assert.Equal(t, restores_enums.RestoreStatusCompleted, restore.Status)
var tableExists int
err = newDB.Get(
&tableExists,
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = ? AND table_name = 'test_data'",
newDBName,
)
assert.NoError(t, err)
assert.Equal(t, 1, tableExists, "Table 'test_data' should exist in restored database")
verifyMariadbDataIntegrity(t, container.DB, newDB)
err = os.Remove(filepath.Join(config.GetEnv().DataFolder, backup.ID.String()))
if err != nil {
t.Logf("Warning: Failed to delete backup file: %v", err)
}
test_utils.MakeDeleteRequest(
t,
router,
"/api/v1/databases/"+database.ID.String(),
"Bearer "+user.Token,
http.StatusNoContent,
)
storages.RemoveTestStorage(storage.ID)
workspaces_testing.RemoveTestWorkspace(workspace, router)
}
func testMariadbBackupRestoreForVersion(
t *testing.T,
mariadbVersion tools.MariadbVersion,
@@ -459,18 +554,40 @@ func createMariadbDatabaseViaAPI(
database string,
version tools.MariadbVersion,
token string,
) *databases.Database {
return createMariadbDatabaseWithExcludeEventsViaAPI(
t, router, name, workspaceID,
host, port, username, password, database,
version, false, token,
)
}
func createMariadbDatabaseWithExcludeEventsViaAPI(
t *testing.T,
router *gin.Engine,
name string,
workspaceID uuid.UUID,
host string,
port int,
username string,
password string,
database string,
version tools.MariadbVersion,
isExcludeEvents bool,
token string,
) *databases.Database {
request := databases.Database{
Name: name,
WorkspaceID: &workspaceID,
Type: databases.DatabaseTypeMariadb,
Mariadb: &mariadbtypes.MariadbDatabase{
Host: host,
Port: port,
Username: username,
Password: password,
Database: &database,
Version: version,
Host: host,
Port: port,
Username: username,
Password: password,
Database: &database,
Version: version,
IsExcludeEvents: isExcludeEvents,
},
}

View File

@@ -0,0 +1,11 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE mariadb_databases
ADD COLUMN is_exclude_events BOOLEAN NOT NULL DEFAULT FALSE;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE mariadb_databases
DROP COLUMN is_exclude_events;
-- +goose StatementEnd

View File

@@ -9,4 +9,5 @@ export interface MariadbDatabase {
password: string;
database?: string;
isHttps: boolean;
isExcludeEvents?: boolean;
}

View File

@@ -1,5 +1,5 @@
import { CopyOutlined } from '@ant-design/icons';
import { App, Button, Input, InputNumber, Switch } from 'antd';
import { CopyOutlined, DownOutlined, InfoCircleOutlined, UpOutlined } from '@ant-design/icons';
import { App, Button, Checkbox, Input, InputNumber, Switch, Tooltip } from 'antd';
import { useEffect, useState } from 'react';
import { type Database, databaseApi } from '../../../../entity/databases';
@@ -45,6 +45,9 @@ export const EditMariaDbSpecificDataComponent = ({
const [isTestingConnection, setIsTestingConnection] = useState(false);
const [isConnectionFailed, setIsConnectionFailed] = useState(false);
const hasAdvancedValues = !!database.mariadb?.isExcludeEvents;
const [isShowAdvanced, setShowAdvanced] = useState(hasAdvancedValues);
const parseFromClipboard = async () => {
try {
const text = await navigator.clipboard.readText();
@@ -297,7 +300,7 @@ export const EditMariaDbSpecificDataComponent = ({
</div>
)}
<div className="mb-3 flex w-full items-center">
<div className="mb-1 flex w-full items-center">
<div className="min-w-[150px]">Use HTTPS</div>
<Switch
checked={editingDatabase.mariadb?.isHttps}
@@ -314,6 +317,52 @@ export const EditMariaDbSpecificDataComponent = ({
/>
</div>
<div className="mt-4 mb-1 flex items-center">
<div
className="flex cursor-pointer items-center text-sm text-blue-600 hover:text-blue-800"
onClick={() => setShowAdvanced(!isShowAdvanced)}
>
<span className="mr-2">Advanced settings</span>
{isShowAdvanced ? (
<UpOutlined style={{ fontSize: '12px' }} />
) : (
<DownOutlined style={{ fontSize: '12px' }} />
)}
</div>
</div>
{isShowAdvanced && (
<div className="mb-1 flex w-full items-center">
<div className="min-w-[150px]">Exclude events</div>
<div className="flex items-center">
<Checkbox
checked={editingDatabase.mariadb?.isExcludeEvents || false}
onChange={(e) => {
if (!editingDatabase.mariadb) return;
setEditingDatabase({
...editingDatabase,
mariadb: {
...editingDatabase.mariadb,
isExcludeEvents: e.target.checked,
},
});
}}
>
Skip events
</Checkbox>
<Tooltip
className="cursor-pointer"
title="Skip backing up database events. Enable this if the event scheduler is disabled on your MariaDB server."
>
<InfoCircleOutlined className="ml-2" style={{ color: 'gray' }} />
</Tooltip>
</div>
</div>
)}
<div className="mt-5 flex">
{isShowCancelButton && (
<Button className="mr-1" danger ghost onClick={() => onCancel()}>

View File

@@ -55,6 +55,13 @@ export const ShowMariaDbSpecificDataComponent = ({ database }: Props) => {
<div className="min-w-[150px]">Use HTTPS</div>
<div>{database.mariadb?.isHttps ? 'Yes' : 'No'}</div>
</div>
{database.mariadb?.isExcludeEvents && (
<div className="mb-1 flex w-full items-center">
<div className="min-w-[150px]">Exclude events</div>
<div>Yes</div>
</div>
)}
</div>
);
};