import crm migrations
This commit is contained in:
109
crm/db/migrate.go
Normal file
109
crm/db/migrate.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
_ "github.com/crusttech/crust/sam/db/mysql"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"github.com/titpetric/factory"
|
||||
)
|
||||
|
||||
//go:generate statik -p mysql -Z -f -src=schema/mysql
|
||||
|
||||
func statements(contents []byte, err error) ([]string, error) {
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return regexp.MustCompilePOSIX(";$").Split(string(contents), -1), nil
|
||||
}
|
||||
|
||||
type migration struct {
|
||||
Project string
|
||||
Filename string
|
||||
StatementIndex int
|
||||
Status string
|
||||
}
|
||||
|
||||
func Migrate(db *factory.DB) error {
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating statik filesystem")
|
||||
}
|
||||
|
||||
var files []string
|
||||
|
||||
fs.Walk(statikFS, "/", func(filename string, info os.FileInfo, err error) error {
|
||||
matched, err := filepath.Match("*.up.sql", filename)
|
||||
if err == nil && matched {
|
||||
files = append(files, filename)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
sort.Strings(files)
|
||||
|
||||
if len(files) == 0 {
|
||||
return errors.New("No files encoded for migration, need at least one SQL file")
|
||||
}
|
||||
|
||||
migrate := func(filename string, useLog bool) error {
|
||||
status := migration{
|
||||
Project: "crm",
|
||||
Filename: filename,
|
||||
}
|
||||
if useLog {
|
||||
if err := db.Get(&status, "select * from migrations where project=? and filename=?", status.Project, status.Filename); err != nil {
|
||||
return err
|
||||
}
|
||||
if status.Status == "ok" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
up := func() error {
|
||||
stmts, err := statements(fs.ReadFile(statikFS, filename))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Error reading migration %s", filename))
|
||||
}
|
||||
|
||||
log.Println("Running migration for", filename)
|
||||
for idx, query := range stmts {
|
||||
if strings.TrimSpace(query) != "" && idx >= status.StatementIndex {
|
||||
status.StatementIndex = idx
|
||||
if _, err := db.Exec(query); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Println("Migration done OK")
|
||||
status.Status = "ok"
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := db.Transaction(up); err != nil {
|
||||
status.Status = err.Error()
|
||||
if useLog {
|
||||
db.Replace("migrations", status)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := migrate("/migration.sql", false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, filename := range files {
|
||||
migrate(filename, true)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
42
crm/db/migrate_test.go
Normal file
42
crm/db/migrate_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"log"
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/titpetric/dockertest"
|
||||
"github.com/titpetric/factory"
|
||||
)
|
||||
|
||||
func TestMigrations(t *testing.T) {
|
||||
args := []string{
|
||||
// "--rm",
|
||||
"-e", "MYSQL_ROOT_PASSWORD=root",
|
||||
"-e", "MYSQL_DATABASE=test",
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
|
||||
defer cancel()
|
||||
|
||||
count := 0
|
||||
|
||||
mysql, err := dockertest.RunContainerContext(ctx, "titpetric/percona-xtrabackup", "3306", func(addr string) error {
|
||||
factory.Database.Add("default", "root:root@tcp("+addr+")/test?collation=utf8mb4_general_ci")
|
||||
log.Println(addr)
|
||||
_, err := factory.Database.Get()
|
||||
count++
|
||||
time.Sleep(time.Second)
|
||||
return err
|
||||
}, args...)
|
||||
defer mysql.Terminate() // Shutdown()
|
||||
if err != nil {
|
||||
t.Fatalf("Error starting mysql: %#v", err)
|
||||
}
|
||||
|
||||
db := factory.Database.MustGet()
|
||||
if err := Migrate(db); err != nil {
|
||||
t.Fatalf("Unexpected error: %#v", err)
|
||||
}
|
||||
}
|
||||
12
crm/db/mysql/statik.go
Normal file
12
crm/db/mysql/statik.go
Normal file
File diff suppressed because one or more lines are too long
@@ -1,3 +1,20 @@
|
||||
CREATE TABLE `crm_content` (
|
||||
`id` bigint(20) unsigned NOT NULL,
|
||||
`module_id` bigint(20) unsigned NOT NULL,
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
`archived_at` datetime DEFAULT NULL,
|
||||
`deleted_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`,`module_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `crm_content_columns` (
|
||||
`content_id` bigint(20) NOT NULL,
|
||||
`column_name` varchar(255) NOT NULL,
|
||||
`column_value` text NOT NULL,
|
||||
PRIMARY KEY (`content_id`,`column_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `crm_fields` (
|
||||
`field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',
|
||||
`field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',
|
||||
@@ -12,13 +29,6 @@ CREATE TABLE `crm_module` (
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `crm_module_content` (
|
||||
`id` bigint(20) unsigned NOT NULL,
|
||||
`module_id` bigint(20) unsigned NOT NULL,
|
||||
`json` json NOT NULL COMMENT 'Entry JSON blob based on module fields list',
|
||||
PRIMARY KEY (`id`,`module_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `crm_module_form` (
|
||||
`module_id` bigint(20) unsigned NOT NULL,
|
||||
`place` tinyint(3) unsigned NOT NULL,
|
||||
@@ -29,10 +39,3 @@ CREATE TABLE `crm_module_form` (
|
||||
PRIMARY KEY (`module_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
INSERT INTO `crm_fields` VALUES ('bool','Boolean value (yes / no)','');
|
||||
INSERT INTO `crm_fields` VALUES ('email','E-mail input','');
|
||||
INSERT INTO `crm_fields` VALUES ('enum','Single option picker','');
|
||||
INSERT INTO `crm_fields` VALUES ('hidden','Hidden value','');
|
||||
INSERT INTO `crm_fields` VALUES ('stamp','Date/time input','');
|
||||
INSERT INTO `crm_fields` VALUES ('text','Text input','');
|
||||
INSERT INTO `crm_fields` VALUES ('textarea','Text input (multi-line)','');
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
INSERT INTO `crm_fields` VALUES ('bool','Boolean value (yes / no)','');
|
||||
INSERT INTO `crm_fields` VALUES ('email','E-mail input','');
|
||||
INSERT INTO `crm_fields` VALUES ('enum','Single option picker','');
|
||||
INSERT INTO `crm_fields` VALUES ('hidden','Hidden value','');
|
||||
INSERT INTO `crm_fields` VALUES ('stamp','Date/time input','');
|
||||
INSERT INTO `crm_fields` VALUES ('text','Text input','');
|
||||
INSERT INTO `crm_fields` VALUES ('textarea','Text input (multi-line)','');
|
||||
7
crm/db/schema/mysql/migrations.sql
Normal file
7
crm/db/schema/mysql/migrations.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS `migrations` (
|
||||
`project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',
|
||||
`filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',
|
||||
`statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',
|
||||
`status` varchar(16) NOT NULL COMMENT 'ok or full error message'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
Reference in New Issue
Block a user