Initial commit
This commit is contained in:
		
						commit
						f80de360ba
					
				
					 5 changed files with 263 additions and 0 deletions
				
			
		
							
								
								
									
										31
									
								
								database.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								database.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
package dbschema
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	// Standard
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newDatabase(host string, port int, dbName, user, pass string) (dbase Database, err error) {// {{{
 | 
			
		||||
	dbase.Host = host
 | 
			
		||||
	dbase.Port = port
 | 
			
		||||
	dbase.DbName = dbName
 | 
			
		||||
	dbase.Username = user
 | 
			
		||||
	dbase.Password = pass
 | 
			
		||||
 | 
			
		||||
	dbase.db, err = sql.Open("postgres", dbase.sqlConnString())
 | 
			
		||||
	return
 | 
			
		||||
}// }}}
 | 
			
		||||
 | 
			
		||||
func (dbase Database) sqlConnString() string {// {{{
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"postgresql://%s:%s@%s:%d/%s?sslmode=disable",
 | 
			
		||||
		dbase.Username,
 | 
			
		||||
		dbase.Password,
 | 
			
		||||
		dbase.Host,
 | 
			
		||||
		dbase.Port,
 | 
			
		||||
		dbase.DbName,
 | 
			
		||||
	)
 | 
			
		||||
}// }}}
 | 
			
		||||
 | 
			
		||||
// vim: foldmethod=marker
 | 
			
		||||
							
								
								
									
										5
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
module git.gibonuddevalla.se/go/dbschema
 | 
			
		||||
 | 
			
		||||
go 1.20
 | 
			
		||||
 | 
			
		||||
require github.com/lib/pq v1.10.9
 | 
			
		||||
							
								
								
									
										2
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 | 
			
		||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
			
		||||
							
								
								
									
										71
									
								
								schema.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								schema.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
package dbschema
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	// External
 | 
			
		||||
	_ "github.com/lib/pq"
 | 
			
		||||
 | 
			
		||||
	// Standard
 | 
			
		||||
	"database/sql"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Upgrader struct {
 | 
			
		||||
	schemaDb Database
 | 
			
		||||
	databases map[string]Database
 | 
			
		||||
	logCallback func(string, string)
 | 
			
		||||
	sqlCallback func(string, int) ([]byte, bool)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Database struct {
 | 
			
		||||
	Host     string
 | 
			
		||||
	Port     int
 | 
			
		||||
	DbName   string
 | 
			
		||||
	Username string
 | 
			
		||||
	Password string
 | 
			
		||||
 | 
			
		||||
	db *sql.DB
 | 
			
		||||
	schemaDb *Database
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
func dbUpdate() (err error) {// {{{
 | 
			
		||||
	var rows *sqlx.Rows
 | 
			
		||||
	var schemaStr string
 | 
			
		||||
	var schema int
 | 
			
		||||
	rows, err = db.Queryx(`SELECT value FROM _internal.db WHERE "key"='schema'`)
 | 
			
		||||
	if err != nil { return }
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
 | 
			
		||||
	if !rows.Next() {
 | 
			
		||||
		return errors.New("Table _interval.db missing schema row")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = rows.Scan(&schemaStr); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Run updates
 | 
			
		||||
	schema, err = strconv.Atoi(schemaStr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for i := (schema+1); i <= DB_SCHEMA; i++ {
 | 
			
		||||
		log.Printf("\x1b[32mNotes\x1b[0m Upgrading SQL schema to revision %d...", i)
 | 
			
		||||
		sql, _ := embedded.ReadFile(
 | 
			
		||||
			fmt.Sprintf("sql/%04d.sql", i),
 | 
			
		||||
		)
 | 
			
		||||
		_, err = db.Exec(string(sql))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		_, err = db.Exec(`UPDATE _internal.db SET "value"=$1 WHERE "key"='schema'`, i)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		log.Printf("\x1b[32mNotes\x1b[0m OK: %d", i)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}// }}}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// vim: foldmethod=marker
 | 
			
		||||
							
								
								
									
										154
									
								
								upgrader.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								upgrader.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,154 @@
 | 
			
		|||
package dbschema
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	// Standard
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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) {// {{{
 | 
			
		||||
	upgrader.logCallback = defaultCallback
 | 
			
		||||
	upgrader.databases = map[string]Database{}
 | 
			
		||||
	upgrader.schemaDb, err = newDatabase(
 | 
			
		||||
		host,
 | 
			
		||||
		port,
 | 
			
		||||
		dbName,
 | 
			
		||||
		user,
 | 
			
		||||
		pass,
 | 
			
		||||
	)
 | 
			
		||||
	err = upgrader.verifySchemaTable()
 | 
			
		||||
	return
 | 
			
		||||
}// }}}
 | 
			
		||||
 | 
			
		||||
func (upgrader *Upgrader) SetLogCallback(callback func(string, string)) {// {{{
 | 
			
		||||
	upgrader.logCallback = callback
 | 
			
		||||
}// }}}
 | 
			
		||||
func (upgrader *Upgrader) SetSqlCallback(callback func(string, int) ([]byte, bool)) {// {{{
 | 
			
		||||
	upgrader.sqlCallback = callback
 | 
			
		||||
}// }}}
 | 
			
		||||
func (upgrader Upgrader) verifySchemaTable() (err error) {// {{{
 | 
			
		||||
	var rows *sql.Rows
 | 
			
		||||
	if rows, err = upgrader.schemaDb.db.Query(
 | 
			
		||||
		`SELECT EXISTS (
 | 
			
		||||
			SELECT FROM pg_catalog.pg_class c
 | 
			
		||||
			JOIN   pg_catalog.pg_namespace n ON n.oid = c.relnamespace
 | 
			
		||||
			WHERE  n.nspname = '_db'
 | 
			
		||||
			AND    c.relname = 'schema'
 | 
			
		||||
		)`,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
	var exists bool
 | 
			
		||||
	rows.Next()
 | 
			
		||||
	if err = rows.Scan(&exists); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !exists {
 | 
			
		||||
		upgrader.logCallback("create", "_db.schema")
 | 
			
		||||
		upgrader.schemaDb.db.Exec(`CREATE SCHEMA "_db"`)
 | 
			
		||||
 | 
			
		||||
		if _, err = upgrader.schemaDb.db.Exec(`
 | 
			
		||||
			CREATE TABLE "_db"."schema" (
 | 
			
		||||
				database varchar   NOT NULL,
 | 
			
		||||
				version  int4      NOT NULL,
 | 
			
		||||
				updated  timestamp NOT NULL DEFAULT NOW(),
 | 
			
		||||
 | 
			
		||||
				CONSTRAINT schema_pk PRIMARY KEY (database)
 | 
			
		||||
			);
 | 
			
		||||
			`,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}// }}}
 | 
			
		||||
func (upgrader Upgrader) verifySchemaEntry(dbase Database) (err error) {// {{{
 | 
			
		||||
	var rows *sql.Rows
 | 
			
		||||
	rows, err = upgrader.schemaDb.db.Query(`SELECT version FROM _db.schema WHERE database=$1`, dbase.DbName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
 | 
			
		||||
	if !rows.Next() {
 | 
			
		||||
		upgrader.logCallback("initiate version", dbase.DbName)
 | 
			
		||||
		_, err = upgrader.schemaDb.db.Exec(`INSERT INTO _db.schema(database, version) VALUES($1, 0)`, dbase.DbName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}// }}}
 | 
			
		||||
func (upgrader Upgrader) version(dbName string) (version int, err error) {// {{{
 | 
			
		||||
	var rows *sql.Rows
 | 
			
		||||
	rows, err = upgrader.schemaDb.db.Query(
 | 
			
		||||
		`SELECT version FROM _db.schema WHERE database=$1`,
 | 
			
		||||
		dbName,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
 | 
			
		||||
	if rows.Next() {
 | 
			
		||||
		err = rows.Scan(&version)
 | 
			
		||||
	} else {
 | 
			
		||||
		err = fmt.Errorf(`Database "%s" is missing an entry in _db.schema`, dbName)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}// }}}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	upgrader.databases[dbName] = db
 | 
			
		||||
 | 
			
		||||
	err = upgrader.verifySchemaEntry(db)
 | 
			
		||||
	return
 | 
			
		||||
}// }}}
 | 
			
		||||
func (upgrader Upgrader) Run() (err error) {// {{{
 | 
			
		||||
	var version int
 | 
			
		||||
 | 
			
		||||
	for dbName, db := range upgrader.databases {
 | 
			
		||||
		version, err = upgrader.version(dbName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		upgrader.logCallback("version", fmt.Sprintf("%s: %d", dbName, version))
 | 
			
		||||
 | 
			
		||||
		for {
 | 
			
		||||
			version++
 | 
			
		||||
			sql, found := upgrader.sqlCallback(dbName, version)
 | 
			
		||||
			if !found {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			upgrader.logCallback("exec", fmt.Sprintf("%s: %d", dbName, version))
 | 
			
		||||
			if _, err = db.db.Exec(string(sql)); err != nil {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			_, err = upgrader.schemaDb.db.Exec(`
 | 
			
		||||
				UPDATE _db.schema
 | 
			
		||||
				SET
 | 
			
		||||
					version=$1,
 | 
			
		||||
					updated=NOW()
 | 
			
		||||
				WHERE database=$2
 | 
			
		||||
			`, version, dbName)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}// }}}
 | 
			
		||||
 | 
			
		||||
// vim: foldmethod=marker
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue