From ade67bd26bdebb2624ecc59057d40e3be21894f3 Mon Sep 17 00:00:00 2001 From: Umputun Date: Sun, 12 Jan 2025 14:30:46 -0600 Subject: [PATCH] add factory methods to make storages - Implement new function `NewPostgres` for creating a Postgres database connection with proper error handling. - Create `storage.go` file to manage database connections, supporting Sqlite, Postgres, and MySQL. - Implement `prepareStoreURL` to determine the database type from the connection URL and handle connection string formatting. --- app/storage/engine/engine.go | 12 ++++++- app/storage/storage.go | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 app/storage/storage.go diff --git a/app/storage/engine/engine.go b/app/storage/engine/engine.go index 8c36f74..e44c1c6 100644 --- a/app/storage/engine/engine.go +++ b/app/storage/engine/engine.go @@ -17,6 +17,7 @@ const ( Unknown Type = "" Sqlite Type = "sqlite" Postgres Type = "postgres" + Mysql Type = "mysql" ) // SQL is a wrapper for sqlx.DB with type. @@ -31,7 +32,7 @@ type SQL struct { func NewSqlite(file, gid string) (*SQL, error) { db, err := sqlx.Connect("sqlite", file) if err != nil { - return &SQL{}, err + return &SQL{}, fmt.Errorf("failed to connect to sqlite: %w", err) } if err := setSqlitePragma(db); err != nil { return &SQL{}, err @@ -39,6 +40,15 @@ func NewSqlite(file, gid string) (*SQL, error) { return &SQL{DB: *db, gid: gid, dbType: Sqlite}, nil } +// NewPostgres creates a new postgres database +func NewPostgres(ctx context.Context, connURL, gid string) (*SQL, error) { + db, err := sqlx.ConnectContext(ctx, "postgres", connURL) + if err != nil { + return &SQL{}, fmt.Errorf("failed to connect to postgres: %w", err) + } + return &SQL{DB: *db, gid: gid, dbType: Postgres}, nil +} + // GID returns the group id func (e *SQL) GID() string { return e.gid diff --git a/app/storage/storage.go b/app/storage/storage.go new file mode 100644 index 0000000..637a639 --- /dev/null +++ b/app/storage/storage.go @@ -0,0 +1,67 @@ +package storage + +import ( + "context" + "fmt" + "strings" + + "github.com/umputun/tg-spam/app/storage/engine" +) + +// New creates a new store based on the connection URL and the session duration. +func New(ctx context.Context, connURL, gid string) (*engine.SQL, error) { + dbType, conn, err := prepareStoreURL(connURL) + if err != nil { + return nil, err + } + + switch dbType { + case engine.Sqlite: + res, err := engine.NewSqlite(conn, gid) + if err != nil { + return nil, fmt.Errorf("failed to create sqlite engine: %w", err) + } + return res, nil + case engine.Postgres: + res, err := engine.NewPostgres(ctx, conn, gid) + if err != nil { + return nil, fmt.Errorf("failed to create postgres engine: %w", err) + } + return res, nil + default: + return nil, fmt.Errorf("unsupported database type in connection string") + } +} + +// prepareStoreURL returns the type of the store based on the connection URL and the connection URL. +// supported connection strings for psq, mysql and sqlite +// returns the type of the store and the connection string to use with sqlx +func prepareStoreURL(connURL string) (dbType engine.Type, conn string, err error) { + if strings.HasPrefix(connURL, "postgres://") { + return engine.Postgres, connURL, nil + } + if strings.Contains(connURL, "@tcp(") { + // check if parseTime=true is set in the connection string and add it if not + if !strings.Contains(connURL, "parseTime=true") { + if strings.Contains(connURL, "?") { + connURL += "&parseTime=true" + } else { + connURL += "?parseTime=true" + } + } + return engine.Mysql, connURL, nil + } + if strings.HasPrefix(connURL, "sqlite:/") || strings.HasPrefix(connURL, "sqlite3:/") || + strings.HasSuffix(connURL, ".sqlite") || strings.HasSuffix(connURL, ".db") { + connURL = strings.Replace(connURL, "sqlite:/", "file:/", 1) + connURL = strings.Replace(connURL, "sqlite3:/", "file:/", 1) + return engine.Sqlite, connURL, nil + } + + if connURL == "memory" || strings.Contains(connURL, ":memory:") || + strings.HasPrefix(connURL, "memory://") || strings.HasPrefix(connURL, "mem://") { + return engine.Sqlite, ":memory:", nil + } + + return engine.Unknown, "", fmt.Errorf("unsupported database type in connection string") +}