From dcc4fe1c52b6e578abc0fb471c4cfd54542545fe Mon Sep 17 00:00:00 2001 From: Fabio Bonelli Date: Fri, 4 Sep 2026 06:23:11 +0200 Subject: [PATCH 1/2] refactor: bind the analysis patch once in the sqlite merge From ec3ac79088e877b5d8a21b81e4f984fc29ae008e Mon Sep 17 00:00:00 2001 From: Fabio Bonelli Date: Fri, 4 Sep 2026 06:32:29 +0200 Subject: [PATCH 2/2] refactor: resolve the database dialect in one place --- internal/database/analysis.go | 13 ++++++++---- internal/database/analysis_test.go | 2 +- internal/database/database.go | 33 +++++++++++++++++++++++------- internal/database/database_test.go | 2 +- main_test.go | 4 ++-- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/internal/database/analysis.go b/internal/database/analysis.go index c7c473a..37fbcd0 100644 --- a/internal/database/analysis.go +++ b/internal/database/analysis.go @@ -11,7 +11,7 @@ import ( "gorm.io/gorm/clause" ) -var errUnsupportedAnalysisDialect = errors.New("unsupported database dialect for analysis merge") +var errUnsupportedDialect = errors.New("unsupported database dialect for analysis merge") // MergeAnalysis atomically replaces the namespaces in patch while preserving // every namespace that is already stored but absent from patch. It writes @@ -26,7 +26,12 @@ func MergeAnalysis( patch = common.AnalysisData{} } - expression, err := analysisMergeExpression(gormdb.Name(), patch) + dialect, err := DialectOf(gormdb) + if err != nil { + return nil, err + } + + expression, err := analysisMergeExpression(dialect, patch) if err != nil { return nil, err } @@ -57,7 +62,7 @@ func MergeAnalysis( return merged, nil } -func analysisMergeExpression(dialect string, patch common.AnalysisData) (clause.Expr, error) { +func analysisMergeExpression(dialect Dialect, patch common.AnalysisData) (clause.Expr, error) { patchJSON, err := json.Marshal(patch) if err != nil { return clause.Expr{}, fmt.Errorf("marshal analysis patch: %w", err) @@ -94,6 +99,6 @@ func analysisMergeExpression(dialect string, patch common.AnalysisData) (clause. string(patchJSON), ), nil default: - return clause.Expr{}, fmt.Errorf("%w: %s", errUnsupportedAnalysisDialect, dialect) + return clause.Expr{}, fmt.Errorf("%w: %q", errUnsupportedDialect, dialect) } } diff --git a/internal/database/analysis_test.go b/internal/database/analysis_test.go index 37096ea..a5463b9 100644 --- a/internal/database/analysis_test.go +++ b/internal/database/analysis_test.go @@ -220,7 +220,7 @@ func openAnalysisTestDatabase(t *testing.T) *gorm.DB { if dsn != "" { var err error - dialect, err = Dialect(dsn) + dialect, err = DialectFromDSN(dsn) require.NoError(t, err) } diff --git a/internal/database/database.go b/internal/database/database.go index 125c7a5..52ae803 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -14,19 +14,23 @@ import ( "gorm.io/gorm/schema" ) +type Dialect string + const ( - SQLite = "sqlite" - Postgres = "postgres" + SQLite Dialect = "sqlite" + Postgres Dialect = "postgres" ) var ErrUnknownDialect = errors.New( `unrecognized database DSN: expected a "file:" SQLite DSN, a "postgres://" URL or a key=value connection string`, ) -// Dialect reports which database a DSN points at. Everything that opens -// a connection goes through here, so the application and the test +var errUnknownGormDialect = errors.New("unrecognized gorm driver name") + +// DialectFromDSN reports which database a DSN points at. Everything that +// opens a connection goes through here, so the application and the test // harness can't disagree and end up talking to two different databases. -func Dialect(connection string) (string, error) { +func DialectFromDSN(connection string) (Dialect, error) { switch { case strings.HasPrefix(connection, "file:"): return SQLite, nil @@ -39,8 +43,23 @@ func Dialect(connection string) (string, error) { return "", fmt.Errorf("%w: %q", ErrUnknownDialect, connection) } +// DialectOf reports the Dialect an already open *gorm.DB is talking to, read +// from the driver name gorm reports for it. It is the one place that maps a +// gorm driver name back to a Dialect, so a request handler and NewDatabase +// read the same fact instead of each guessing from the connection. +func DialectOf(gormdb *gorm.DB) (Dialect, error) { + switch name := gormdb.Name(); name { + case string(SQLite): + return SQLite, nil + case string(Postgres): + return Postgres, nil + default: + return "", fmt.Errorf("%w: %q", errUnknownGormDialect, name) + } +} + func NewDatabase(connection string) (*gorm.DB, error) { - dialect, err := Dialect(connection) + dialect, err := DialectFromDSN(connection) if err != nil { return nil, err } @@ -52,7 +71,7 @@ func NewDatabase(connection string) (*gorm.DB, error) { log.Println("using SQLite database") database, err = gorm.Open(sqlite.Open(connection), &gorm.Config{}) - default: + case Postgres: log.Println("using Postgres database") database, err = gorm.Open(postgres.Open(connection), &gorm.Config{ diff --git a/internal/database/database_test.go b/internal/database/database_test.go index 30f5f0e..a04889a 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -26,7 +26,7 @@ func testConnection(t *testing.T) string { return sqliteTestConnection(t) } - dialect, err := Dialect(connection) + dialect, err := DialectFromDSN(connection) require.NoError(t, err) if dialect == SQLite { diff --git a/main_test.go b/main_test.go index 923c229..53b3c9b 100644 --- a/main_test.go +++ b/main_test.go @@ -72,13 +72,13 @@ func init() { dsn := os.Getenv("DATABASE_DSN") - dialect, err := database.Dialect(dsn) + dialect, err := database.DialectFromDSN(dsn) if err != nil { log.Fatal(err) } // database/sql registers the SQLite driver under a name of its own. - dbDriver = dialect + dbDriver = string(dialect) if dialect == database.SQLite { dbDriver = "sqlite3" }