feat(autobrrctl): add db migrate/seed/reset functionality (#934)

* add db seed/reset functionality

* speculative db migration support

* update postgresSchema

* refactor: only migrate data, no schema

* implement transaction in db migration function

* refactor db:seed and db:reset

added transaction to both and updated help message

* change table order

* skip on foreign key constraint violations

* skip feed_cache

* set constraints to null

* fix seed and reset

* simplify migrate func

This version of the function should behave similarly to the previous version, but with less repetition and by relying on PostgreSQL's automatic transaction handling, which starts a new transaction for each separate statement when not in a transaction block. Also, it prepares the insert statement only once for each table, which can significantly improve performance when there are many rows to insert.

* fixed release_action_status

* refactor db:seed and db:reset

* minor adjustment to const usage

* fixed usage examples

* refactor seed/migrate

* refactor(database): seed and convert

* refactor(database): make tables list sorted a-z

* chore(deps): go mod tidy

* refactor(database): convert improve log output

---------

Co-authored-by: ze0s <ze0s@riseup.net>
This commit is contained in:
soup 2023-12-28 14:05:51 +01:00 committed by GitHub
parent 6a94ecacca
commit ebbd851a2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 327 additions and 8 deletions

View file

@ -17,22 +17,35 @@ import (
"github.com/autobrr/autobrr/internal/auth"
"github.com/autobrr/autobrr/internal/config"
"github.com/autobrr/autobrr/internal/database"
"github.com/autobrr/autobrr/internal/database/tools"
"github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/internal/user"
"github.com/autobrr/autobrr/pkg/errors"
_ "github.com/lib/pq"
"golang.org/x/term"
_ "modernc.org/sqlite"
)
const usage = `usage: autobrrctl --config path <action>
const usage = `usage: autobrrctl <action> [options]
create-user <username> Create user
change-password <username> Change password for user
version Can be run without --config
help Show this help message
Actions:
create-user <username> Create a new user
change-password <username> Change the password
db:seed --db-path <path-to-database> --seed-db <path-to-sql-seed> Seed the sqlite database
db:reset --db-path <path-to-database> --seed-db <path-to-sql-seed> Reset the sqlite database
db:convert --sqlite-db <path-to-sqlite-db> --postgres-url <postgres-db-url> Convert SQLite to Postgres
version Display the version of autobrrctl
help Show this help message
Examples:
autobrrctl --config /path/to/config/dir create-user john
autobrrctl --config /path/to/config/dir change-password john
autobrrctl db:reset --db-path /path/to/autobrr.db --seed-db /path/to/seed
autobrrctl db:seed --db-path /path/to/autobrr.db --seed-db /path/to/seed
autobrrctl db:convert --sqlite-db /path/to/autobrr.db --postgres-url postgres://username:password@127.0.0.1:5432/autobrr
autobrrctl version
autobrrctl help
`
var (
@ -56,6 +69,7 @@ func main() {
flag.Parse()
switch cmd := flag.Arg(0); cmd {
case "version":
fmt.Printf("Version: %v\nCommit: %v\nBuild: %v\n", version, commit, date)
@ -91,7 +105,6 @@ func main() {
fmt.Printf("Latest release: %v\n", rel.TagName)
case "create-user":
if configPath == "" {
log.Fatal("--config required")
}
@ -141,7 +154,6 @@ func main() {
}
case "change-password":
if configPath == "" {
log.Fatal("--config required")
}
@ -204,6 +216,68 @@ func main() {
log.Printf("successfully updated password for user %q", username)
case "db:convert":
var sqliteDBPath, postgresDBURL string
migrateFlagSet := flag.NewFlagSet("db:convert", flag.ExitOnError)
migrateFlagSet.StringVar(&sqliteDBPath, "sqlite-db", "", "path to SQLite database file")
migrateFlagSet.StringVar(&postgresDBURL, "postgres-url", "", "URL for PostgreSQL database")
if err := migrateFlagSet.Parse(flag.Args()[1:]); err != nil {
fmt.Printf("Error parsing flags for db:convert: %v\n", err)
migrateFlagSet.Usage()
os.Exit(1)
}
if sqliteDBPath == "" || postgresDBURL == "" {
fmt.Println("Error: missing required flags for db:convert")
flag.Usage()
os.Exit(1)
}
c := tools.NewConverter(sqliteDBPath, postgresDBURL)
if err := c.Convert(); err != nil {
log.Fatalf("database conversion failed: %v", err)
}
case "db:seed", "db:reset":
var dbPath, seedDBPath string
seedResetFlagSet := flag.NewFlagSet("db:seed/db:reset", flag.ExitOnError)
seedResetFlagSet.StringVar(&dbPath, "db-path", "", "path to the database file")
seedResetFlagSet.StringVar(&seedDBPath, "seed-db", "", "path to SQL seed file")
if err := seedResetFlagSet.Parse(flag.Args()[1:]); err != nil {
fmt.Printf("Error parsing flags for db:seed or db:reset: %v\n", err)
seedResetFlagSet.Usage()
os.Exit(1)
}
if dbPath == "" || seedDBPath == "" {
fmt.Println("Error: missing required flags for db:seed or db:reset")
flag.Usage()
os.Exit(1)
}
s := tools.NewSQLiteSeeder(dbPath, seedDBPath)
if cmd == "db:seed" {
if err := s.Seed(); err != nil {
fmt.Println("Error seeding the database:", err)
os.Exit(1)
}
fmt.Println("Database seeding completed successfully!")
} else {
if err := s.Reset(); err != nil {
fmt.Println("Error resetting the database:", err)
os.Exit(1)
}
if err := s.Seed(); err != nil {
fmt.Println("Error seeding the database:", err)
os.Exit(1)
}
fmt.Println("Database reset and reseed completed successfully!")
}
default:
flag.Usage()
if cmd != "help" {