diff --git a/schema.go b/schema.go index 836a836..c7b5894 100644 --- a/schema.go +++ b/schema.go @@ -1,3 +1,22 @@ +/* +Package dbschema is used to keep the SQL schema up to date. + + func sqlProvider(dbName string, version int) (sql []byte, found bool) { + // read an SQL file and return the contents + return + } + + upgrader := dbschema.NewUpgrader() + upgrader.SetSqlCallback(sqlProvider) + + if err = upgrader.AddDatabase("127.0.0.1", 5432, "foo", "postgres", "password"); err != nil { + panic(err) + } + + if err = upgrader.Run(); err != nil { + panic(err) + } +*/ package dbschema import ( @@ -8,6 +27,7 @@ import ( "database/sql" ) +// An upgrader verifies the schema for one or more databases and upgrades them if possible. type Upgrader struct { databases map[string]Database logCallback func(string, string) diff --git a/upgrader.go b/upgrader.go index 13e7c38..820aa3f 100644 --- a/upgrader.go +++ b/upgrader.go @@ -1,6 +1,9 @@ package dbschema import ( + // External + "github.com/lib/pq" + // Standard "database/sql" "fmt" @@ -9,19 +12,49 @@ import ( func defaultCallback(topic, msg string) {// {{{ fmt.Printf("[%s] %s\n", topic, msg) }// }}} -func NewUpgrader(host string, port int, dbName, user, pass string) (upgrader Upgrader, err error) {// {{{ + +// NewUpgrader creates an upgrader with an empty list of databases. +func NewUpgrader() (upgrader Upgrader) {// {{{ upgrader.logCallback = defaultCallback upgrader.databases = map[string]Database{} return }// }}} +// SetLogCallback allows to set a callback for custom logging. func (upgrader *Upgrader) SetLogCallback(callback func(string, string)) {// {{{ upgrader.logCallback = callback }// }}} +// SetSqlCallback is required for providing the SQL schema updates. func (upgrader *Upgrader) SetSqlCallback(callback func(string, int) ([]byte, bool)) {// {{{ upgrader.sqlCallback = callback }// }}} +func (dbase Database) createSchemaTable() (err error) {// {{{ + dbase.upgrader.logCallback("create", fmt.Sprintf("%s, _db.schema", dbase.DbName)) + _, err = dbase.db.Exec(`CREATE SCHEMA "_db"`) + + // Error code 42P06 "duplicate_schema" is an OK error, + // table can still be missing and created. + pqErr, _ := err.(*pq.Error) + if pqErr != nil && pqErr.Code != "42P06" { + return + } + + _, err = dbase.db.Exec(` + CREATE TABLE "_db"."schema" ( + version int4 NOT NULL, + updated timestamp NOT NULL DEFAULT NOW(), + + CONSTRAINT schema_pk PRIMARY KEY (version) + )`, + ) + return +}// }}} +func (dbase Database) appendSchemaVersion(version int) (err error) {// {{{ + _, err = dbase.db.Exec(`INSERT INTO _db.schema(version) VALUES($1)`, version) + return +}// }}} + func (dbase Database) verifySchemaTable() (err error) {// {{{ var rows *sql.Rows if rows, err = dbase.db.Query( @@ -41,37 +74,21 @@ func (dbase Database) verifySchemaTable() (err error) {// {{{ return } - if !exists { - dbase.upgrader.logCallback("create", fmt.Sprintf("%s, _db.schema", dbase.DbName)) - dbase.db.Exec(`CREATE SCHEMA "_db"`) - - if _, err = dbase.db.Exec(` - CREATE TABLE "_db"."schema" ( - version int4 NOT NULL, - updated timestamp NOT NULL DEFAULT NOW(), - - CONSTRAINT schema_pk PRIMARY KEY (version) - )`, - ); err != nil { - return - } + if exists { + return } + err = dbase.createSchemaTable() return }// }}} func (dbase Database) verifySchemaEntry() (err error) {// {{{ - var rows *sql.Rows - rows, err = dbase.db.Query(`SELECT version FROM _db.schema LIMIT 1`) - if err != nil { - return - } - defer rows.Close() + var version int + var row *sql.Row + row = dbase.db.QueryRow(`SELECT version FROM _db.schema LIMIT 1`) - if !rows.Next() { + err = row.Scan(&version) + if err == sql.ErrNoRows { dbase.upgrader.logCallback("initiate version", dbase.DbName) - _, err = dbase.db.Exec(`INSERT INTO _db.schema(version) VALUES(0)`) - if err != nil { - return - } + err = dbase.appendSchemaVersion(0) } return @@ -94,6 +111,7 @@ func (dbase Database) version() (version int, err error) {// {{{ return }// }}} +// AddDatabase sets a database up for the Run() function with verifying/creating the _db.schema table. func (upgrader Upgrader) AddDatabase(host string, port int, dbName, user, pass string) (err error) {// {{{ var db Database if db, err = newDatabase(host, port, dbName, user, pass); err != nil { @@ -110,6 +128,7 @@ func (upgrader Upgrader) AddDatabase(host string, port int, dbName, user, pass s err = db.verifySchemaEntry() return }// }}} +// Run executes the actual schema updates until there are no more available. func (upgrader Upgrader) Run() (err error) {// {{{ var version int @@ -131,11 +150,7 @@ func (upgrader Upgrader) Run() (err error) {// {{{ if _, err = dbase.db.Exec(string(sql)); err != nil { return } - _, err = dbase.db.Exec(` - INSERT INTO _db.schema(version) - VALUES($1) - `, version) - if err != nil { + if err = dbase.appendSchemaVersion(version); err != nil { return } }