package dbx import ( "fmt" "reflect" "strings" "time" "github.com/go-jet/jet/v2/mysql" "golang.org/x/exp/constraints" ) // 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 } // ExprValues converts a list of values to a list of mysql.Expression values using // function f to transform the values (mysql.String for strings, mysql.Uint64, etc). func ExprValues[T any](values []T, f func(T) mysql.Expression) []mysql.Expression { expressions := make([]mysql.Expression, len(values)) for i, v := range values { expressions[i] = f(v) } return expressions } // ExprStringers converts a list of fmt.Stringers to a list of mysql.Expression values. func ExprStringers(values []fmt.Stringer) []mysql.Expression { expressions := make([]mysql.Expression, len(values)) for i, v := range values { expressions[i] = mysql.String(v.String()) } return expressions } // NowPtr returns a pointer to the current time. func NowPtr() *time.Time { now := time.Now() return &now } // Ptr returns a pointer to the given value of any scalar type. Returns nil if the value is a zero value. func Ptr[T any](val T) *T { if reflect.ValueOf(val).IsZero() { return nil } return &val } // Val returns the value of the pointer to a scalar type, or the zero value if the pointer is nil. func Val[T any](ptr *T) T { if ptr == nil { var zero T return zero } return *ptr } // TrimPtr trims the whitespace from a pointer to a string and returns nil only if the pointer is nil. func TrimPtr(s *string) *string { if s == nil { return nil } trimmed := strings.TrimSpace(*s) return &trimmed } // TrimPtrToNil trims the whitespace from a pointer to a string and returns nil // if the resulting string is empty or if the pointer is nil. func TrimPtrToNil(s *string) *string { if s == nil { return nil } trimmed := strings.TrimSpace(*s) if trimmed == "" { return nil } return &trimmed } // IsZero checks if a pointer references the zero value of a given type and // returns an error if this condition is met, otherwise returns nil if the // pointer is nil or the value is not zero. func IsZero[T any](ptr *T) error { if ptr == nil { return nil } if reflect.ValueOf(*ptr).IsZero() { return ErrValueIsZero } return nil } // ApplyPtr compares the existing value with a new value and returns the updated value if they differ. // If the new value is nil, the existing value is retained. If the new value is a zero-value, the // existing value is NOT retained, it will be set to nil. If the value is changed, targetColumn is pushed // to updatedColumns. func ApplyPtr[T constraints.Float | constraints.Integer | string | bool]( existing *T, newVal *T, updatedColumns *mysql.ColumnList, targetColumn mysql.Column, ) *T { if newVal == nil { return existing } if reflect.ValueOf(*newVal).IsZero() { newVal = nil } if newVal == nil && existing == nil || newVal != nil && existing != nil && *existing == *newVal { return existing } *updatedColumns = append(*updatedColumns, targetColumn) return newVal } // ApplyComplexPtr compares the existing value with a new value and returns the updated value if they differ. // The new value may be of a different type (e.g. existing is uint16 and new is uint64), but it will be // converted to match the current type resulting in potential loss of data. If the new value is nil, the // existing value is retained. If the new value is a zero-value, the existing value is NOT retained, it // will be set to nil. If the value is changed, targetColumn is pushed to updatedColumns. func ApplyComplexPtr[ Existing constraints.Float | constraints.Integer, New constraints.Float | constraints.Integer, ]( existing *Existing, newVal *New, updatedColumns *mysql.ColumnList, targetColumn mysql.Column, ) *Existing { if newVal == nil { return existing } cast := Existing(*newVal) // Convert new value to existing type if existing != nil && *existing == cast { return existing } if reflect.ValueOf(cast).IsZero() { if existing != nil { *updatedColumns = append(*updatedColumns, targetColumn) } return nil } else { *updatedColumns = append(*updatedColumns, targetColumn) return &cast } } type ApplyInterface[T any] interface { Equal(T) bool IsZero() bool } // ApplyInterfacePtr compares the existing value with a new value and returns the updated value if // they differ. Comparable types must have IsZero and Equal methods. If the new value is nil, the // existing value is retained. If the new value is a zero-value, the existing value is NOT retained, // it will be set to nil. If the value is changed, targetColumn is pushed to updatedColumns. func ApplyInterfacePtr[T ApplyInterface[T]]( existing *T, newVal *T, updatedColumns *mysql.ColumnList, targetColumn mysql.Column, ) *T { if newVal == nil { return existing } if existing != nil && (*existing).Equal(*newVal) { return existing } if (*newVal).IsZero() { if existing != nil { *updatedColumns = append(*updatedColumns, targetColumn) } return nil } else { *updatedColumns = append(*updatedColumns, targetColumn) return newVal } } // ApplyVal compares the existing value with a pointer to a new value and returns the updated value if they // differ. If the new value is nil, the existing value is retained. If the value is changed, targetColumn // is pushed to updatedColumns func ApplyVal[T constraints.Float | constraints.Integer | string | bool]( existing T, newVal *T, updatedColumns *mysql.ColumnList, targetColumn mysql.Column, ) T { if newVal == nil { return existing } if existing == *newVal { return existing } *updatedColumns = append(*updatedColumns, targetColumn) return *newVal }