Files
wehub-resource-sync 75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:11:07 +08:00

253 lines
8.3 KiB
Go

package databases
import (
"context"
"errors"
"log/slog"
"time"
"github.com/google/uuid"
"databasus-backend/internal/features/databases/databases/mariadb"
"databasus-backend/internal/features/databases/databases/mongodb"
"databasus-backend/internal/features/databases/databases/mysql"
postgresql_logical "databasus-backend/internal/features/databases/databases/postgresql/logical"
postgresql_physical "databasus-backend/internal/features/databases/databases/postgresql/physical"
"databasus-backend/internal/features/notifiers"
"databasus-backend/internal/util/encryption"
)
type Database struct {
ID uuid.UUID `json:"id" gorm:"column:id;primaryKey;type:uuid;default:gen_random_uuid()"`
// WorkspaceID can be null when a database is created via restore operation
// outside the context of any workspace
WorkspaceID *uuid.UUID `json:"workspaceId" gorm:"column:workspace_id;type:uuid"`
Name string `json:"name" gorm:"column:name;type:text;not null"`
Type DatabaseType `json:"type" gorm:"column:type;type:text;not null"`
PostgresqlLogical *postgresql_logical.PostgresqlLogicalDatabase `json:"postgresqlLogical,omitzero" gorm:"foreignKey:DatabaseID"`
PostgresqlPhysical *postgresql_physical.PostgresqlPhysicalDatabase `json:"postgresqlPhysical,omitzero" gorm:"foreignKey:DatabaseID"`
Mysql *mysql.MysqlDatabase `json:"mysql,omitzero" gorm:"foreignKey:DatabaseID"`
Mariadb *mariadb.MariadbDatabase `json:"mariadb,omitzero" gorm:"foreignKey:DatabaseID"`
Mongodb *mongodb.MongodbDatabase `json:"mongodb,omitzero" gorm:"foreignKey:DatabaseID"`
Notifiers []notifiers.Notifier `json:"notifiers" gorm:"many2many:database_notifiers;"`
// these fields are not reliable, but
// they are used for pretty UI
LastBackupTime *time.Time `json:"lastBackupTime,omitzero" gorm:"column:last_backup_time;type:timestamp with time zone"`
LastBackupErrorMessage *string `json:"lastBackupErrorMessage,omitempty" gorm:"column:last_backup_error_message;type:text"`
HealthStatus *HealthStatus `json:"healthStatus" gorm:"column:health_status;type:text;not null"`
}
func (d *Database) Validate() error {
if d.Name == "" {
return errors.New("name is required")
}
switch d.Type {
case DatabaseTypePostgresLogical:
if d.PostgresqlLogical == nil {
return errors.New("postgresql database is required")
}
return d.PostgresqlLogical.Validate()
case DatabaseTypePostgresPhysical:
if d.PostgresqlPhysical == nil {
return errors.New("postgresql physical database is required")
}
return d.PostgresqlPhysical.Validate()
case DatabaseTypeMysql:
if d.Mysql == nil {
return errors.New("mysql database is required")
}
return d.Mysql.Validate()
case DatabaseTypeMariadb:
if d.Mariadb == nil {
return errors.New("mariadb database is required")
}
return d.Mariadb.Validate()
case DatabaseTypeMongodb:
if d.Mongodb == nil {
return errors.New("mongodb database is required")
}
return d.Mongodb.Validate()
default:
return errors.New("invalid database type: " + string(d.Type))
}
}
func (d *Database) ValidateUpdate(old, new Database) error {
// Database type cannot be changed after creation — the entire backup
// structure (storage files, schedulers, etc.) is tied to the type at
// creation time. Recreating that state automatically is error-prone;
// it is safer for the user to create a new database and remove the old.
if old.Type != new.Type {
return errors.New("database type cannot be changed; create a new database instead")
}
if old.Type == DatabaseTypePostgresLogical && old.PostgresqlLogical != nil && new.PostgresqlLogical != nil {
if err := new.PostgresqlLogical.ValidateUpdate(old.PostgresqlLogical); err != nil {
return err
}
}
if old.Type == DatabaseTypePostgresPhysical &&
old.PostgresqlPhysical != nil && new.PostgresqlPhysical != nil {
if err := new.PostgresqlPhysical.ValidateUpdate(old.PostgresqlPhysical); err != nil {
return err
}
}
return nil
}
func (d *Database) TestConnection(
logger *slog.Logger,
encryptor encryption.FieldEncryptor,
) error {
switch d.Type {
case DatabaseTypePostgresLogical:
return d.PostgresqlLogical.TestConnection(logger, encryptor)
case DatabaseTypePostgresPhysical:
return d.PostgresqlPhysical.TestReplicationConnection(logger, encryptor)
case DatabaseTypeMysql:
return d.Mysql.TestConnection(logger, encryptor)
case DatabaseTypeMariadb:
return d.Mariadb.TestConnection(logger, encryptor)
case DatabaseTypeMongodb:
return d.Mongodb.TestConnection(logger, encryptor)
default:
return errors.New("connection test not supported for database type: " + string(d.Type))
}
}
func (d *Database) GetRawDbSizeMb(
ctx context.Context,
logger *slog.Logger,
encryptor encryption.FieldEncryptor,
) (float64, error) {
switch d.Type {
case DatabaseTypePostgresLogical:
return d.PostgresqlLogical.GetRawDbSizeMb(ctx, logger, encryptor)
case DatabaseTypeMysql:
return d.Mysql.GetRawDbSizeMb(ctx, logger, encryptor)
case DatabaseTypeMariadb:
return d.Mariadb.GetRawDbSizeMb(ctx, logger, encryptor)
case DatabaseTypeMongodb:
return d.Mongodb.GetRawDbSizeMb(ctx, logger, encryptor)
default:
return 0, errors.New("logical backup not supported for database type: " + string(d.Type))
}
}
func (d *Database) IsUserReadOnly(
ctx context.Context,
logger *slog.Logger,
encryptor encryption.FieldEncryptor,
) (bool, []string, error) {
switch d.Type {
case DatabaseTypePostgresLogical:
return d.PostgresqlLogical.IsUserReadOnly(ctx, logger, encryptor)
case DatabaseTypePostgresPhysical:
return d.PostgresqlPhysical.IsUserReplicationOnly(ctx, logger, encryptor)
case DatabaseTypeMysql:
return d.Mysql.IsUserReadOnly(ctx, logger, encryptor)
case DatabaseTypeMariadb:
return d.Mariadb.IsUserReadOnly(ctx, logger, encryptor)
case DatabaseTypeMongodb:
return d.Mongodb.IsUserReadOnly(ctx, logger, encryptor)
default:
return false, nil, errors.New("read-only check not supported for this database type")
}
}
func (d *Database) HideSensitiveData() {
if d.PostgresqlLogical != nil {
d.PostgresqlLogical.HideSensitiveData()
}
if d.PostgresqlPhysical != nil {
d.PostgresqlPhysical.HideSensitiveData()
}
if d.Mysql != nil {
d.Mysql.HideSensitiveData()
}
if d.Mariadb != nil {
d.Mariadb.HideSensitiveData()
}
if d.Mongodb != nil {
d.Mongodb.HideSensitiveData()
}
}
func (d *Database) EncryptSensitiveFields(encryptor encryption.FieldEncryptor) error {
if d.PostgresqlLogical != nil {
return d.PostgresqlLogical.EncryptSensitiveFields(encryptor)
}
if d.PostgresqlPhysical != nil {
return d.PostgresqlPhysical.EncryptSensitiveFields(encryptor)
}
if d.Mysql != nil {
return d.Mysql.EncryptSensitiveFields(encryptor)
}
if d.Mariadb != nil {
return d.Mariadb.EncryptSensitiveFields(encryptor)
}
if d.Mongodb != nil {
return d.Mongodb.EncryptSensitiveFields(encryptor)
}
return nil
}
func (d *Database) PopulateDbData(
logger *slog.Logger,
encryptor encryption.FieldEncryptor,
) error {
if d.PostgresqlLogical != nil {
return d.PostgresqlLogical.PopulateDbData(logger, encryptor)
}
if d.PostgresqlPhysical != nil {
return d.PostgresqlPhysical.PopulateDbData(logger, encryptor)
}
if d.Mysql != nil {
return d.Mysql.PopulateDbData(logger, encryptor)
}
if d.Mariadb != nil {
return d.Mariadb.PopulateDbData(logger, encryptor)
}
if d.Mongodb != nil {
return d.Mongodb.PopulateDbData(logger, encryptor)
}
return nil
}
func (d *Database) Update(incoming *Database) {
d.Name = incoming.Name
d.Type = incoming.Type
d.Notifiers = incoming.Notifiers
switch d.Type {
case DatabaseTypePostgresLogical:
if d.PostgresqlLogical != nil && incoming.PostgresqlLogical != nil {
d.PostgresqlLogical.Update(incoming.PostgresqlLogical)
}
case DatabaseTypePostgresPhysical:
if d.PostgresqlPhysical != nil && incoming.PostgresqlPhysical != nil {
d.PostgresqlPhysical.Update(incoming.PostgresqlPhysical)
}
case DatabaseTypeMysql:
if d.Mysql != nil && incoming.Mysql != nil {
d.Mysql.Update(incoming.Mysql)
}
case DatabaseTypeMariadb:
if d.Mariadb != nil && incoming.Mariadb != nil {
d.Mariadb.Update(incoming.Mariadb)
}
case DatabaseTypeMongodb:
if d.Mongodb != nil && incoming.Mongodb != nil {
d.Mongodb.Update(incoming.Mongodb)
}
}
}