Skip to content

Commit

Permalink
Merge pull request #19 from willemschots/4-sqlite-settings
Browse files Browse the repository at this point in the history
4: Seperate read and write instances for sqlite
  • Loading branch information
willemschots authored Mar 25, 2024
2 parents 0ff0fd8 + fd1d7dc commit ad49c0c
Show file tree
Hide file tree
Showing 34 changed files with 112 additions and 66 deletions.
4 changes: 2 additions & 2 deletions internal/auth/db/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

"github.com/willemschots/househunt/internal/auth"
"github.com/willemschots/househunt/internal/auth/db"
"github.com/willemschots/househunt/internal/db/testdb"
"github.com/willemschots/househunt/internal/errorz"
"github.com/willemschots/househunt/internal/migrate/testdb"
)

func Test_Tx_CreateUser(t *testing.T) {
Expand Down Expand Up @@ -373,7 +373,7 @@ func now(t *testing.T, i int) time.Time {
func storeForTest(t *testing.T) *db.Store {
t.Helper()

testDB := testdb.RunTestDB(t)
testDB := testdb.RunWhile(t, true)

i := 0
return db.New(testDB, func() time.Time {
Expand Down
48 changes: 48 additions & 0 deletions internal/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package db

import (
"database/sql"
)

const (
// both options use wal mode, foreign keys, and a busy timeout of 5 seconds.
// the writeOptions also use immediate transactions to prevent locking issues.
// To run SQLite so that it works well with out app, we need a few options
// We need to configure a few options to make sure SQLite works well with our app:
// - WAL Mode so that reads and writes don't block eachother.
// - A busy timeout, specifying the duration a connection will wait for a lock.
// - Foreign keys are enforced.
writeOptions = "?mode=rw_&_foreign_keys=on&_journal_mode=wal&_busy_timeout=5000&_txlock=immediate"
readOptions = "?mode=ro_&_foreign_keys=on&_journal_mode=wal&_busy_timeout=5000"
)

// OpenSQLite3 opens a pool of SQLite3 connections. Different settings
// are appropriate for reading and writing, so this function needs to know
// what the sql.DB will be used for.
//
// See this comment for more information:
// https://github.com/mattn/go-sqlite3/issues/1179#issuecomment-1638083995
func OpenSQLite(dbFile string, write bool) (*sql.DB, error) {
optsPostfix := readOptions
if write {
optsPostfix = writeOptions
}

// Open the database file with the correct options.
db, err := sql.Open("sqlite3", dbFile+optsPostfix)
if err != nil {
return nil, err
}

if write {
// use only a single connection for writing.
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)

// don't close this connection.
db.SetConnMaxLifetime(0)
db.SetConnMaxIdleTime(0)
}

return db, nil
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import (
"time"

_ "github.com/mattn/go-sqlite3"
"github.com/willemschots/househunt/internal/migrate"
"github.com/willemschots/househunt/internal/db/migrate"
"github.com/willemschots/househunt/internal/db/testdb"
)

func Test_RunFS(t *testing.T) {
t.Run("ok, empty dir", func(t *testing.T) {
db := openSQLiteDBForTest(t)
db := testdb.RunUnmigratedWhile(t, true)

meta := migrate.Metadata{
"v1.0.0", timeRFC3339(t, "2024-03-20T14:56:00Z"),
Expand All @@ -30,7 +31,7 @@ func Test_RunFS(t *testing.T) {
})

t.Run("ok, subdir is skipped", func(t *testing.T) {
db := openSQLiteDBForTest(t)
db := testdb.RunUnmigratedWhile(t, true)

meta := migrate.Metadata{
"v1.0.0", timeRFC3339(t, "2024-03-20T14:56:00Z"),
Expand All @@ -56,7 +57,7 @@ func Test_RunFS(t *testing.T) {
})

t.Run("ok, progression of migrations", func(t *testing.T) {
db := openSQLiteDBForTest(t)
db := testdb.RunUnmigratedWhile(t, true)

metas := []migrate.Metadata{
{"v1.0.0", timeRFC3339(t, "2024-03-20T14:56:00Z")},
Expand Down Expand Up @@ -122,7 +123,7 @@ func Test_RunFS(t *testing.T) {
})

t.Run("fail, error in migration", func(t *testing.T) {
db := openSQLiteDBForTest(t)
db := testdb.RunUnmigratedWhile(t, true)

metas := []migrate.Metadata{
{"v1.0.0", timeRFC3339(t, "2024-03-20T14:56:00Z")},
Expand Down Expand Up @@ -158,7 +159,8 @@ func Test_RunFS(t *testing.T) {
})

t.Run("fail, migration file that was executed was removed from disk", func(t *testing.T) {
db := openSQLiteDBForTest(t)
db := testdb.RunUnmigratedWhile(t, true)

metas := []migrate.Metadata{
{"v1.0.0", timeRFC3339(t, "2024-03-20T14:56:00Z")},
{"v2.0.0", timeRFC3339(t, "2024-04-20T14:56:00Z")},
Expand All @@ -185,7 +187,8 @@ func Test_RunFS(t *testing.T) {
})

t.Run("fail, migration file that was executed was renamed", func(t *testing.T) {
db := openSQLiteDBForTest(t)
db := testdb.RunUnmigratedWhile(t, true)

metas := []migrate.Metadata{
{"v1.0.0", timeRFC3339(t, "2024-03-20T14:56:00Z")},
{"v2.0.0", timeRFC3339(t, "2024-04-20T14:56:00Z")},
Expand Down Expand Up @@ -214,7 +217,7 @@ func Test_RunFS(t *testing.T) {

func Test_QueryMigrations(t *testing.T) {
t.Run("fail, no table", func(t *testing.T) {
db := openSQLiteDBForTest(t)
db := testdb.RunUnmigratedWhile(t, true)

_, err := migrate.QueryMigrations(context.Background(), db)
if !errors.Is(err, migrate.ErrNoTable) {
Expand All @@ -223,22 +226,6 @@ func Test_QueryMigrations(t *testing.T) {
})
}

func openSQLiteDBForTest(t *testing.T) *sql.DB {
db, err := sql.Open("sqlite3", ":memory:?_foreign_keys=on")
if err != nil {
t.Fatalf("failed to open database: %v", err)
}

t.Cleanup(func() {
err := db.Close()
if err != nil {
t.Errorf("failed to close database: %v", err)
}
})

return db
}

func assertTable(t *testing.T, db *sql.DB, want []migrate.Migration) {
t.Helper()

Expand Down
File renamed without changes.
51 changes: 51 additions & 0 deletions internal/db/testdb/testdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package testdb

import (
"context"
"database/sql"
"testing"
"time"

_ "github.com/mattn/go-sqlite3"
"github.com/willemschots/househunt/internal/db"
"github.com/willemschots/househunt/internal/db/migrate"
"github.com/willemschots/househunt/migrations"
)

// RunWhile runs a database while the provided test is executing.
// It returns an empty database with all migrations applied.
func RunWhile(t *testing.T, write bool) *sql.DB {
t.Helper()

db := RunUnmigratedWhile(t, write)

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

_, err := migrate.RunFS(ctx, db, migrations.FS, migrate.Metadata{})
if err != nil {
t.Fatalf("failed to run migrations: %v", err)
}

return db
}

// RunUnmigratedWhile runs a database while the provided test is executing.
// It returns an empty database without any migrations applied.
func RunUnmigratedWhile(t *testing.T, write bool) *sql.DB {
t.Helper()

db, err := db.OpenSQLite(":memory:", write)
if err != nil {
t.Fatalf("failed to open database: %v", err)
}

t.Cleanup(func() {
err := db.Close()
if err != nil {
t.Errorf("failed to close database: %v", err)
}
})

return db
}
40 changes: 0 additions & 40 deletions internal/migrate/testdb/testdb.go

This file was deleted.

0 comments on commit ad49c0c

Please sign in to comment.