get database migrations up and running
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE IF NOT EXISTS books(
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
title VARCHAR(1024),
|
||||
authors VARCHAR(1024),
|
||||
sortauthor VARCHAR(1024),
|
||||
isbn10 VARCHAR(10),
|
||||
isbn13 VARCHAR(13),
|
||||
format VARCHAR(255),
|
||||
genre VARCHAR(255),
|
||||
publisher VARCHAR(255),
|
||||
series VARCHAR(255),
|
||||
volume VARCHAR(255),
|
||||
year VARCHAR(10),
|
||||
signed BOOLEAN,
|
||||
description TEXT,
|
||||
notes TEXT,
|
||||
onloan VARCHAR(255),
|
||||
coverurl VARCHAR(1024),
|
||||
PRIMARY KEY (id)
|
||||
)
|
@@ -25,6 +25,7 @@ type migration struct {
|
||||
|
||||
type MySQL struct {
|
||||
connection *sql.DB
|
||||
tableName string
|
||||
versionTable string
|
||||
migrationsDirectory string
|
||||
}
|
||||
@@ -37,8 +38,9 @@ func NewMySQLConnection(user, pass, host, port, db string) (*MySQL, error) {
|
||||
}
|
||||
return &MySQL{
|
||||
connection: connection,
|
||||
tableName: "books",
|
||||
versionTable: "migrations",
|
||||
migrationsDirectory: "/migrations/mysql",
|
||||
migrationsDirectory: "migrations/mysql",
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -72,26 +74,26 @@ func (m *MySQL) GetLatestMigration(ctx context.Context) (int, error) {
|
||||
}
|
||||
|
||||
var latestMigration int
|
||||
err := m.connection.QueryRowContext(ctx, "SELECT MAX(id) FROM "+m.versionTable).Scan(&latestMigration)
|
||||
err := m.connection.QueryRowContext(ctx, "SELECT COALESCE(MAX(id), 0) FROM "+m.versionTable).Scan(&latestMigration)
|
||||
return latestMigration, err
|
||||
}
|
||||
|
||||
func (m *MySQL) RunMigrations(ctx context.Context) (int, error) {
|
||||
func (m *MySQL) RunMigrations(ctx context.Context) (int, int, error) {
|
||||
if m.connection == nil || m.migrationsDirectory == "" || m.versionTable == "" {
|
||||
return 0, fmt.Errorf("uninitialized mysql client")
|
||||
return 0, 0, fmt.Errorf("uninitialized mysql client")
|
||||
}
|
||||
|
||||
var migrations map[int]migration
|
||||
migrations := map[int]migration{}
|
||||
dir, err := migrationsFS.ReadDir(m.migrationsDirectory)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
return 0, 0, err
|
||||
}
|
||||
for f := range dir {
|
||||
if dir[f].Type().IsRegular() {
|
||||
mig := migration{}
|
||||
id, name, err := parseMigrationFileName(dir[f].Name())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, 0, err
|
||||
}
|
||||
mig.id, mig.name = id, name
|
||||
mig.content, err = fs.ReadFile(migrationsFS, m.migrationsDirectory+"/"+dir[f].Name())
|
||||
@@ -101,41 +103,43 @@ func (m *MySQL) RunMigrations(ctx context.Context) (int, error) {
|
||||
|
||||
latestMigrationRan, err := m.GetLatestMigration(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// exit if nothing to do (that is, there's no greater migration ID)
|
||||
if _, ok := migrations[latestMigrationRan+1]; !ok {
|
||||
return latestMigrationRan, nil
|
||||
return latestMigrationRan, 0, nil
|
||||
}
|
||||
|
||||
// loop over and apply migrations if required
|
||||
tx, err := m.connection.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return latestMigrationRan, err
|
||||
return latestMigrationRan, 0, err
|
||||
}
|
||||
migrationsRun := 0
|
||||
for migrationsToRun := true; migrationsToRun; _, migrationsToRun = migrations[latestMigrationRan+1] {
|
||||
mig := migrations[latestMigrationRan+1]
|
||||
_, err := tx.ExecContext(ctx, string(mig.content))
|
||||
if err != nil {
|
||||
nestederr := tx.Rollback()
|
||||
if nestederr != nil {
|
||||
return latestMigrationRan, nestederr
|
||||
return latestMigrationRan, migrationsRun, nestederr
|
||||
}
|
||||
return latestMigrationRan, err
|
||||
return latestMigrationRan, migrationsRun, err
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO "+m.versionTable+" (id, name, datetime) VALUES (?, ?, ?)", mig.id, mig.name, time.Now())
|
||||
if err != nil {
|
||||
nestederr := tx.Rollback()
|
||||
if nestederr != nil {
|
||||
return latestMigrationRan, nestederr
|
||||
return latestMigrationRan, migrationsRun, nestederr
|
||||
}
|
||||
return latestMigrationRan, err
|
||||
return latestMigrationRan, migrationsRun, err
|
||||
}
|
||||
latestMigrationRan = latestMigrationRan + 1
|
||||
migrationsRun = migrationsRun + 1
|
||||
}
|
||||
err = tx.Commit()
|
||||
return latestMigrationRan, err
|
||||
return latestMigrationRan, migrationsRun, err
|
||||
}
|
||||
|
||||
func (m *MySQL) GetAllBooks(ctx context.Context) ([]book.Book, error) {
|
||||
@@ -144,7 +148,25 @@ func (m *MySQL) GetAllBooks(ctx context.Context) ([]book.Book, error) {
|
||||
}
|
||||
|
||||
books := []book.Book{}
|
||||
rows, err := m.connection.QueryContext(ctx, "SELECT id, title FROM books")
|
||||
rows, err := m.connection.QueryContext(ctx, `
|
||||
SELECT id,
|
||||
title,
|
||||
authors,
|
||||
sortauthor,
|
||||
isbn10,
|
||||
isbn13,
|
||||
format,
|
||||
genre,
|
||||
publisher,
|
||||
series,
|
||||
volume,
|
||||
year,
|
||||
signed,
|
||||
description,
|
||||
notes,
|
||||
onloan,
|
||||
coverurl
|
||||
FROM `+m.tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -152,10 +174,18 @@ func (m *MySQL) GetAllBooks(ctx context.Context) ([]book.Book, error) {
|
||||
|
||||
for rows.Next() {
|
||||
b := book.Book{}
|
||||
err := rows.Scan(&b.ID, &b.Title)
|
||||
var authors string
|
||||
err := rows.Scan(
|
||||
&b.ID, &b.Title, &authors,
|
||||
&b.SortAuthor, &b.ISBN10, &b.ISBN13,
|
||||
&b.Format, &b.Genre, &b.Publisher,
|
||||
&b.Series, &b.Volume, &b.Year,
|
||||
&b.Signed, &b.Description, &b.Notes,
|
||||
&b.OnLoan, &b.CoverURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Authors = strings.Split(authors, ";")
|
||||
books = append(books, b)
|
||||
}
|
||||
|
||||
@@ -167,7 +197,28 @@ func (m *MySQL) AddBook(ctx context.Context, b *book.Book) error {
|
||||
return fmt.Errorf("uninitialized mysql client")
|
||||
}
|
||||
|
||||
res, err := m.connection.ExecContext(ctx, "INSERT INTO books (title) VALUES (?)", b.Title)
|
||||
res, err := m.connection.ExecContext(ctx, `
|
||||
INSERT INTO `+m.tableName+`
|
||||
(title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, series, volume, year, signed, description, notes, onloan, coverurl)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, )`,
|
||||
b.Title,
|
||||
strings.Join(b.Authors, ";"),
|
||||
b.SortAuthor,
|
||||
b.ISBN10,
|
||||
b.ISBN13,
|
||||
b.Format,
|
||||
b.Genre,
|
||||
b.Publisher,
|
||||
b.Series,
|
||||
b.Volume,
|
||||
b.Year,
|
||||
b.Signed,
|
||||
b.Description,
|
||||
b.Notes,
|
||||
b.OnLoan,
|
||||
b.CoverURL,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,8 +236,47 @@ func (m *MySQL) UpdateBook(ctx context.Context, old, new *book.Book) error {
|
||||
if m.connection == nil {
|
||||
return fmt.Errorf("uninitialized mysql client")
|
||||
}
|
||||
if old.ID != new.ID {
|
||||
return fmt.Errorf("cannot change book ID")
|
||||
}
|
||||
|
||||
res, err := m.connection.ExecContext(ctx, "UPDATE books SET title=? WHERE id=?", new.Title, old.ID)
|
||||
res, err := m.connection.ExecContext(ctx, `
|
||||
UPDATE `+m.tableName+`
|
||||
SET id=?
|
||||
title=?
|
||||
authors=?
|
||||
sortauthor=?
|
||||
isbn10=?
|
||||
isbn13=?
|
||||
format=?
|
||||
genre=?
|
||||
publisher=?
|
||||
series=?
|
||||
volume=?
|
||||
year=?
|
||||
signed=?
|
||||
description=?
|
||||
notes=?
|
||||
onloan=?
|
||||
coverurl=?
|
||||
WHERE id=?`,
|
||||
new.Title,
|
||||
strings.Join(new.Authors, ";"),
|
||||
new.SortAuthor,
|
||||
new.ISBN10,
|
||||
new.ISBN13,
|
||||
new.Format,
|
||||
new.Genre,
|
||||
new.Publisher,
|
||||
new.Series,
|
||||
new.Volume,
|
||||
new.Year,
|
||||
new.Signed,
|
||||
new.Description,
|
||||
new.Notes,
|
||||
new.OnLoan,
|
||||
new.CoverURL,
|
||||
old.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Reference in New Issue
Block a user