commit 358fed43eaec508099e64e1b799f6ad71127bf2b Author: NiseVoid Date: Fri Oct 21 20:01:00 2016 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd56d44 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bindata.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..18b197f --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2016, Fuyu +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b356ab --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Go database migrate + +This is a simple database migration library for Go. This library is intended to be used with go-bindata. +We recommended using a go generate command to update the go-bindata file. + +## Example + +`files/0001.sql`: + +```sql +CREATE TABLE tests ( + Name varchar(100) NOT NULL +); + +INSERT INTO tests VALUES ('migration test'); +``` + +Run `go-bindata --prefix files files` +*Optionally add the flags `-nomemcopy` and `-nometadata`* + +`main.go`: + +```go +package main + +import ( + "migrate" +) + +func main() { + db, err = sql.Open(`postgres`, `host=/run/postgresql dbname=testdb sslmode=disable`) + if err != nil { + fmt.Println(`Failed to connect to database. Message:`, err) + } + err = migrate.Migrate(db, 1, migrate.Options{Schema:`testschema`}, migrations.Asset} + if err != nil { + fmt.Println(`The migration failed! Message:`, err) + } + + fmt.Println(`The database migration/update was successful`) +} +``` + +## File names: + +The names of your migration files should look like this: + +``` +0001.sql +0002.sql +... +0010.sql +... +0325.sql +``` diff --git a/migrate.go b/migrate.go new file mode 100644 index 0000000..1a73772 --- /dev/null +++ b/migrate.go @@ -0,0 +1,92 @@ +// Package migrate allows you to update your database from your application +package migrate + +import ( + "database/sql" + "errors" + "fmt" + "strconv" +) + +// Options contains all settings +type Options struct { + TableName string // Name used for version info table; defaults to DefaultTableName if not set + Schema string // Schema used for version info table; For PostgreSQL, ignored if not set + AssetPrefix string +} + +// DefaultTableName is the name used when no TableName is specified in Options +const DefaultTableName = `version` + +// ErrUpdatesMissing indicates an update is missing, making it impossible to execute the migration +var ErrUpdatesMissing = errors.New(`Missing migration files`) + +// ErrDatabaseNewer indicates that the database version is newer than the requested version. We throw an error because downgrades might cause dataloss +var ErrDatabaseNewer = errors.New(`Current version is newer than the requested version`) + +const fileFormat = `%04d.sql` + +// AssetFunc is a function that returns the data for the given name +type AssetFunc func(string) ([]byte, error) + +// Migrate executes +func Migrate(db *sql.DB, version int, o Options, asset AssetFunc) error { + if o.TableName == `` { + o.TableName = DefaultTableName + } + + table := o.TableName + if o.Schema != `` { + table = o.Schema + `.` + table + } + + _, err := db.Exec(`CREATE TABLE IF NOT EXISTS ` + table + ` (Version integer NOT NULL PRIMARY KEY)`) + if err != nil { + return err + } + + row := db.QueryRow(`SELECT Version FROM ` + table + ` ORDER BY Version DESC`) + if err != nil { + return err + } + + var v int + err = row.Scan(&v) + if err != sql.ErrNoRows && err != nil { + return err + } + + if v > version { + return ErrDatabaseNewer + } + + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + for i := v + 1; i <= version; i++ { + asset, err := asset(fmt.Sprintf(o.AssetPrefix+fileFormat, version)) + if err != nil { + return ErrUpdatesMissing + } + + _, err = tx.Exec(string(asset)) + if err != nil { + return err + } + + _, err = tx.Exec(`INSERT INTO ` + table + ` VALUES (` + strconv.Itoa(i) + `)`) + if err != nil { + return err + } + } + + err = tx.Commit() + if err != nil { + return err + } + + return nil +}