From 757483a5740c6afb5e459305f76056f3b0090429 Mon Sep 17 00:00:00 2001 From: Elijah Duffy Date: Wed, 4 Jun 2025 18:11:27 -0700 Subject: [PATCH] improve file structure --- config.go | 5 ++- dbx.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++ db.go => mysql.go | 93 ------------------------------------------ 3 files changed, 105 insertions(+), 95 deletions(-) create mode 100644 dbx.go rename db.go => mysql.go (67%) diff --git a/config.go b/config.go index 68f8e4a..291be26 100644 --- a/config.go +++ b/config.go @@ -1,7 +1,8 @@ package dbx -// DBConfig defines the expected structure for database configuration. Includes -// tags for validation and maps to +// DBConfig provides a template for database connection configuration compatible +// with go-toolkit/config. If used as a field in a struct, remember to include +// the `validate:"dive"` tag to ensure that the validator checks sub-fields. type DBConfig struct { User string `validate:"required"` Password string `validate:"required"` diff --git a/dbx.go b/dbx.go new file mode 100644 index 0000000..9b9e2e6 --- /dev/null +++ b/dbx.go @@ -0,0 +1,102 @@ +package dbx + +import ( + "database/sql" + "reflect" + "strings" + + "gitea.auvem.com/go-toolkit/app" + "github.com/go-jet/jet/v2/qrm" + _ "github.com/go-sql-driver/mysql" +) + +// ModuleDBName is the name of the database module. +const ModuleDBName = "database" + +var ( + // ErrNoRows is returned when a query returns no rows. + ErrNoRows = qrm.ErrNoRows + + // sqlDB stores the current SQL database handle. + sqlDB *sql.DB + + // config stores the database connection configuration. + config DBConfig + + // dbModule is the singleton module instance for the database connection. + dbModule *app.Module +) + +// SQLO returns the current SQL database handle. +func SQLO() *sql.DB { + dbModule.RequireLoaded("dbx.SQLO requires database module") // ensure the module is loaded before accessing the database + if sqlDB == nil { + panic("SQL database not initialized") + } + return sqlDB +} + +// Queryable interface is an SQL driver object that can execute SQL statements +// for Jet. +type Queryable interface { + qrm.Queryable + Query(string, ...any) (*sql.Rows, error) +} + +// Executable interface is an SQL driver object that can execute SQL statements +// for Jet. +type Executable interface { + qrm.Executable + Exec(string, ...any) (sql.Result, error) +} + +// ExecutableTx interface is an SQL driver object that implements the Executable +// interface and can also begin a transaction. +type ExecutableTx interface { + Executable + Begin() (*sql.Tx, error) +} + +// DestName returns the name of the type passed as `destTypeStruct` as a string, +// normalized for compatibility with the Jet QRM. +func DestName(destTypeStruct any, path ...string) string { + v := reflect.ValueOf(destTypeStruct) + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + destIdent := v.Type().String() + destIdent = destIdent[strings.LastIndex(destIdent, ".")+1:] + + for i, p := range path { + if v.Kind() != reflect.Struct { + dbModule.Logger().Error("DestName: path parent is not a struct", "path", destIdent+"."+strings.Join(path[:i+1], ".")) + return "" + } + + v = v.FieldByName(p) + + if !v.IsValid() { + dbModule.Logger().Error("DestName: field does not exist", "path", destIdent+"."+strings.Join(path[:i+1], ".")) + return "" + } + + destIdent += "." + p + } + + return destIdent +} + +// StringToFilter processes a string to be used as a filter in an SQL LIKE +// statement. It replaces all spaces with % and adds % to the beginning and +// end of the string. +func StringToFilter(str string) string { + // Remove any existing leading or trailing % characters + str = strings.Trim(str, "%") + + // Replace all spaces with % and add % to the beginning and end of the string + str = strings.ReplaceAll(str, " ", "%") + str = "%" + str + "%" + + return str +} diff --git a/db.go b/mysql.go similarity index 67% rename from db.go rename to mysql.go index 39efe41..ffec3f2 100644 --- a/db.go +++ b/mysql.go @@ -5,7 +5,6 @@ import ( "database/sql" "errors" "fmt" - "reflect" "slices" "strings" @@ -13,35 +12,8 @@ import ( "github.com/fatih/color" "github.com/go-jet/jet/v2/mysql" "github.com/go-jet/jet/v2/qrm" - _ "github.com/go-sql-driver/mysql" ) -// ModuleDBName is the name of the database module. -const ModuleDBName = "database" - -var ( - // ErrNoRows is returned when a query returns no rows. - ErrNoRows = qrm.ErrNoRows - - // sqlDB stores the current SQL database handle. - sqlDB *sql.DB - - // config stores the database connection configuration. - config DBConfig - - // dbModule is the singleton module instance for the database connection. - dbModule *app.Module -) - -// SQLO returns the current SQL database handle. -func SQLO() *sql.DB { - dbModule.RequireLoaded("dbx.SQLO requires database module") // ensure the module is loaded before accessing the database - if sqlDB == nil { - panic("SQL database not initialized") - } - return sqlDB -} - // ModuleDB returns the database module with the provided configuration. func ModuleDB(cfg DBConfig, forceDebugLog bool) *app.Module { if dbModule != nil { @@ -118,27 +90,6 @@ func teardownDB(_ *app.Module) error { return nil } -// Queryable interface is an SQL driver object that can execute SQL statements -// for Jet. -type Queryable interface { - qrm.Queryable - Query(string, ...any) (*sql.Rows, error) -} - -// Executable interface is an SQL driver object that can execute SQL statements -// for Jet. -type Executable interface { - qrm.Executable - Exec(string, ...any) (sql.Result, error) -} - -// ExecutableTx interface is an SQL driver object that implements the Executable -// interface and can also begin a transaction. -type ExecutableTx interface { - Executable - Begin() (*sql.Tx, error) -} - // MustQuery executes a query and returns an error if any. Filters errors for // db.ErrNoRows (qrm.ErrNoRows) and returns nil or the error provided in that case. func MustQuery(sqlo Queryable, stmt mysql.Statement, dest any, notFoundErr error) error { @@ -250,50 +201,6 @@ func ContainsCol(cols mysql.ColumnList, col mysql.Column) bool { return slices.Contains(cols, col) } -// DestName returns the name of the type passed as `destTypeStruct` as a string, -// normalized for compatibility with the Jet QRM. -func DestName(destTypeStruct interface{}, path ...string) string { - v := reflect.ValueOf(destTypeStruct) - for v.Kind() == reflect.Ptr { - v = v.Elem() - } - - destIdent := v.Type().String() - destIdent = destIdent[strings.LastIndex(destIdent, ".")+1:] - - for i, p := range path { - if v.Kind() != reflect.Struct { - dbModule.Logger().Error("DestName: path parent is not a struct", "path", destIdent+"."+strings.Join(path[:i+1], ".")) - return "" - } - - v = v.FieldByName(p) - - if !v.IsValid() { - dbModule.Logger().Error("DestName: field does not exist", "path", destIdent+"."+strings.Join(path[:i+1], ".")) - return "" - } - - destIdent += "." + p - } - - return destIdent -} - -// StringToFilter processes a string to be used as a filter in an SQL LIKE -// statement. It replaces all spaces with % and adds % to the beginning and -// end of the string. -func StringToFilter(str string) string { - // Remove any existing leading or trailing % characters - str = strings.Trim(str, "%") - - // Replace all spaces with % and add % to the beginning and end of the string - str = strings.ReplaceAll(str, " ", "%") - str = "%" + str + "%" - - return str -} - // initDBLogger initializes the database statement logger func initDBLogger() { mysql.SetQueryLogger(func(ctx context.Context, queryInfo mysql.QueryInfo) {