Changeset 255 in code for trunk


Ignore:
Timestamp:
Apr 10, 2020, 8:45:02 PM (5 years ago)
Author:
contact
Message:

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.

Location:
trunk
Files:
1 deleted
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/README.md

    r189 r255  
    1111## Usage
    1212
    13     sqlite3 soju.db <schema.sql
    1413    go run ./cmd/sojuctl create-user <username>
    1514    go run ./cmd/soju
  • trunk/db.go

    r251 r255  
    4949var ErrNoSuchChannel = fmt.Errorf("soju: no such channel")
    5050
     51const schema = `
     52CREATE TABLE User (
     53        username VARCHAR(255) PRIMARY KEY,
     54        password VARCHAR(255) NOT NULL
     55);
     56
     57CREATE TABLE Network (
     58        id INTEGER PRIMARY KEY,
     59        name VARCHAR(255),
     60        user VARCHAR(255) NOT NULL,
     61        addr VARCHAR(255) NOT NULL,
     62        nick VARCHAR(255) NOT NULL,
     63        username VARCHAR(255),
     64        realname VARCHAR(255),
     65        pass VARCHAR(255),
     66        sasl_mechanism VARCHAR(255),
     67        sasl_plain_username VARCHAR(255),
     68        sasl_plain_password VARCHAR(255),
     69        FOREIGN KEY(user) REFERENCES User(username),
     70        UNIQUE(user, addr, nick)
     71);
     72
     73CREATE TABLE Channel (
     74        id INTEGER PRIMARY KEY,
     75        network INTEGER NOT NULL,
     76        name VARCHAR(255) NOT NULL,
     77        key VARCHAR(255),
     78        FOREIGN KEY(network) REFERENCES Network(id),
     79        UNIQUE(network, name)
     80);
     81`
     82
     83var migrations = []string{
     84        "", // migration #0 is reserved for schema initialization
     85}
     86
    5187type DB struct {
    5288        lock sync.RWMutex
     
    5591
    5692func OpenSQLDB(driver, source string) (*DB, error) {
    57         db, err := sql.Open(driver, source)
    58         if err != nil {
    59                 return nil, err
    60         }
    61         return &DB{db: db}, nil
     93        sqlDB, err := sql.Open(driver, source)
     94        if err != nil {
     95                return nil, err
     96        }
     97
     98        db := &DB{db: sqlDB}
     99        if err := db.upgrade(); err != nil {
     100                return nil, err
     101        }
     102
     103        return db, nil
    62104}
    63105
     
    66108        defer db.lock.Unlock()
    67109        return db.Close()
     110}
     111
     112func (db *DB) upgrade() error {
     113        db.lock.Lock()
     114        defer db.lock.Unlock()
     115
     116        var version int
     117        if err := db.db.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
     118                return fmt.Errorf("failed to query schema version: %v", err)
     119        }
     120
     121        if version == len(migrations) {
     122                return nil
     123        } else if version > len(migrations) {
     124                return fmt.Errorf("soju (version %d) older than schema (version %d)", len(migrations), version)
     125        }
     126
     127        tx, err := db.db.Begin()
     128        if err != nil {
     129                return err
     130        }
     131        defer tx.Rollback()
     132
     133        if version == 0 {
     134                if _, err := tx.Exec(schema); err != nil {
     135                        return fmt.Errorf("failed to initialize schema: %v", err)
     136                }
     137        } else {
     138                for i := version; i < len(migrations); i++ {
     139                        if _, err := tx.Exec(migrations[i]); err != nil {
     140                                return fmt.Errorf("failed to execute migration #%v: %v", i, err)
     141                        }
     142                }
     143        }
     144
     145        // For some reason prepared statements don't work here
     146        _, err = tx.Exec(fmt.Sprintf("PRAGMA user_version = %d", len(migrations)))
     147        if err != nil {
     148                return fmt.Errorf("failed to bump schema version: %v", err)
     149        }
     150
     151        return tx.Commit()
    68152}
    69153
Note: See TracChangeset for help on using the changeset viewer.