Files
dbHandler/dbHandler.go
2025-11-30 21:01:53 +01:00

373 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package dbHandler
import (
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"gitea.tecamino.com/paadi/tecamino-logger/logging"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
// DBHandler
//
// Description:
//
// Wraps the GORM database connection and provides helper methods for
// common CRUD operations, as well as integrated logging for traceability.
//
// Fields:
// - db: Active GORM database connection.
// - logger: Pointer to a custom logger instance for structured logging.
type DBHandler struct {
db *gorm.DB
logger *logging.Logger
NewCreatedDB bool // indicater for new created database
}
// NewDBHandler
//
// Description:
//
// Creates a new database handler using the specified SQLite database file.
//
// Behavior:
// 1. Opens a GORM connection to the database file at `dbPath`.
// 2. Wraps it in a `DBHandler` struct with logging support.
//
// Parameters:
// - dbPath: Path to the SQLite database file.
// - logger: Logging instance to record DB operations.
//
// Returns:
// - dH: A pointer to the initialized `DBHandler`.
// - err: Any error encountered during database connection.
func NewDBHandler(name, path string, logger *logging.Logger) (dH *DBHandler, err error) {
if logger == nil {
logger, err = logging.NewLogger(filepath.Join(path, name)+".log", nil)
if err != nil {
return
}
}
dH = &DBHandler{logger: logger}
if filepath.Ext(name) == "" {
name += ".db"
}
if path != "" {
if _, err := os.Stat(path); err != nil {
if err := os.MkdirAll(path, 0666); err != err {
return nil, err
}
dH.NewCreatedDB = true
}
}
dbPath := filepath.Join(path, name)
logger.Debug("NewDBHandler", "open database "+dbPath)
dH.db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
return
}
// close DB
func (dH *DBHandler) Close() error {
sqlDB, err := dH.db.DB()
if err != nil {
return err
}
return sqlDB.Close()
}
// addNewTable
//
// Description:
//
// Uses GORMs `AutoMigrate` to create or update the database schema
// for the provided model type.
//
// Parameters:
// - model: Struct type representing the database table schema.
//
// Returns:
// - error: Any migration error encountered.
func (dH *DBHandler) AddNewTable(model any) error {
return dH.db.AutoMigrate(&model)
}
// addNewColum
//
// Description:
//
// Inserts a new record into the database table corresponding to `model`.
//
// Parameters:
// - model: Struct instance containing values to be inserted.
//
// Returns:
// - error: Any error encountered during record creation.
func (dH *DBHandler) AddNewColum(model any) error {
return dH.db.Create(model).Error
}
// getById
//
// Description:
//
// Retrieves a record (or all records) from a table by numeric ID.
//
// Behavior:
// - If `id == 0`, returns all records in the table.
// - Otherwise, fetches the record matching the given ID.
//
// Parameters:
// - model: Pointer to a slice or struct to store the result.
// - id: Numeric ID to query by.
//
// Returns:
// - error: Any query error or “not found” message.
func (dH *DBHandler) GetById(model any, id uint, relations ...string) (err error) {
dH.logger.Debug("getById", "find id "+fmt.Sprint(id))
query := dH.db
for _, relation := range relations {
query = query.Preload(relation)
}
if id == 0 {
return query.Find(model).Error
}
err = query.First(model, id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("no record found for id: %v", id)
} else if err != nil {
return fmt.Errorf("query failed: %w", err)
}
return nil
}
// getByKey
//
// Description:
//
// Retrieves one or more records matching a key/value pair.
//
// Behavior:
// - If `LikeSearch` is true, performs a SQL LIKE query.
// - Otherwise, performs an exact match query.
//
// Parameters:
// - model: Pointer to a slice or struct to store results.
// - key: Column name (e.g., "email").
// - value: Value to match or partially match.
// - LikeSearch: If true, replaces '*' with '%' for wildcard matching.
//
// Returns:
// - error: Any database query error.
func (dH *DBHandler) GetByKey(model any, key string, value any, LikeSearch bool, relations ...string) error {
query := dH.db
for _, relation := range relations {
query = query.Preload(relation)
}
if LikeSearch {
value = strings.ReplaceAll(fmt.Sprint(value), "*", "%")
dH.logger.Debug("getByKey", "find like key "+key+" value "+fmt.Sprint(value))
return query.Where(key+" LIKE ?", value).Find(model).Error
}
dH.logger.Debug("getByKey", "find equal key "+key+" value "+fmt.Sprint(value))
return query.Find(model, key+" = ?", value).Error
}
// updateValuesById
//
// Description:
//
// Updates record fields based on their unique ID.
//
// Behavior:
// 1. Confirms that `model` is a pointer to a struct.
// 2. Fetches the record by ID.
// 3. Updates all non-zero fields using `gorm.Model.Updates`.
//
// Parameters:
// - model: Pointer to struct containing new values.
// - id: Numeric ID of the record to update.
//
// Returns:
// - error: If the model is invalid or query/update fails.
func (dH *DBHandler) UpdateValuesById(model any, id uint, relations ...string) error {
dH.logger.Debug("updateValuesByKey", "model"+fmt.Sprint(model))
modelType := reflect.TypeOf(model)
if modelType.Kind() != reflect.Ptr {
return errors.New("model must be a pointer to struct")
}
lookUpModel := reflect.New(modelType.Elem()).Interface()
if err := dH.GetById(lookUpModel, id, relations...); err != nil {
return err
}
// Preload relations
query := dH.db
for _, relation := range relations {
query = query.Preload(relation)
}
// Update only provided keys
return query.Model(lookUpModel).Updates(model).Error
}
// updateValuesByKey
//
// Description:
//
// Updates records based on a key/value match.
//
// Behavior:
// 1. Confirms model type.
// 2. Fetches the matching record(s) using `getByKey`.
// 3. Updates all non-zero fields.
//
// Parameters:
// - model: Pointer to struct containing updated values.
// - key: Column name to filter by.
// - value: Value to match.
//
// Returns:
// - error: Any query or update error.
func (dH *DBHandler) UpdateValuesByKey(model any, key string, value any, relations ...string) error {
dH.logger.Debug("updateValuesByKey", "model"+fmt.Sprint(model))
modelType := reflect.TypeOf(model)
if modelType.Kind() != reflect.Ptr {
return errors.New("model must be a pointer to struct")
}
lookUpModel := reflect.New(modelType.Elem()).Interface()
if err := dH.GetByKey(lookUpModel, key, value, false, relations...); err != nil {
return err
}
query := dH.db
for _, relation := range relations {
query = query.Preload(relation)
}
return query.Model(lookUpModel).Updates(model).Error
}
func (dH *DBHandler) DeleteById(model any, relation string, ids ...uint) error {
if len(ids) == 0 {
return fmt.Errorf("no IDs provided")
}
if len(ids) == 1 && ids[0] == 0 {
dH.logger.Debug("deleteById", "delete ALL records!")
return dH.db.Session(&gorm.Session{AllowGlobalUpdate: true, FullSaveAssociations: true}).
Delete(model).Error
}
dH.logger.Debug("deleteById", "delete ids "+fmt.Sprint(ids))
if err := dH.db.Delete(model, ids).Error; err != nil {
return fmt.Errorf("delete failed: %w", err)
}
return nil
}
// deleteByKey
//
// Description:
//
// Deletes records that match a key/value pair.
//
// Behavior:
// - Supports LIKE queries if `LikeSearch` is true.
//
// Parameters:
// - model: Model struct type representing the table.
// - key: Column name to filter by.
// - value: Value to match.
// - LikeSearch: Whether to use wildcard search.
//
// Returns:
// - error: Any deletion error.
func (dH *DBHandler) DeleteByKey(model any, key string, value any, LikeSearch bool, relations ...string) error {
if LikeSearch {
value = strings.ReplaceAll(fmt.Sprint(value), "*", "%")
dH.logger.Debug("deleteByKey", "delete like key "+key+" value "+fmt.Sprint(value))
return dH.db.Where(key+" LIKE ?", value).Delete(model).Error
}
dH.logger.Debug("deleteByKey", "delete equal key "+key+" value "+fmt.Sprint(value))
query := dH.db
for _, relation := range relations {
query = query.Preload(relation)
}
return query.Where(key+" = ?", value).Delete(model).Error
}
func (dH *DBHandler) Exists(model any, key string, value any, likeSearch bool, relations ...string) (found bool) {
var query string
var args any
// Detect LIKE and IN behavior
val := reflect.ValueOf(value)
isSlice := val.Kind() == reflect.Slice
switch {
case isSlice:
if val.Len() == 0 {
dH.logger.Error("Exists", fmt.Sprintf("empty slice for %s", key))
return false
}
query = fmt.Sprintf("%s IN ?", key)
args = value
case likeSearch:
str := strings.ReplaceAll(fmt.Sprint(value), "*", "%")
query = fmt.Sprintf("%s LIKE ?", key)
args = str
default:
query = fmt.Sprintf("%s = ?", key)
args = value
}
dH.logger.Debug("exists", "checking existence for key "+query+" value "+fmt.Sprint(args))
datababase := dH.db
for _, relation := range relations {
datababase = datababase.Preload(relation)
}
tx := datababase.Where(query, args).Find(model)
if tx.Error != nil {
dH.logger.Error("Exists", fmt.Sprintf("query failed: %v", tx.Error))
return false
}
return tx.RowsAffected > 0
}
func (dH *DBHandler) AddRelation(model, relation any, relationName string) error {
return dH.db.Model(model).Association(relationName).Append(relation)
}
func (dH *DBHandler) DeleteRelation(model, relation any, relationName string) error {
return dH.db.Model(model).Association(relationName).Delete(relation)
}