Compare commits
1
Commits
next
...
654ff2daaa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
654ff2daaa |
+11
-7
@@ -90,6 +90,17 @@ func (database *Database) connect(ctx context.Context) error {
|
|||||||
dbURL = config.DefaultDBURL
|
dbURL = config.DefaultDBURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SQLite keeps the foreign keys setting per connection. The driver
|
||||||
|
// runs each _pragma parameter on every connection it opens. A
|
||||||
|
// PRAGMA run once would miss the connection the pool opens after a
|
||||||
|
// cancelled query breaks the old one.
|
||||||
|
separator := "?"
|
||||||
|
if strings.Contains(dbURL, "?") {
|
||||||
|
separator = "&"
|
||||||
|
}
|
||||||
|
|
||||||
|
dbURL += separator + "_pragma=foreign_keys(1)"
|
||||||
|
|
||||||
database.log.Info(
|
database.log.Info(
|
||||||
"connecting to database", "url", dbURL,
|
"connecting to database", "url", dbURL,
|
||||||
)
|
)
|
||||||
@@ -109,13 +120,6 @@ func (database *Database) connect(ctx context.Context) error {
|
|||||||
database.conn = conn
|
database.conn = conn
|
||||||
database.log.Info("database connected")
|
database.log.Info("database connected")
|
||||||
|
|
||||||
_, err = database.conn.ExecContext(
|
|
||||||
ctx, "PRAGMA foreign_keys = ON",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("enable foreign keys: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return database.runMigrations(ctx)
|
return database.runMigrations(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package db_test
|
package db_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.uber.org/fx/fxtest"
|
"go.uber.org/fx/fxtest"
|
||||||
"sneak.berlin/go/neoirc/internal/config"
|
"sneak.berlin/go/neoirc/internal/config"
|
||||||
@@ -14,6 +17,16 @@ import (
|
|||||||
|
|
||||||
const wantBusyTimeoutMS = 5000
|
const wantBusyTimeoutMS = 5000
|
||||||
|
|
||||||
|
const (
|
||||||
|
// endlessQuery counts the rows of a recursive query that never
|
||||||
|
// stops, so it runs until its context ends.
|
||||||
|
endlessQuery = `WITH RECURSIVE n(x) AS
|
||||||
|
(SELECT 1 UNION ALL SELECT x + 1 FROM n)
|
||||||
|
SELECT COUNT(*) FROM n`
|
||||||
|
|
||||||
|
endlessQueryTimeout = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
// TestDefaultDBURLSetsWALAndBusyTimeout opens a file database
|
// TestDefaultDBURLSetsWALAndBusyTimeout opens a file database
|
||||||
// with the default URL's parameters the way the server does,
|
// with the default URL's parameters the way the server does,
|
||||||
// and checks the journal mode and busy timeout it ends up with.
|
// and checks the journal mode and busy timeout it ends up with.
|
||||||
@@ -73,3 +86,84 @@ func TestDefaultDBURLSetsWALAndBusyTimeout(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestForeignKeysOnAfterCancelledQuery cancels a query while it
|
||||||
|
// runs, which makes the pool close its only connection and open a
|
||||||
|
// new one. It checks that the file database keeps its tables and
|
||||||
|
// that the new connection has foreign keys on.
|
||||||
|
func TestForeignKeysOnAfterCancelledQuery(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_, defaultParams, _ := strings.Cut(config.DefaultDBURL, "?")
|
||||||
|
|
||||||
|
urlParams := map[string]string{
|
||||||
|
"default parameters": "?" + defaultParams,
|
||||||
|
"no parameters": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, params := range urlParams {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dbURL := "file://" +
|
||||||
|
filepath.Join(t.TempDir(), "state.db") + params
|
||||||
|
|
||||||
|
lifecycle := fxtest.NewLifecycle(t)
|
||||||
|
|
||||||
|
log, err := logger.New(lifecycle, logger.Params{
|
||||||
|
Globals: &globals.Globals{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
database, err := db.New(lifecycle, db.Params{
|
||||||
|
Logger: log,
|
||||||
|
Config: &config.Config{DBURL: dbURL},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle.RequireStart()
|
||||||
|
t.Cleanup(lifecycle.RequireStop)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(
|
||||||
|
t.Context(), endlessQueryTimeout,
|
||||||
|
)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err = database.GetDB().ExecContext(ctx, endlessQuery)
|
||||||
|
if !errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
t.Fatalf("endless query: err = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var migrations int
|
||||||
|
|
||||||
|
err = database.GetDB().QueryRowContext(
|
||||||
|
t.Context(),
|
||||||
|
"SELECT COUNT(*) FROM schema_migrations",
|
||||||
|
).Scan(&migrations)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if migrations == 0 {
|
||||||
|
t.Error("schema_migrations is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var foreignKeys int
|
||||||
|
|
||||||
|
err = database.GetDB().QueryRowContext(
|
||||||
|
t.Context(), "PRAGMA foreign_keys",
|
||||||
|
).Scan(&foreignKeys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if foreignKeys != 1 {
|
||||||
|
t.Errorf("foreign_keys = %d, want 1", foreignKeys)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user