mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-10-04 14:36:50 +02:00
240 lines
6.4 KiB
Go
240 lines
6.4 KiB
Go
package g
|
|
|
|
import (
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
// Scan implements the database/sql.Scanner interface for Option[T].
|
|
//
|
|
// Behavior:
|
|
// - If src is nil, the Option is set to None (SQL NULL).
|
|
// - If T implements sql.Scanner, its Scan method is used.
|
|
// - If src can be directly assigned to T, it is assigned as-is.
|
|
// - Otherwise, common database type conversions are attempted (e.g., int64 → int, []byte → string).
|
|
//
|
|
// Supported conversions (common SQL types):
|
|
// - INTEGER → int, int8, int16, int32, int64, uint*
|
|
// - REAL → float32, float64
|
|
// - TEXT → string, []byte
|
|
// - BLOB → []byte
|
|
// - BOOLEAN → bool
|
|
// - TIMESTAMP → time.Time
|
|
//
|
|
// Driver-owned []byte buffers (BLOB/TEXT) are copied before being stored, so the
|
|
// scanned Option keeps a value that is safe to retain across subsequent rows
|
|
// (per the database/sql Scanner contract).
|
|
//
|
|
// Returns an error if the value cannot be converted to T.
|
|
func (o *Option[T]) Scan(src any) error {
|
|
if src == nil {
|
|
*o = None[T]()
|
|
return nil
|
|
}
|
|
|
|
var v T
|
|
|
|
if scanner, ok := any(&v).(sql.Scanner); ok {
|
|
if err := scanner.Scan(src); err != nil {
|
|
return err
|
|
}
|
|
*o = Some(v)
|
|
return nil
|
|
}
|
|
|
|
if val, ok := src.(T); ok {
|
|
// Copy driver-owned []byte buffers before storing: database/sql may
|
|
// overwrite the backing array on the next row (database/sql contract).
|
|
if b, isBytes := any(val).([]byte); isBytes {
|
|
val = any(append([]byte(nil), b...)).(T)
|
|
}
|
|
*o = Some(val)
|
|
return nil
|
|
}
|
|
|
|
if converted, ok := convertToT[T](src); ok {
|
|
*o = Some(converted)
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("Option.Scan: cannot scan %T into %T", src, v)
|
|
}
|
|
|
|
// Value implements the database/sql/driver.Valuer interface for Option[T].
|
|
//
|
|
// Behavior:
|
|
// - If the Option is None, returns nil (SQL NULL).
|
|
// - If T implements driver.Valuer, its Value method is used.
|
|
// - If the underlying value is already a valid driver.Value type (int64, float64, bool, []byte, string, time.Time), it is returned directly.
|
|
// - Otherwise, safe conversions are applied (int → int64, uint → int64, float32 → float64).
|
|
//
|
|
// Returns an error if the value cannot be converted to a driver.Value.
|
|
func (o Option[T]) Value() (driver.Value, error) {
|
|
if o.IsNone() {
|
|
return nil, nil
|
|
}
|
|
|
|
if valuer, ok := any(o.v).(driver.Valuer); ok {
|
|
return valuer.Value()
|
|
}
|
|
|
|
switch val := any(o.v).(type) {
|
|
case int64, float64, bool, []byte, string, time.Time:
|
|
return val, nil
|
|
default:
|
|
if converted, ok := convertToDriverValue(val); ok {
|
|
return converted, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("Option.Value: unsupported type %T", o.v)
|
|
}
|
|
|
|
// convertToT attempts to safely convert a source value from database/sql
|
|
// into type T, supporting common database types without using reflection.
|
|
//
|
|
// Only standard Go primitive types are supported:
|
|
// - int64 → int, int8, int16, int32, uint*, uint64 (if fits)
|
|
// - float64 → float32, float64
|
|
// - string / []byte → string
|
|
// - []byte / string → []byte
|
|
// - bool → bool
|
|
// - time.Time → time.Time
|
|
//
|
|
// Returns the converted value and true on success, otherwise the zero value of T and false.
|
|
func convertToT[T any](src any) (T, bool) {
|
|
var zero T
|
|
|
|
switch any(zero).(type) {
|
|
case int:
|
|
if i64, ok := src.(int64); ok && fitsInt(i64) {
|
|
return any(int(i64)).(T), true
|
|
}
|
|
case int8:
|
|
if i64, ok := src.(int64); ok && fitsInt8(i64) {
|
|
return any(int8(i64)).(T), true
|
|
}
|
|
case int16:
|
|
if i64, ok := src.(int64); ok && fitsInt16(i64) {
|
|
return any(int16(i64)).(T), true
|
|
}
|
|
case int32:
|
|
if i64, ok := src.(int64); ok && fitsInt32(i64) {
|
|
return any(int32(i64)).(T), true
|
|
}
|
|
case int64:
|
|
if i64, ok := src.(int64); ok {
|
|
return any(i64).(T), true
|
|
}
|
|
case uint:
|
|
if i64, ok := src.(int64); ok && i64 >= 0 {
|
|
return any(uint(i64)).(T), true
|
|
}
|
|
case uint8:
|
|
if i64, ok := src.(int64); ok && i64 >= 0 && i64 <= math.MaxUint8 {
|
|
return any(uint8(i64)).(T), true
|
|
}
|
|
case uint16:
|
|
if i64, ok := src.(int64); ok && i64 >= 0 && i64 <= math.MaxUint16 {
|
|
return any(uint16(i64)).(T), true
|
|
}
|
|
case uint32:
|
|
if i64, ok := src.(int64); ok && i64 >= 0 && i64 <= math.MaxUint32 {
|
|
return any(uint32(i64)).(T), true
|
|
}
|
|
case uint64:
|
|
if i64, ok := src.(int64); ok && i64 >= 0 {
|
|
return any(uint64(i64)).(T), true
|
|
}
|
|
case float32:
|
|
if f64, ok := src.(float64); ok {
|
|
return any(float32(f64)).(T), true
|
|
}
|
|
case float64:
|
|
if f64, ok := src.(float64); ok {
|
|
return any(f64).(T), true
|
|
}
|
|
case string:
|
|
switch v := src.(type) {
|
|
case string:
|
|
return any(v).(T), true
|
|
case []byte:
|
|
return any(string(v)).(T), true
|
|
}
|
|
case []byte:
|
|
switch v := src.(type) {
|
|
case []byte:
|
|
// Copy the driver-owned buffer: database/sql may reuse it on the next row.
|
|
return any(append([]byte(nil), v...)).(T), true
|
|
case string:
|
|
return any([]byte(v)).(T), true
|
|
}
|
|
case bool:
|
|
if b, ok := src.(bool); ok {
|
|
return any(b).(T), true
|
|
}
|
|
case time.Time:
|
|
if t, ok := src.(time.Time); ok {
|
|
return any(t).(T), true
|
|
}
|
|
}
|
|
|
|
return zero, false
|
|
}
|
|
|
|
// convertToDriverValue safely converts primitive Go types to a value
|
|
// compatible with database/sql driver.Value.
|
|
//
|
|
// Supported conversions:
|
|
// - int, int8, int16, int32 → int64
|
|
// - uint8, uint16, uint32 → int64
|
|
// - uint, uint64 → int64 (only if <= math.MaxInt64)
|
|
// - float32 → float64
|
|
//
|
|
// Returns the converted value and true on success, otherwise nil and false.
|
|
func convertToDriverValue(val any) (driver.Value, bool) {
|
|
switch v := val.(type) {
|
|
case int:
|
|
return int64(v), true
|
|
case int8:
|
|
return int64(v), true
|
|
case int16:
|
|
return int64(v), true
|
|
case int32:
|
|
return int64(v), true
|
|
case uint:
|
|
if uint64(v) <= math.MaxInt64 {
|
|
return int64(v), true
|
|
}
|
|
case uint8:
|
|
return int64(v), true
|
|
case uint16:
|
|
return int64(v), true
|
|
case uint32:
|
|
return int64(v), true
|
|
case uint64:
|
|
if v <= math.MaxInt64 {
|
|
return int64(v), true
|
|
}
|
|
case float32:
|
|
return float64(v), true
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// fitsInt returns true if i fits in a Go int.
|
|
func fitsInt(i int64) bool { return i >= math.MinInt && i <= math.MaxInt }
|
|
|
|
// fitsInt8 returns true if i fits in an int8.
|
|
func fitsInt8(i int64) bool { return i >= math.MinInt8 && i <= math.MaxInt8 }
|
|
|
|
// fitsInt16 returns true if i fits in an int16.
|
|
func fitsInt16(i int64) bool { return i >= math.MinInt16 && i <= math.MaxInt16 }
|
|
|
|
// fitsInt32 returns true if i fits in an int32.
|
|
func fitsInt32(i int64) bool { return i >= math.MinInt32 && i <= math.MaxInt32 }
|