From f8f5e0d493ba5a19b71c71fe61a3d5cf0bc4ea6e Mon Sep 17 00:00:00 2001 From: Antonio Pagano Date: Fri, 14 Nov 2025 15:01:34 -0500 Subject: [PATCH 1/3] feat: allowing to specify the driver and the params for each conFn --- db/conn_fn.go | 57 +++++++++++++++ db/conn_options.go | 35 +++++++++ db/conn_settings.go | 24 +++++++ db/{connection_test.go => conn_test.go} | 0 db/connection.go | 96 ------------------------- 5 files changed, 116 insertions(+), 96 deletions(-) create mode 100644 db/conn_fn.go create mode 100644 db/conn_options.go create mode 100644 db/conn_settings.go rename db/{connection_test.go => conn_test.go} (100%) delete mode 100644 db/connection.go diff --git a/db/conn_fn.go b/db/conn_fn.go new file mode 100644 index 0000000..33b5401 --- /dev/null +++ b/db/conn_fn.go @@ -0,0 +1,57 @@ +package db + +import ( + "database/sql" + "sync" +) + +var ( + dbPool = map[string]*sql.DB{} + cmux sync.Mutex +) + +// ConnFn is the database connection builder function that +// will be used by the application based on the driver and +// connection string. +type ConnFn func() (*sql.DB, error) + +// ConnectionFn is the database connection builder function that +// will be used by the application based on the driver and +// connection string. It opens the connection only once +// and return the same connection on subsequent calls. +func ConnectionFn(url string, opts ...connectionOption) ConnFn { + return func() (cx *sql.DB, err error) { + cmux.Lock() + defer cmux.Unlock() + + // Return existing connection if available and valid + // to avoid reopening connections. Otherwise continue + // to create a new one. + if conn := dbPool[url]; conn != nil && conn.Ping() == nil { + return conn, nil + } + + cs := &connSettings{ + url: url, + + driver: "postgres", + params: "", + } + + // Apply options before connecting to the database. + for _, v := range opts { + v(cs) + } + + conn, err := sql.Open(cs.driver, cs.connectionURL()) + if err != nil { + return nil, err + } + + // This uses url instead of modURL while the params apply + // to all connections. + dbPool[url] = conn + + return conn, nil + } +} diff --git a/db/conn_options.go b/db/conn_options.go new file mode 100644 index 0000000..d1f5443 --- /dev/null +++ b/db/conn_options.go @@ -0,0 +1,35 @@ +package db + +import "net/url" + +// connectionOptions for the database +type connectionOption func(*connSettings) + +// WithDriver allows to specify the driver to use driver defaults to +// postgres. +func WithDriver(name string) connectionOption { + return func(cs *connSettings) { + cs.driver = name + } +} + +// Params allows to specify additional connection parameters +// that will be encoded as URL params next to the connection string. +// params should be in key,value,key,value,... format. +// e.g Params("sslmode", "disable", "timezone", "UTC") +func Params(params ...string) connectionOption { + vals := url.Values{} + for i := 0; i < len(params); i += 2 { + key := params[i] + if i+1 >= len(params) { + vals.Add(key, "") + break + } + + vals.Add(key, params[i+1]) + } + + return func(cs *connSettings) { + cs.params = vals.Encode() + } +} diff --git a/db/conn_settings.go b/db/conn_settings.go new file mode 100644 index 0000000..427658a --- /dev/null +++ b/db/conn_settings.go @@ -0,0 +1,24 @@ +package db + +import "strings" + +// connSettings holds database connection settings. +// while opening the connection, its modified by +// connectionOption functions. +type connSettings struct { + params string + driver string + url string +} + +func (cs connSettings) connectionURL() string { + if cs.params == "" { + return cs.url + } + + if strings.Contains(cs.url, "?") { + return cs.url + "&" + cs.params + } + + return cs.url + "?" + cs.params +} diff --git a/db/connection_test.go b/db/conn_test.go similarity index 100% rename from db/connection_test.go rename to db/conn_test.go diff --git a/db/connection.go b/db/connection.go deleted file mode 100644 index 6cf1b60..0000000 --- a/db/connection.go +++ /dev/null @@ -1,96 +0,0 @@ -package db - -import ( - "database/sql" - "net/url" - "strings" - "sync" -) - -var ( - dbPool = map[string]*sql.DB{} - cmux sync.Mutex - - // DriverName defaults to postgres - driverName = "postgres" - - // Connection params to be appended to the connection string - connParams string -) - -// ConnFn is the database connection builder function that -// will be used by the application based on the driver and -// connection string. -type ConnFn func() (*sql.DB, error) - -// connectionOptions for the database -type connectionOption func() - -// ConnectionFn is the database connection builder function that -// will be used by the application based on the driver and -// connection string. It opens the connection only once -// and return the same connection on subsequent calls. -func ConnectionFn(url string, opts ...connectionOption) ConnFn { - return func() (cx *sql.DB, err error) { - cmux.Lock() - defer cmux.Unlock() - - if conn := dbPool[url]; conn != nil && conn.Ping() == nil { - return conn, nil - } - - // Apply options before connecting to the database. - for _, v := range opts { - v() - } - - // Modify the URL to include connection params - // if any. - modURL := url - if strings.Contains(modURL, "?") { - modURL = modURL + "&" + connParams - } else if connParams != "" { - modURL = modURL + "?" + connParams - } - - conn, err := sql.Open(driverName, modURL) - if err != nil { - return nil, err - } - - // This uses url instead of modURL while the params apply - // to all connections. - dbPool[url] = conn - - return conn, nil - } -} - -// WithDriver allows to specify the driver to use driver defaults to -// postgres. -func WithDriver(name string) connectionOption { - return func() { - driverName = name - } -} - -// Params allows to specify additional connection parameters -// that will be encoded as URL params next to the connection string. -// params should be in key,value,key,value,... format. -// e.g Params("sslmode", "disable", "timezone", "UTC") -func Params(params ...string) connectionOption { - vals := url.Values{} - for i := 0; i < len(params); i += 2 { - key := params[i] - if i+1 >= len(params) { - vals.Add(key, "") - break - } - - vals.Add(key, params[i+1]) - } - - return func() { - connParams = vals.Encode() - } -} From 8fa751ddf5c9a891c1de562c676aee38d1811285 Mon Sep 17 00:00:00 2001 From: Antonio Pagano Date: Fri, 14 Nov 2025 15:06:56 -0500 Subject: [PATCH 2/3] task: solving some minor warnings from the linter --- db/manager.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/db/manager.go b/db/manager.go index a7a370d..ed66e4f 100644 --- a/db/manager.go +++ b/db/manager.go @@ -12,15 +12,13 @@ import ( "strings" ) -var ( - // postgresURLExp is the regular expression to extract the database name - // and the user credentials from the database URL. - postgresURLExp = regexp.MustCompile(`postgres://(.*):(.*)@(.*):(.*)/([^?]*)`) -) +// postgresURLExp is the regular expression to extract the database name +// and the user credentials from the database URL. +var postgresURLExp = regexp.MustCompile(`postgres://(.*):(.*)@(.*):(.*)/([^?]*)`) // Create a new database based on the passed URL. func Create(url string) error { - var createFn func(string) error = createSQLite + createFn := createSQLite if strings.Contains(url, "postgres") { createFn = createPostgres } @@ -48,7 +46,13 @@ func createPostgres(conURL string) error { return fmt.Errorf("invalid database URL: %s", conURL) } - db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s:%s/postgres?sslmode=disable", matches[1], matches[2], matches[3], matches[4])) + db, err := sql.Open( + "postgres", + fmt.Sprintf( + "postgres://%s:%s@%s:%s/postgres?sslmode=disable", + matches[1], matches[2], matches[3], matches[4], + ), + ) if err != nil { return fmt.Errorf("connecting to database: %w", err) } @@ -56,7 +60,6 @@ func createPostgres(conURL string) error { var exists int row := db.QueryRow("SELECT COUNT(datname) FROM pg_database WHERE datname ilike $1", matches[5]) err = row.Scan(&exists) - if err != nil { return err } @@ -75,7 +78,7 @@ func createPostgres(conURL string) error { // Drop a database based on the passed URL. func Drop(url string) error { - var dropFn func(string) error = dropSQLite + dropFn := dropSQLite if strings.Contains(url, "postgres") { dropFn = dropPostgres } From 2281de1ff603beb42f8bcf68d768a356616749a4 Mon Sep 17 00:00:00 2001 From: Antonio Pagano Date: Fri, 14 Nov 2025 15:11:30 -0500 Subject: [PATCH 3/3] task: a bit more of readability --- db/manager.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/db/manager.go b/db/manager.go index ed66e4f..c8bc487 100644 --- a/db/manager.go +++ b/db/manager.go @@ -46,13 +46,12 @@ func createPostgres(conURL string) error { return fmt.Errorf("invalid database URL: %s", conURL) } - db, err := sql.Open( - "postgres", - fmt.Sprintf( - "postgres://%s:%s@%s:%s/postgres?sslmode=disable", - matches[1], matches[2], matches[3], matches[4], - ), + dsn := fmt.Sprintf( + "postgres://%s:%s@%s:%s/postgres?sslmode=disable", + matches[1], matches[2], matches[3], matches[4], ) + + db, err := sql.Open("postgres", dsn) if err != nil { return fmt.Errorf("connecting to database: %w", err) }