Set up DB migration infrastructure
The database is now initialized automatically on first run. The schema version is stored in SQLite's user_version special field. Migrations are stored in an array and applied based on the schema version.
This commit is contained in:
parent
da4b91793e
commit
2194259124
@ -10,7 +10,6 @@ A user-friendly IRC bouncer.
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
sqlite3 soju.db <schema.sql
|
|
||||||
go run ./cmd/sojuctl create-user <username>
|
go run ./cmd/sojuctl create-user <username>
|
||||||
go run ./cmd/soju
|
go run ./cmd/soju
|
||||||
|
|
||||||
|
88
db.go
88
db.go
@ -48,17 +48,59 @@ type Channel struct {
|
|||||||
|
|
||||||
var ErrNoSuchChannel = fmt.Errorf("soju: no such channel")
|
var ErrNoSuchChannel = fmt.Errorf("soju: no such channel")
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
CREATE TABLE User (
|
||||||
|
username VARCHAR(255) PRIMARY KEY,
|
||||||
|
password VARCHAR(255) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE Network (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name VARCHAR(255),
|
||||||
|
user VARCHAR(255) NOT NULL,
|
||||||
|
addr VARCHAR(255) NOT NULL,
|
||||||
|
nick VARCHAR(255) NOT NULL,
|
||||||
|
username VARCHAR(255),
|
||||||
|
realname VARCHAR(255),
|
||||||
|
pass VARCHAR(255),
|
||||||
|
sasl_mechanism VARCHAR(255),
|
||||||
|
sasl_plain_username VARCHAR(255),
|
||||||
|
sasl_plain_password VARCHAR(255),
|
||||||
|
FOREIGN KEY(user) REFERENCES User(username),
|
||||||
|
UNIQUE(user, addr, nick)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE Channel (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
network INTEGER NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
key VARCHAR(255),
|
||||||
|
FOREIGN KEY(network) REFERENCES Network(id),
|
||||||
|
UNIQUE(network, name)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
var migrations = []string{
|
||||||
|
"", // migration #0 is reserved for schema initialization
|
||||||
|
}
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenSQLDB(driver, source string) (*DB, error) {
|
func OpenSQLDB(driver, source string) (*DB, error) {
|
||||||
db, err := sql.Open(driver, source)
|
sqlDB, err := sql.Open(driver, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &DB{db: db}, nil
|
|
||||||
|
db := &DB{db: sqlDB}
|
||||||
|
if err := db.upgrade(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Close() error {
|
func (db *DB) Close() error {
|
||||||
@ -67,6 +109,48 @@ func (db *DB) Close() error {
|
|||||||
return db.Close()
|
return db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) upgrade() error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
var version int
|
||||||
|
if err := db.db.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
|
||||||
|
return fmt.Errorf("failed to query schema version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if version == len(migrations) {
|
||||||
|
return nil
|
||||||
|
} else if version > len(migrations) {
|
||||||
|
return fmt.Errorf("soju (version %d) older than schema (version %d)", len(migrations), version)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := db.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
if version == 0 {
|
||||||
|
if _, err := tx.Exec(schema); err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize schema: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := version; i < len(migrations); i++ {
|
||||||
|
if _, err := tx.Exec(migrations[i]); err != nil {
|
||||||
|
return fmt.Errorf("failed to execute migration #%v: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For some reason prepared statements don't work here
|
||||||
|
_, err = tx.Exec(fmt.Sprintf("PRAGMA user_version = %d", len(migrations)))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bump schema version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
func fromStringPtr(ptr *string) string {
|
func fromStringPtr(ptr *string) string {
|
||||||
if ptr == nil {
|
if ptr == nil {
|
||||||
return ""
|
return ""
|
||||||
|
29
schema.sql
29
schema.sql
@ -1,29 +0,0 @@
|
|||||||
CREATE TABLE User (
|
|
||||||
username VARCHAR(255) PRIMARY KEY,
|
|
||||||
password VARCHAR(255) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE Network (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
name VARCHAR(255),
|
|
||||||
user VARCHAR(255) NOT NULL,
|
|
||||||
addr VARCHAR(255) NOT NULL,
|
|
||||||
nick VARCHAR(255) NOT NULL,
|
|
||||||
username VARCHAR(255),
|
|
||||||
realname VARCHAR(255),
|
|
||||||
pass VARCHAR(255),
|
|
||||||
sasl_mechanism VARCHAR(255),
|
|
||||||
sasl_plain_username VARCHAR(255),
|
|
||||||
sasl_plain_password VARCHAR(255),
|
|
||||||
FOREIGN KEY(user) REFERENCES User(username),
|
|
||||||
UNIQUE(user, addr, nick)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE Channel (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
network INTEGER NOT NULL,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
key VARCHAR(255),
|
|
||||||
FOREIGN KEY(network) REFERENCES Network(id),
|
|
||||||
UNIQUE(network, name)
|
|
||||||
);
|
|
Loading…
Reference in New Issue
Block a user