From e4aeaf2419d60812f2b546fc9337d409b64b55ff Mon Sep 17 00:00:00 2001 From: Denis Shkrut Date: Mon, 20 Apr 2020 19:09:33 +0300 Subject: [PATCH 1/5] Update init script --- .gitignore | 2 +- README.md | 19 +++++++++++++++++-- entry.go | 12 ++---------- init.sh | 14 +++++++++++++- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index e0c2c8a..2b33c85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea .env -.docker-compose \ No newline at end of file +docker-compose.yml \ No newline at end of file diff --git a/README.md b/README.md index f9084ed..39bec9c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## Overview -Simple module for using migrations in your project +A simple module for using migrations in your project `Now only for MySQL and Postgres!` @@ -21,15 +21,30 @@ for update use flag `-u`: Run this command for put to your project the template for usage go-migrations: ``` bash $GOPATH/src/github.com/ShkrutDenis/go-migrations/init.sh + or if you use vendor folder + bash vendor/github.com/ShkrutDenis/go-migrations/init.sh +``` + +Or you can copy sources from your dependencies path manually if you have trouble with command. +For example from: +``` + .../github.com/ShkrutDenis/go-migrations/template ``` In `migrations/list` directory create your migrations like existed example In `migrations/entry.go` in `getMigrationsList()` method put your migrations structures +For migrate: +``` + go run migrations/entry.go +``` + +If you want to rollback, add `--rollback` flag. + #### Environment variables -Module use next variables for creating a connection with DB: +Module uses next variables for creating a connection with DB: - DB_DRIVER - DB_USER diff --git a/entry.go b/entry.go index db232af..a64bd45 100644 --- a/entry.go +++ b/entry.go @@ -55,17 +55,9 @@ func rollBack() { } func upOrIgnore(migration store.Migratable) { - var raw model.Migration - var err error - if firstRun { - goto run + if !firstRun && model.MigrationExist(connection, migration.GetName()) { + return } - err = connection.Get(&raw, "SELECT * FROM migrations WHERE name=?", migration.GetName()) - if err != nil { - goto run - } - return -run: log.Println("Migrating", migration.GetName()) migration.Up(connection) model.AddMigrationRaw(connection, migration.GetName(), lastBatch+1) diff --git a/init.sh b/init.sh index d1a1c39..84e3a13 100644 --- a/init.sh +++ b/init.sh @@ -1 +1,13 @@ -cp -a "$GOPATH"/src/go-migrations/template/. ./ \ No newline at end of file +#!/bin/sh +if [ -d $GOPATH/src/go-migrations/template/ ] +then + cp -a $GOPATH/src/go-migrations/template/. ./ + exit 1 +fi +if [ -d vendor/go-migrations/template ] +then + cp -a vendor/src/go-migrations/template/. ./ + exit 1 +fi +echo "Dependency path not found" +exit 0 \ No newline at end of file From 14e6b12742da8526cd4bde3ffec5bb7c6de996a8 Mon Sep 17 00:00:00 2001 From: Denis Shkrut Date: Mon, 20 Apr 2020 19:11:06 +0300 Subject: [PATCH 2/5] Update mysql builder: add using contracts. --- builder/contract/column.go | 29 ++++++++ builder/contract/foreign_kay.go | 14 ++++ builder/contract/table.go | 16 ++++ builder/contract/unique_key.go | 13 ++++ .../mysql/column/column.go | 57 ++++++++------ .../mysql/info/info.go | 0 .../mysql/key/foreign.go | 21 +++--- .../mysql/key/primary.go | 0 .../mysql/key/unique.go | 9 ++- .../mysql/table/table.go | 74 +++++++++++++------ 10 files changed, 175 insertions(+), 58 deletions(-) create mode 100644 builder/contract/column.go create mode 100644 builder/contract/foreign_kay.go create mode 100644 builder/contract/table.go create mode 100644 builder/contract/unique_key.go rename {query_builders => builder}/mysql/column/column.go (69%) rename {query_builders => builder}/mysql/info/info.go (100%) rename {query_builders => builder}/mysql/key/foreign.go (66%) rename {query_builders => builder}/mysql/key/primary.go (100%) rename {query_builders => builder}/mysql/key/unique.go (73%) rename {query_builders => builder}/mysql/table/table.go (68%) diff --git a/builder/contract/column.go b/builder/contract/column.go new file mode 100644 index 0000000..12c3919 --- /dev/null +++ b/builder/contract/column.go @@ -0,0 +1,29 @@ +package contract + +type Column interface { + Type(string) Column + Nullable() Column + NotNull() Column + Autoincrement() Column + NotAutoincrement() Column + Default(string) Column + Primary() Column + Unique() Column + NotUnique() Column + Drop() Column + Change() Column + First() Column + After(string) Column + Rename(string) Column + GetSQL() string + GetName() string + GetUniqueKeyName() string + IsPrimary() bool + IsUnique() bool + HasUniqueKey() bool + NeedUniqueKey() bool + NeedDropUniqueKey() bool + IsWaitingDrop() bool + IsWaitingRename() bool + IsWaitingChange() bool +} diff --git a/builder/contract/foreign_kay.go b/builder/contract/foreign_kay.go new file mode 100644 index 0000000..19a82b6 --- /dev/null +++ b/builder/contract/foreign_kay.go @@ -0,0 +1,14 @@ +package contract + +type ForeignKey interface { + Reference(string) ForeignKey + On(string) ForeignKey + OnUpdate(string) ForeignKey + OnDelete(string) ForeignKey + Drop() ForeignKey + SetKeyName(string) ForeignKey + GenerateKeyName() ForeignKey + GetSQL() string + GetName() string + ForDrop() bool +} diff --git a/builder/contract/table.go b/builder/contract/table.go new file mode 100644 index 0000000..cfd558e --- /dev/null +++ b/builder/contract/table.go @@ -0,0 +1,16 @@ +package contract + +type Table interface { + Column(string) Column + String(string, int) Column + Integer(string) Column + WithTimestamps() Table + RenameColumn(string, string) Column + DropColumn(string) Column + PrimaryKey(string) Column + ForeignKey(string) ForeignKey + DropForeignKey(string) Table + GetSQL() string + Exec() error + MustExec() +} diff --git a/builder/contract/unique_key.go b/builder/contract/unique_key.go new file mode 100644 index 0000000..2d1fd81 --- /dev/null +++ b/builder/contract/unique_key.go @@ -0,0 +1,13 @@ +package contract + +import "github.com/jmoiron/sqlx" + +type UniqueKey interface { + SetKeyName(string) UniqueKey + GenerateKeyName() UniqueKey + Drop() UniqueKey + GetSQL() string + Exec(*sqlx.DB) error + MustExec(*sqlx.DB) + GetName() string +} diff --git a/query_builders/mysql/column/column.go b/builder/mysql/column/column.go similarity index 69% rename from query_builders/mysql/column/column.go rename to builder/mysql/column/column.go index b204beb..23f35ac 100644 --- a/query_builders/mysql/column/column.go +++ b/builder/mysql/column/column.go @@ -2,7 +2,8 @@ package column import ( "fmt" - "github.com/ShkrutDenis/go-migrations/query_builders/mysql/info" + "github.com/ShkrutDenis/go-migrations/builder/contract" + "github.com/ShkrutDenis/go-migrations/builder/mysql/info" "github.com/jmoiron/sqlx" ) @@ -24,6 +25,8 @@ type Column struct { unique bool hasUniqueKey bool + isPrimaryKey bool + drop bool change bool first bool @@ -33,14 +36,14 @@ type Column struct { info *info.ColumnInfo } -func NewColumn(table, fieldName string, con *sqlx.DB) *Column { +func NewColumn(table, fieldName string, con *sqlx.DB) contract.Column { ci := info.GetColumnInfo(table, fieldName, con) c := &Column{name: fieldName, info: ci} c.init() return c } -func (c *Column) init() *Column { +func (c *Column) init() contract.Column { if c.info != nil { c.fieldType = c.info.ColumnType if c.info.Nullable() { @@ -57,68 +60,73 @@ func (c *Column) init() *Column { } // Functions for modify table -func (c *Column) Type(fieldType string) *Column { +func (c *Column) Type(fieldType string) contract.Column { c.fieldType = fieldType return c } -func (c *Column) Nullable() *Column { +func (c *Column) Nullable() contract.Column { c.nullable = null return c } -func (c *Column) NotNull() *Column { +func (c *Column) NotNull() contract.Column { c.nullable = notNull return c } -func (c *Column) Autoincrement() *Column { +func (c *Column) Autoincrement() contract.Column { c.autoincrement = true return c } -func (c *Column) NotAutoincrement() *Column { +func (c *Column) NotAutoincrement() contract.Column { c.autoincrement = false return c } -func (c *Column) Default(value string) *Column { +func (c *Column) Default(value string) contract.Column { c.hasDefault = value != "" c.defaultValue = value return c } -func (c *Column) Unique() *Column { +func (c *Column) Primary() contract.Column { + c.isPrimaryKey = true + return c +} + +func (c *Column) Unique() contract.Column { c.unique = true return c } -func (c *Column) NotUnique() *Column { +func (c *Column) NotUnique() contract.Column { c.unique = false return c } -func (c *Column) Drop() *Column { +func (c *Column) Drop() contract.Column { c.drop = true return c } -func (c *Column) Change() *Column { +func (c *Column) Change() contract.Column { c.change = true return c } -func (c *Column) First() *Column { +func (c *Column) First() contract.Column { c.first = true return c } -func (c *Column) After(name string) *Column { +func (c *Column) After(name string) contract.Column { c.after = name return c } -func (c *Column) Rename(name string) *Column { +func (c *Column) Rename(name string) contract.Column { c.rename = name return c } @@ -149,20 +157,21 @@ func (c *Column) changeColumnSQL() string { } func (c *Column) renameColumnSQL() string { - sql := fmt.Sprintf("change column %v %v", c.name, c.rename) - sql += c.columnOptionsSQL() - sql += c.columnPositionSQL() - return sql + ";" + return fmt.Sprintf("change column %v %v;", c.name, c.rename) } func (c *Column) dropColumnSQL() string { - return fmt.Sprintf("drop column %v;", c.name) + return fmt.Sprintf("drop column %v,", c.name) } func (c *Column) columnOptionsSQL() string { sql := " " + c.fieldType if c.hasDefault { - sql += " default " + c.defaultValue + if c.fieldType != "bool" && c.fieldType != "boolean" { + sql += fmt.Sprintf(" default '%v'", c.defaultValue) + } else { + sql += fmt.Sprintf(" default %v", c.defaultValue) + } } if c.autoincrement { sql += " auto_increment" @@ -201,6 +210,10 @@ func (c *Column) GetUniqueKeyName() string { return k.KeyName } +func (c *Column) IsPrimary() bool { + return c.isPrimaryKey +} + func (c *Column) IsUnique() bool { return c.unique } diff --git a/query_builders/mysql/info/info.go b/builder/mysql/info/info.go similarity index 100% rename from query_builders/mysql/info/info.go rename to builder/mysql/info/info.go diff --git a/query_builders/mysql/key/foreign.go b/builder/mysql/key/foreign.go similarity index 66% rename from query_builders/mysql/key/foreign.go rename to builder/mysql/key/foreign.go index 03c1dbd..ff18641 100644 --- a/query_builders/mysql/key/foreign.go +++ b/builder/mysql/key/foreign.go @@ -2,7 +2,8 @@ package key import ( "fmt" - "github.com/ShkrutDenis/go-migrations/query_builders/mysql/info" + "github.com/ShkrutDenis/go-migrations/builder/contract" + "github.com/ShkrutDenis/go-migrations/builder/mysql/info" ) type ForeignKey struct { @@ -18,11 +19,11 @@ type ForeignKey struct { change string } -func NewForeignKey(table, baseColumn string) *ForeignKey { +func NewForeignKey(table, baseColumn string) contract.ForeignKey { return &ForeignKey{baseTable: table, baseColumn: baseColumn, onDelete: "restrict", onUpdate: "restrict"} } -func NewForeignKeyByKeyInfo(ki *info.KeyInfo) *ForeignKey { +func NewForeignKeyByKeyInfo(ki *info.KeyInfo) contract.ForeignKey { return &ForeignKey{ name: ki.ConstraintName, baseTable: ki.TableName, @@ -32,37 +33,37 @@ func NewForeignKeyByKeyInfo(ki *info.KeyInfo) *ForeignKey { } } -func (fk *ForeignKey) Reference(table string) *ForeignKey { +func (fk *ForeignKey) Reference(table string) contract.ForeignKey { fk.targetTable = table return fk } -func (fk *ForeignKey) On(field string) *ForeignKey { +func (fk *ForeignKey) On(field string) contract.ForeignKey { fk.targetColumn = field return fk } -func (fk *ForeignKey) OnUpdate(action string) *ForeignKey { +func (fk *ForeignKey) OnUpdate(action string) contract.ForeignKey { fk.onUpdate = action return fk } -func (fk *ForeignKey) OnDelete(action string) *ForeignKey { +func (fk *ForeignKey) OnDelete(action string) contract.ForeignKey { fk.onDelete = action return fk } -func (fk *ForeignKey) Drop() *ForeignKey { +func (fk *ForeignKey) Drop() contract.ForeignKey { fk.drop = true return fk } -func (fk *ForeignKey) SetKeyName(name string) *ForeignKey { +func (fk *ForeignKey) SetKeyName(name string) contract.ForeignKey { fk.name = name return fk } -func (fk *ForeignKey) GenerateKeyName() *ForeignKey { +func (fk *ForeignKey) GenerateKeyName() contract.ForeignKey { fk.name = fmt.Sprintf("%v_%v_%v_fk", fk.baseTable, fk.targetTable, fk.targetColumn) return fk } diff --git a/query_builders/mysql/key/primary.go b/builder/mysql/key/primary.go similarity index 100% rename from query_builders/mysql/key/primary.go rename to builder/mysql/key/primary.go diff --git a/query_builders/mysql/key/unique.go b/builder/mysql/key/unique.go similarity index 73% rename from query_builders/mysql/key/unique.go rename to builder/mysql/key/unique.go index 79e8a0b..a1d638a 100644 --- a/query_builders/mysql/key/unique.go +++ b/builder/mysql/key/unique.go @@ -2,6 +2,7 @@ package key import ( "fmt" + "github.com/ShkrutDenis/go-migrations/builder/contract" "github.com/jmoiron/sqlx" ) @@ -13,22 +14,22 @@ type UniqueKey struct { drop bool } -func NewUniqueKey(table, field string) *UniqueKey { +func NewUniqueKey(table, field string) contract.UniqueKey { uk := &UniqueKey{table: table, field: field} return uk } -func (uk *UniqueKey) SetKeyName(name string) *UniqueKey { +func (uk *UniqueKey) SetKeyName(name string) contract.UniqueKey { uk.name = name return uk } -func (uk *UniqueKey) GenerateKeyName() *UniqueKey { +func (uk *UniqueKey) GenerateKeyName() contract.UniqueKey { uk.name = fmt.Sprintf("%v_%v_uindex", uk.table, uk.field) return uk } -func (uk *UniqueKey) Drop() *UniqueKey { +func (uk *UniqueKey) Drop() contract.UniqueKey { uk.drop = true return uk } diff --git a/query_builders/mysql/table/table.go b/builder/mysql/table/table.go similarity index 68% rename from query_builders/mysql/table/table.go rename to builder/mysql/table/table.go index 5f3af97..ca589a8 100644 --- a/query_builders/mysql/table/table.go +++ b/builder/mysql/table/table.go @@ -2,22 +2,25 @@ package table import ( "fmt" - "github.com/ShkrutDenis/go-migrations/query_builders/mysql/column" - "github.com/ShkrutDenis/go-migrations/query_builders/mysql/info" - "github.com/ShkrutDenis/go-migrations/query_builders/mysql/key" + "github.com/ShkrutDenis/go-migrations/builder/contract" + "github.com/ShkrutDenis/go-migrations/builder/mysql/column" + "github.com/ShkrutDenis/go-migrations/builder/mysql/info" + "github.com/ShkrutDenis/go-migrations/builder/mysql/key" "github.com/jmoiron/sqlx" + "log" "strconv" "strings" ) type Table struct { name string + newName string primaryKey *key.PrimaryKey - foreignKeys []*key.ForeignKey - uniqueKeys []*key.UniqueKey + foreignKeys []contract.ForeignKey + uniqueKeys []contract.UniqueKey - columns []*column.Column + columns []contract.Column timestamps bool drop bool @@ -26,26 +29,30 @@ type Table struct { connect *sqlx.DB } -func NewTable(name string, con *sqlx.DB) *Table { +func NewTable(name string, con *sqlx.DB) contract.Table { return &Table{name: name, connect: con.Unsafe()} } -func DropTable(name string, con *sqlx.DB) *Table { +func DropTable(name string, con *sqlx.DB) contract.Table { return &Table{name: name, drop: true, connect: con.Unsafe()} } -func ChangeTable(name string, con *sqlx.DB) *Table { +func ChangeTable(name string, con *sqlx.DB) contract.Table { return &Table{name: name, change: true, connect: con.Unsafe()} } +func RenameTable(oldName, newName string, con *sqlx.DB) contract.Table { + return &Table{name: oldName, newName: newName, connect: con.Unsafe()} +} + // Functions for table columns -func (t *Table) Column(name string) *column.Column { +func (t *Table) Column(name string) contract.Column { c := column.NewColumn(t.name, name, t.connect) t.columns = append(t.columns, c) return c } -func (t *Table) String(name string, length int) *column.Column { +func (t *Table) String(name string, length int) contract.Column { if length < 0 { length = 255 } @@ -53,12 +60,12 @@ func (t *Table) String(name string, length int) *column.Column { return c } -func (t *Table) Integer(name string) *column.Column { +func (t *Table) Integer(name string) contract.Column { c := t.Column(name).Type("int") return c } -func (t *Table) WithTimestamps() *Table { +func (t *Table) WithTimestamps() contract.Table { t.timestamps = true return t } @@ -68,29 +75,32 @@ func (t *Table) getTimeStampsSQL() string { "updated_at datetime default current_timestamp ON UPDATE current_timestamp not null" } -func (t *Table) RenameColumn(oldName, newName string) *column.Column { +func (t *Table) RenameColumn(oldName, newName string) contract.Column { c := t.Column(oldName).Rename(newName) return c } -func (t *Table) DropColumn(name string) *column.Column { +func (t *Table) DropColumn(name string) contract.Column { c := t.Column(name).Drop() return c } // Functions for keys -func (t *Table) PrimaryKey(Column string) *Table { - t.primaryKey = key.NewPrimaryKey(t.name, Column).GenerateKeyName() - return t +func (t *Table) PrimaryKey(Column string) contract.Column { + if ok, c := t.hasColumn(Column); ok { + return c.Primary() + } else { + return t.Integer("id").Autoincrement().Primary() + } } -func (t *Table) ForeignKey(Column string) *key.ForeignKey { +func (t *Table) ForeignKey(Column string) contract.ForeignKey { k := key.NewForeignKey(t.name, Column) t.foreignKeys = append(t.foreignKeys, k) return k } -func (t *Table) DropForeignKey(name string) *Table { +func (t *Table) DropForeignKey(name string) contract.Table { if !t.change { return t } @@ -107,6 +117,9 @@ func (t *Table) GetSQL() string { if t.drop { return t.dropTableSQL() } + if t.newName != "" { + return t.renameTableSQL() + } sql := "" for _, k := range t.foreignKeys { if k.ForDrop() { @@ -125,6 +138,9 @@ func (t *Table) createTableSQL() string { for _, c := range t.columns { sql += c.GetSQL() + "," t.checkUniqueKey(c) + if c.IsPrimary() { + t.primaryKey = key.NewPrimaryKey(t.name, c.GetName()).GenerateKeyName() + } } if t.timestamps { sql += t.getTimeStampsSQL() + "," @@ -175,7 +191,7 @@ func (t *Table) changeTableSQL() string { fKeys += "add " + k.GetSQL() + "," } if forDrop != "" { - forDrop = base + forDrop + forDrop = base + strings.TrimRight(forDrop, ",") + ";" } if forRename != "" { forRename = base + forRename + ";" @@ -196,6 +212,10 @@ func (t *Table) dropTableSQL() string { return fmt.Sprintf("DROP TABLE %v;", t.name) } +func (t *Table) renameTableSQL() string { + return fmt.Sprintf("RENAME TABLE %v TO %v;", t.name, t.newName) +} + // Execution functions func (t *Table) Exec() error { queries := strings.Split(t.GetSQL(), ";") @@ -224,6 +244,7 @@ func (t *Table) MustExec() { if q == "" { break } + log.Println(q) t.connect.MustExec(q + ";") } @@ -233,7 +254,7 @@ func (t *Table) MustExec() { } // Helpful functions -func (t *Table) checkUniqueKey(c *column.Column) { +func (t *Table) checkUniqueKey(c contract.Column) { if c.NeedUniqueKey() { k := key.NewUniqueKey(t.name, c.GetName()).GenerateKeyName() t.uniqueKeys = append(t.uniqueKeys, k) @@ -243,3 +264,12 @@ func (t *Table) checkUniqueKey(c *column.Column) { t.uniqueKeys = append(t.uniqueKeys, k) } } + +func (t *Table) hasColumn(name string) (bool, contract.Column) { + for _, c := range t.columns { + if c.GetName() == name { + return true, c + } + } + return false, nil +} From 9809bad526ad608eac84de35c98dcb15aa6bf39d Mon Sep 17 00:00:00 2001 From: Denis Shkrut Date: Mon, 20 Apr 2020 19:11:41 +0300 Subject: [PATCH 3/5] Add builder for Postgres --- builder/builder.go | 53 ++++++ builder/postgress/column/column.go | 246 ++++++++++++++++++++++++++ builder/postgress/info/info.go | 132 ++++++++++++++ builder/postgress/key/foreign.go | 89 ++++++++++ builder/postgress/key/unique.go | 62 +++++++ builder/postgress/table/table.go | 270 +++++++++++++++++++++++++++++ 6 files changed, 852 insertions(+) create mode 100644 builder/builder.go create mode 100644 builder/postgress/column/column.go create mode 100644 builder/postgress/info/info.go create mode 100644 builder/postgress/key/foreign.go create mode 100644 builder/postgress/key/unique.go create mode 100644 builder/postgress/table/table.go diff --git a/builder/builder.go b/builder/builder.go new file mode 100644 index 0000000..0762fee --- /dev/null +++ b/builder/builder.go @@ -0,0 +1,53 @@ +package builder + +import ( + "github.com/ShkrutDenis/go-migrations/builder/contract" + mysql "github.com/ShkrutDenis/go-migrations/builder/mysql/table" + postgres "github.com/ShkrutDenis/go-migrations/builder/postgress/table" + "github.com/jmoiron/sqlx" + "os" +) + +func NewTable(name string, con *sqlx.DB) contract.Table { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return mysql.NewTable(name, con) + case "postgres": + return postgres.NewTable(name, con) + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func DropTable(name string, con *sqlx.DB) contract.Table { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return mysql.DropTable(name, con) + case "postgres": + return postgres.DropTable(name, con) + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func ChangeTable(name string, con *sqlx.DB) contract.Table { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return mysql.ChangeTable(name, con) + case "postgres": + return postgres.ChangeTable(name, con) + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func RenameTable(oldName, newName string, con *sqlx.DB) contract.Table { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return mysql.RenameTable(oldName, newName, con) + case "postgres": + return postgres.RenameTable(oldName, newName, con) + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} diff --git a/builder/postgress/column/column.go b/builder/postgress/column/column.go new file mode 100644 index 0000000..f1cd298 --- /dev/null +++ b/builder/postgress/column/column.go @@ -0,0 +1,246 @@ +package column + +import ( + "fmt" + "github.com/ShkrutDenis/go-migrations/builder/contract" + "github.com/ShkrutDenis/go-migrations/builder/postgress/info" + "github.com/jmoiron/sqlx" + "strings" +) + +const ( + null = "NULL" + notNull = "NOT NULL" +) + +type Column struct { + name string + fieldType string + + hasDefault bool + defaultValue string + + nullable string + autoincrement bool + + isPrimaryKey bool + + unique bool + hasUniqueKey bool + + drop bool + change bool + first bool + after string + rename string + + info *info.ColumnInfo +} + +func NewColumn(table, fieldName string, con *sqlx.DB) contract.Column { + ci := info.GetColumnInfo(table, fieldName, con) + c := &Column{name: fieldName, info: ci} + c.init() + return c +} + +func (c *Column) init() contract.Column { + if c.info != nil { + c.fieldType = c.info.GetType() + if c.info.Nullable() { + c.nullable = null + } else { + c.nullable = notNull + } + c.unique = c.info.IsUnique() + c.hasUniqueKey = c.unique + c.hasDefault, c.defaultValue = c.info.HasDefault() + c.autoincrement = c.info.IsPrimary() + } + return c +} + +// Functions for modify table +func (c *Column) Type(fieldType string) contract.Column { + c.fieldType = fieldType + return c +} + +func (c *Column) Nullable() contract.Column { + c.nullable = null + return c +} + +func (c *Column) NotNull() contract.Column { + c.nullable = notNull + return c +} + +func (c *Column) Autoincrement() contract.Column { + c.autoincrement = true + return c +} + +func (c *Column) NotAutoincrement() contract.Column { + c.autoincrement = false + return c +} + +func (c *Column) Default(value string) contract.Column { + c.hasDefault = value != "" + c.defaultValue = value + return c +} + +func (c *Column) Primary() contract.Column { + c.isPrimaryKey = true + return c +} + +func (c *Column) Unique() contract.Column { + c.unique = true + return c +} + +func (c *Column) NotUnique() contract.Column { + c.unique = false + return c +} + +func (c *Column) Drop() contract.Column { + c.drop = true + return c +} + +func (c *Column) Change() contract.Column { + c.change = true + return c +} + +func (c *Column) First() contract.Column { + c.first = true + return c +} + +func (c *Column) After(name string) contract.Column { + c.after = name + return c +} + +func (c *Column) Rename(name string) contract.Column { + c.rename = name + return c +} + +// Functions for generate SQL +func (c *Column) GetSQL() string { + if c.drop { + return c.dropColumnSQL() + } + if c.rename != "" { + return c.renameColumnSQL() + } + if c.change { + return c.changeColumnSQL() + } + return c.addColumnSQL() +} + +func (c *Column) addColumnSQL() string { + return c.name + c.columnOptionsSQL() + c.columnPositionSQL() +} + +func (c *Column) changeColumnSQL() string { + if c.info == nil { + panic(fmt.Sprintf("Column %v not found. Column name is correct?", c.name)) + } + var sql string + sql += fmt.Sprintf("ALTER COLUMN %v DROP DEFAULT,", c.name) + if c.fieldType != c.info.GetType() { + sql += fmt.Sprintf("ALTER COLUMN %v TYPE %v USING %v::%v,", c.name, c.fieldType, c.name, c.fieldType) + } + sql += fmt.Sprintf("ALTER COLUMN %v SET DEFAULT '%v',", c.name, c.defaultValue) + if c.info.Nullable() && c.nullable == notNull { + sql += fmt.Sprintf("ALTER COLUMN %v SET NOT NULL,", c.name) + } else if !c.info.Nullable() && c.nullable == null { + sql += fmt.Sprintf("ALTER COLUMN %v DROP NOT NULL,", c.name) + } + return strings.TrimRight(sql, ",") +} + +func (c *Column) renameColumnSQL() string { + return fmt.Sprintf("rename column %v to %v;", c.name, c.rename) +} + +func (c *Column) dropColumnSQL() string { + return fmt.Sprintf("drop column %v,", c.name) +} + +func (c *Column) columnOptionsSQL() string { + if c.isPrimaryKey { + return " serial primary key" + } + sql := " " + c.fieldType + + if c.hasDefault { + sql += fmt.Sprintf(" default '%v'", c.defaultValue) + } + if c.nullable == "" { + c.nullable = notNull + } + sql += " " + c.nullable + return sql +} + +func (c *Column) columnPositionSQL() string { + // TODO:Postgres don`t support positioning + return "" +} + +// Helpful functions +func (c *Column) GetName() string { + return c.name +} + +func (c *Column) GetUniqueKeyName() string { + if c.info == nil { + return "" + } + k := c.info.GetUniqueKey() + if k == nil { + return "" + } + return k.KeyName +} + +func (c *Column) IsPrimary() bool { + return c.isPrimaryKey +} + +func (c *Column) IsUnique() bool { + return c.unique +} + +func (c *Column) HasUniqueKey() bool { + return c.hasUniqueKey +} + +func (c *Column) NeedUniqueKey() bool { + return c.unique && !c.hasUniqueKey +} + +func (c *Column) NeedDropUniqueKey() bool { + return !c.unique && c.hasUniqueKey +} + +func (c *Column) IsWaitingDrop() bool { + return c.drop +} + +func (c *Column) IsWaitingRename() bool { + return c.rename != "" +} + +func (c *Column) IsWaitingChange() bool { + return c.change +} diff --git a/builder/postgress/info/info.go b/builder/postgress/info/info.go new file mode 100644 index 0000000..2cb39c5 --- /dev/null +++ b/builder/postgress/info/info.go @@ -0,0 +1,132 @@ +package info + +import ( + "database/sql" + "fmt" + "github.com/jmoiron/sqlx" + "strings" +) + +type ColumnInfo struct { + ColumnDefault sql.NullString `db:"column_default"` + IsNullable string `db:"is_nullable"` + ColumnType string `db:"data_type"` + CharacterLength int `db:"character_maximum_length"` + ColumnKeysInfo []*IndexInfo +} + +func (ci *ColumnInfo) Nullable() bool { + return ci.IsNullable == "YES" +} + +func (ci *ColumnInfo) HasDefault() (bool, string) { + if ci.ColumnDefault.String == "" { + return false, "" + } + if strings.Contains(ci.ColumnDefault.String, "'") { + return true, strings.Split(ci.ColumnDefault.String, "'")[1] + } + return true, ci.ColumnDefault.String +} + +func (ci *ColumnInfo) IsUnique() bool { + for _, k := range ci.ColumnKeysInfo { + if k.IsUnique() { + return true + } + } + return false +} + +func (ci *ColumnInfo) GetUniqueKey() *IndexInfo { + for _, k := range ci.ColumnKeysInfo { + if k.IsUnique() { + return k + } + } + return nil +} + +func (ci *ColumnInfo) IsPrimary() bool { + for _, k := range ci.ColumnKeysInfo { + if k.IsPrimary() { + return true + } + } + return false +} + +func (ci *ColumnInfo) GetType() string { + if ci.ColumnType == "character varying" { + return fmt.Sprintf("varchar(%v)", ci.CharacterLength) + } + return ci.ColumnType +} + +func GetColumnInfo(table, column string, db *sqlx.DB) *ColumnInfo { + ci := &ColumnInfo{} + _ = db.Get(ci, "SELECT column_default, is_nullable, data_type, character_maximum_length FROM information_schema.columns"+ + " WHERE table_name=$1 AND column_name=$2;", + table, column) + if ci.ColumnType == "" { + return nil + } + ci.ColumnKeysInfo = GetIndexInfoByColumn(table, column, db) + return ci +} + +type IndexInfo struct { + KeyName string `db:"constraint_name"` + KeyType string `db:"constraint_type"` +} + +func (ii *IndexInfo) IsUnique() bool { + return ii.KeyType == "UNIQUE" +} + +func (ii *IndexInfo) IsPrimary() bool { + return ii.KeyType == "PRIMARY KEY" +} + +func (ii *IndexInfo) IsForeign() bool { + return ii.KeyType == "FOREIGN KEY" +} + +func GetIndexInfoByColumn(table, column string, db *sqlx.DB) []*IndexInfo { + var ii []*IndexInfo + _ = db.Select(&ii, "SELECT ccu.constraint_name, tc.constraint_type FROM information_schema.constraint_column_usage as ccu"+ + " INNER JOIN information_schema.table_constraints as tc ON tc.constraint_name = ccu.constraint_name "+ + " WHERE ccu.table_name = $1 AND ccu.column_name = $2;", table, column) + return ii +} + +func GetIndexInfoByTable(table string, db *sqlx.DB) []*IndexInfo { + var ii []*IndexInfo + _ = db.Select(&ii, "SELECT ccu.constraint_name, tc.constraint_type FROM information_schema.constraint_column_usage as ccu"+ + " INNER JOIN information_schema.table_constraints as tc ON tc.constraint_name = ccu.constraint_name "+ + " WHERE ccu.table_name = $1;", table) + return ii +} + +type KeyInfo struct { + ConstraintName string `db:"constraint_name"` + BaseTable string `db:"base_table"` + BaseColumn string `db:"base_column"` + TargetTable string `db:"target_table"` + TargetColumn string `db:"target_column"` + OnUpdate string `db:"update_rule"` + OnDelete string `db:"delete_rule"` +} + +func GetKeyInfo(name string, db *sqlx.DB) *KeyInfo { + var ki []*KeyInfo + _ = db.Select(&ki, "SELECT rc.constraint_name,update_rule,delete_rule, kcu.column_name as base_column, kcu.table_name as base_table, ccu.column_name as target_column, ccu.table_name as target_table"+ + " FROM information_schema.referential_constraints as rc"+ + " INNER JOIN information_schema.key_column_usage as kcu ON kcu.constraint_name = rc.constraint_name"+ + " INNER JOIN information_schema.constraint_column_usage as ccu ON ccu.constraint_name = rc.constraint_name"+ + " WHERE rc.constraint_name = $1;", name) + if len(ki) < 1 { + return nil + } + return ki[0] +} diff --git a/builder/postgress/key/foreign.go b/builder/postgress/key/foreign.go new file mode 100644 index 0000000..f1cc57a --- /dev/null +++ b/builder/postgress/key/foreign.go @@ -0,0 +1,89 @@ +package key + +import ( + "fmt" + "github.com/ShkrutDenis/go-migrations/builder/contract" + "github.com/ShkrutDenis/go-migrations/builder/postgress/info" +) + +type ForeignKey struct { + name string + baseTable string + baseColumn string + targetTable string + targetColumn string + onDelete string + onUpdate string + + drop bool + change string +} + +func NewForeignKey(table, baseColumn string) contract.ForeignKey { + return &ForeignKey{baseTable: table, baseColumn: baseColumn, onDelete: "NO ACTION", onUpdate: "NO ACTION"} +} + +func NewForeignKeyByKeyInfo(ki *info.KeyInfo) contract.ForeignKey { + return &ForeignKey{ + name: ki.ConstraintName, + baseTable: ki.BaseTable, + baseColumn: ki.BaseColumn, + targetTable: ki.TargetTable, + targetColumn: ki.TargetColumn, + onUpdate: ki.OnUpdate, + onDelete: ki.OnDelete, + } +} + +func (fk *ForeignKey) Reference(table string) contract.ForeignKey { + fk.targetTable = table + return fk +} + +func (fk *ForeignKey) On(field string) contract.ForeignKey { + fk.targetColumn = field + return fk +} + +func (fk *ForeignKey) OnUpdate(action string) contract.ForeignKey { + fk.onUpdate = action + return fk +} + +func (fk *ForeignKey) OnDelete(action string) contract.ForeignKey { + fk.onDelete = action + return fk +} + +func (fk *ForeignKey) Drop() contract.ForeignKey { + fk.drop = true + return fk +} + +func (fk *ForeignKey) SetKeyName(name string) contract.ForeignKey { + fk.name = name + return fk +} + +func (fk *ForeignKey) GenerateKeyName() contract.ForeignKey { + fk.name = fmt.Sprintf("%v_%v_%v_fkey", fk.baseTable, fk.targetTable, fk.targetColumn) + return fk +} + +func (fk *ForeignKey) GetSQL() string { + if fk.drop { + return fmt.Sprintf("ALTER TABLE %v DROP CONSTRAINT %v;", + fk.baseTable, fk.name) + } + return fmt.Sprintf("constraint %v foreign key (%v) references %v (%v) on update %v on delete %v", + fk.name, fk.baseColumn, fk.targetTable, fk.targetColumn, fk.onUpdate, fk.onDelete) +} + +// Helpful functions +func (fk *ForeignKey) GetName() string { + return fk.name +} + +func (fk *ForeignKey) ForDrop() bool { + return fk.drop +} diff --git a/builder/postgress/key/unique.go b/builder/postgress/key/unique.go new file mode 100644 index 0000000..4db2197 --- /dev/null +++ b/builder/postgress/key/unique.go @@ -0,0 +1,62 @@ +package key + +import ( + "fmt" + "github.com/ShkrutDenis/go-migrations/builder/contract" + "github.com/jmoiron/sqlx" + "log" +) + +type UniqueKey struct { + name string + table string + field string + + drop bool +} + +func NewUniqueKey(table, field string) contract.UniqueKey { + uk := &UniqueKey{table: table, field: field} + return uk +} + +func (uk *UniqueKey) SetKeyName(name string) contract.UniqueKey { + uk.name = name + return uk +} + +func (uk *UniqueKey) GenerateKeyName() contract.UniqueKey { + uk.name = fmt.Sprintf("unique_%v_%v", uk.table, uk.field) + return uk +} + +func (uk *UniqueKey) Drop() contract.UniqueKey { + uk.drop = true + return uk +} + +func (uk *UniqueKey) GetSQL() string { + if uk.drop { + return fmt.Sprintf("ALTER TABLE %v DROP CONSTRAINT %v;", + uk.table, uk.name) + } + return fmt.Sprintf("Create unique index %v_%v on %v (%v);", + uk.table, uk.field, uk.table, uk.field) + + fmt.Sprintf(" ALter table %v add constraint %v unique using index %v_%v;", + uk.table, uk.name, uk.table, uk.field) +} + +func (uk *UniqueKey) Exec(con *sqlx.DB) error { + _, err := con.Exec(uk.GetSQL()) + return err +} + +func (uk *UniqueKey) MustExec(con *sqlx.DB) { + log.Println(uk.GetSQL()) + con.MustExec(uk.GetSQL()) +} + +// Helpful functions +func (uk *UniqueKey) GetName() string { + return uk.name +} diff --git a/builder/postgress/table/table.go b/builder/postgress/table/table.go new file mode 100644 index 0000000..30998d7 --- /dev/null +++ b/builder/postgress/table/table.go @@ -0,0 +1,270 @@ +package table + +import ( + "fmt" + "github.com/ShkrutDenis/go-migrations/builder/contract" + "github.com/ShkrutDenis/go-migrations/builder/postgress/column" + "github.com/ShkrutDenis/go-migrations/builder/postgress/info" + "github.com/ShkrutDenis/go-migrations/builder/postgress/key" + "github.com/jmoiron/sqlx" + "log" + "strconv" + "strings" +) + +type Table struct { + name string + newName string + + foreignKeys []contract.ForeignKey + uniqueKeys []contract.UniqueKey + + columns []contract.Column + timestamps bool + + drop bool + change bool + + connect *sqlx.DB +} + +func NewTable(name string, con *sqlx.DB) contract.Table { + return &Table{name: name, connect: con.Unsafe()} +} + +func DropTable(name string, con *sqlx.DB) contract.Table { + return &Table{name: name, drop: true, connect: con.Unsafe()} +} + +func ChangeTable(name string, con *sqlx.DB) contract.Table { + return &Table{name: name, change: true, connect: con.Unsafe()} +} + +func RenameTable(oldName, newName string, con *sqlx.DB) contract.Table { + return &Table{name: oldName, newName: newName, connect: con.Unsafe()} +} + +// Functions for table columns +func (t *Table) Column(name string) contract.Column { + c := column.NewColumn(t.name, name, t.connect) + t.columns = append(t.columns, c) + return c +} + +func (t *Table) String(name string, length int) contract.Column { + if length < 0 { + length = 255 + } + c := t.Column(name).Type("varchar(" + strconv.Itoa(length) + ")") + return c +} + +func (t *Table) Integer(name string) contract.Column { + c := t.Column(name).Type("int") + return c +} + +func (t *Table) WithTimestamps() contract.Table { + t.timestamps = true + return t +} + +func (t *Table) getTimeStampsSQL() string { + return "created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP," + + "updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP" +} + +func (t *Table) RenameColumn(oldName, newName string) contract.Column { + c := t.Column(oldName).Rename(newName) + return c +} + +func (t *Table) DropColumn(name string) contract.Column { + c := t.Column(name).Drop() + return c +} + +// Functions for keys +func (t *Table) PrimaryKey(Column string) contract.Column { + if ok, c := t.hasColumn(Column); ok { + return c.Primary() + } else { + return t.Integer("id").Primary() + } +} + +func (t *Table) ForeignKey(Column string) contract.ForeignKey { + k := key.NewForeignKey(t.name, Column) + t.foreignKeys = append(t.foreignKeys, k) + return k +} + +func (t *Table) DropForeignKey(name string) contract.Table { + if !t.change { + return t + } + ki := info.GetKeyInfo(name, t.connect) + if ki == nil { + panic("Foreign key " + name + " not exist. Foreign key name is correct?") + } + t.foreignKeys = append(t.foreignKeys, key.NewForeignKeyByKeyInfo(ki).Drop()) + return t +} + +// Generate SQL functions +func (t *Table) GetSQL() string { + if t.drop { + return t.dropTableSQL() + } + if t.newName != "" { + return t.renameTableSQL() + } + sql := "" + for _, k := range t.foreignKeys { + if k.ForDrop() { + sql += k.GetSQL() + } + } + if t.change { + return sql + t.changeTableSQL() + } + return sql + t.createTableSQL() +} + +func (t *Table) createTableSQL() string { + sql := "CREATE TABLE " + t.name + "(" + for _, c := range t.columns { + sql += c.GetSQL() + "," + t.checkUniqueKey(c) + } + if t.timestamps { + sql += t.getTimeStampsSQL() + "," + } + for _, k := range t.foreignKeys { + if k.ForDrop() { + continue + } + if k.GetName() == "" { + k.GenerateKeyName() + } + sql += k.GetSQL() + "," + } + return strings.TrimRight(sql, ",") + ");" +} + +func (t *Table) changeTableSQL() string { + base := "ALTER TABLE " + t.name + " " + var forAdd string + var forModify string + var forRename string + var forDrop string + var fKeys string + for _, c := range t.columns { + if c.IsWaitingDrop() { + forDrop += c.GetSQL() + continue + } + t.checkUniqueKey(c) + if c.IsWaitingRename() { + forRename += c.GetSQL() + continue + } + if c.IsWaitingChange() { + forModify += c.GetSQL() + "," + continue + } + forAdd += "add " + c.GetSQL() + "," + } + for _, k := range t.foreignKeys { + if k.ForDrop() { + continue + } + if k.GetName() == "" { + k.GenerateKeyName() + } + fKeys += "add " + k.GetSQL() + "," + } + if forDrop != "" { + forDrop = base + strings.TrimRight(forDrop, ",") + ";" + } + if forRename != "" { + forRename = base + forRename + ";" + } + if forModify != "" { + forModify = base + strings.TrimRight(forModify, ",") + ";" + } + if forAdd != "" { + forAdd = base + strings.TrimRight(forAdd, ",") + ";" + } + if fKeys != "" { + fKeys = base + strings.TrimRight(fKeys, ",") + ";" + } + return forRename + forDrop + forModify + forAdd + fKeys +} + +func (t *Table) dropTableSQL() string { + return fmt.Sprintf("DROP TABLE %v;", t.name) +} + +func (t *Table) renameTableSQL() string { + return fmt.Sprintf("ALTER TABLE %v RENAME TO %v;", t.name, t.newName) +} + +// Execution functions +func (t *Table) Exec() error { + queries := strings.Split(t.GetSQL(), ";") + for i, q := range queries { + if i == len(queries) { + break + } + _, err := t.connect.Exec(q + ";") + if err != nil { + return err + } + } + + for _, k := range t.uniqueKeys { + if err := k.Exec(t.connect); err != nil { + return err + } + } + + return nil +} + +func (t *Table) MustExec() { + queries := strings.Split(t.GetSQL(), ";") + for _, q := range queries { + if q == "" { + break + } + log.Println(q) + t.connect.MustExec(q + ";") + } + + for _, k := range t.uniqueKeys { + k.MustExec(t.connect) + } +} + +// Helpful functions +func (t *Table) checkUniqueKey(c contract.Column) { + if c.NeedUniqueKey() { + k := key.NewUniqueKey(t.name, c.GetName()).GenerateKeyName() + t.uniqueKeys = append(t.uniqueKeys, k) + } + if c.NeedDropUniqueKey() { + k := key.NewUniqueKey(t.name, c.GetName()).SetKeyName(c.GetUniqueKeyName()).Drop() + t.uniqueKeys = append(t.uniqueKeys, k) + } +} + +// Helpful functions +func (t *Table) hasColumn(name string) (bool, contract.Column) { + for _, c := range t.columns { + if c.GetName() == name { + return true, c + } + } + return false, nil +} From 111fbafe16506bbb043c85652ee9a052d3d18281 Mon Sep 17 00:00:00 2001 From: Denis Shkrut Date: Mon, 20 Apr 2020 19:12:05 +0300 Subject: [PATCH 4/5] Update migration model for using with different drivers --- model/migration.go | 115 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 7 deletions(-) diff --git a/model/migration.go b/model/migration.go index f8ae9c0..ea642d4 100644 --- a/model/migration.go +++ b/model/migration.go @@ -2,6 +2,7 @@ package model import ( "github.com/jmoiron/sqlx" + "os" "time" ) @@ -13,34 +14,134 @@ type Migration struct { } func CreateMigrationsTable(connection *sqlx.DB) bool { - _, err := connection.Exec("SELECT * FROM migrations LIMIT 1") + _, err := connection.Exec(checkTableExitSQL()) if err != nil { - connection.MustExec("CREATE TABLE migrations (id int auto_increment, name varchar(255) not null, batch int not null, created_at datetime default current_timestamp not null, constraint migrations_pk primary key (id));") + connection.MustExec(creteTableSQL()) return true } return false } func AddMigrationRaw(connection *sqlx.DB, migration string, lastBatch int) { - connection.MustExec("INSERT INTO migrations (name, batch) VALUES (?, ?)", migration, lastBatch) + connection.MustExec(addRawSQL(), migration, lastBatch) +} + +func MigrationExist(connection *sqlx.DB, name string) bool { + var raw Migration + err := connection.Get(&raw, getRawSQL(), name) + if err != nil { + return false + } + return true } func GetLastBatch(connection *sqlx.DB) int { var raw Migration - _ = connection.Get(&raw, "SELECT * FROM migrations ORDER BY batch DESC LIMIT 1") + _ = connection.Get(&raw, getLastBatchSQL()) return raw.Batch } func GetLastMigrations(connection *sqlx.DB, lastBatch int) []*Migration { var list []*Migration - _ = connection.Select(&list, "SELECT * FROM migrations WHERE batch=? ORDER BY created_at DESC, id DESC;", lastBatch) + err := connection.Select(&list, getLastRawSQL(), lastBatch) + if err != nil { + panic(err) + } return list } func RemoveMigrationRaw(connection *sqlx.DB, migration string) { - connection.MustExec("DELETE FROM migrations WHERE name=?", migration) + connection.MustExec(removeMigrationSQL(), migration) } func RemoveLastBatch(connection *sqlx.DB, lastBatch int) { - connection.MustExec("DELETE FROM migrations WHERE batch=?", lastBatch) + connection.MustExec(removeBatchSQL(), lastBatch) +} + +func checkTableExitSQL() string { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return "SELECT * FROM migrations LIMIT 1;" + case "postgres": + return "SELECT * FROM migrations LIMIT 1;" + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func creteTableSQL() string { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return "CREATE TABLE migrations (id int auto_increment, name varchar(255) not null, batch int not null, created_at datetime default current_timestamp not null, constraint migrations_pk primary key (id));" + case "postgres": + return "CREATE TABLE migrations (id serial PRIMARY KEY, name varchar(255) not null, batch int not null, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP not null);" + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func getRawSQL() string { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return "SELECT * FROM migrations WHERE name=?;" + case "postgres": + return "SELECT * FROM migrations WHERE name=$1;" + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func addRawSQL() string { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return "INSERT INTO migrations (name, batch) VALUES (?, ?);" + case "postgres": + return "INSERT INTO migrations (name, batch) VALUES ($1, $2);" + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func getLastBatchSQL() string { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return "SELECT * FROM migrations ORDER BY batch DESC LIMIT 1;" + case "postgres": + return "SELECT * FROM migrations ORDER BY batch DESC LIMIT 1;" + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func getLastRawSQL() string { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return "SELECT * FROM migrations WHERE batch=? ORDER BY created_at DESC, id DESC;" + case "postgres": + return "SELECT * FROM migrations WHERE batch=$1 ORDER BY created_at DESC, id DESC;" + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func removeBatchSQL() string { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return "DELETE FROM migrations WHERE batch=?;" + case "postgres": + return "DELETE FROM migrations WHERE batch=$1;" + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } +} + +func removeMigrationSQL() string { + switch os.Getenv("DB_DRIVER") { + case "mysql": + return "DELETE FROM migrations WHERE name=$1" + case "postgres": + return "" + default: + panic("Not supported DB driver: " + os.Getenv("DB_DRIVER")) + } } From fbc1c43e3db64fec2e25fbc90be40abf41b9721f Mon Sep 17 00:00:00 2001 From: Denis Shkrut Date: Mon, 20 Apr 2020 19:12:43 +0300 Subject: [PATCH 5/5] Update helpful comment in migration template --- template/migrations/list/1_create_example_table.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/template/migrations/list/1_create_example_table.go b/template/migrations/list/1_create_example_table.go index ac9a0bb..c269e76 100644 --- a/template/migrations/list/1_create_example_table.go +++ b/template/migrations/list/1_create_example_table.go @@ -16,12 +16,16 @@ func (m *CreateExampleTable) Up(con *sqlx.DB) { // Write your migration logic here // Example: // con.MustExec("CREATE TABLE example ( id int auto_increment, constraint migrations_pk primary key (id));") - // Or you can use existed query builder + // Or you can use existed query builder: + // import "github.com/ShkrutDenis/go-migrations/builder" + // builder.NewTable("example", con) } func (m *CreateExampleTable) Down(con *sqlx.DB) { // Write your migration rollback logic here // Example: // con.MustExec("DROP TABLE example;") - // Or you can use existed query builder + // Or you can use existed query builder: + // import "github.com/ShkrutDenis/go-migrations/builder" + // builder.NewTable("example", con) }