déplacement des spécifiques dans le dialecte

This commit is contained in:
2026-08-08 21:54:09 +02:00
parent 4caff16e01
commit a25627482a
2 changed files with 242 additions and 243 deletions
+14 -243
View File
@@ -17,6 +17,9 @@ type dbfield struct {
type Dialect interface {
TableExists(db *sql.DB, tableName string) (bool, error)
ColumnSpec(f dbfield) (string, error)
// ScanResult scans a single row into the provided destination slice
// The row parameter can be *sql.Row or *sql.Rows, both implement Scan(dest ...any) error
ScanResult(row interface{ Scan(dest ...any) error }, fields []any, fieldTypes []dbfield) error
}
type Table[T any] struct {
@@ -89,27 +92,21 @@ func (t Table[T]) Get(id string) (*T, error) {
}
var obj T
// Use a slice of pointers to struct fields for scanning
// We need to pass pointers to each field in the order of the SQL columns
// Create field destinations
ptrVal := reflect.ValueOf(&obj).Elem()
fields := make([]any, len(t.fields))
for i, f := range t.fields {
fieldValue := ptrVal.FieldByName(f.name)
if !fieldValue.IsValid() {
return nil, fmt.Errorf("field %s not found in struct", f.name)
}
// For all fields, pass the address so Scan can set the value
// For pointer fields (e.g., *string), this gives **T
// For value fields (e.g., string), this gives *T
fields[i] = fieldValue.Addr().Interface()
}
if err := row.Scan(fields...); err != nil {
// Handle time scanning manually for SQLite which returns dates as strings
if strings.Contains(err.Error(), "unsupported Scan, storing driver.Value type string into type *time.Time") {
return t.getWithTimeHandling(id)
// Use dialect to scan with proper type handling
if err := t.dialect.ScanResult(row, fields, t.fields); err != nil {
if err == sql.ErrNoRows {
return nil, sql.ErrNoRows
}
return nil, err
}
@@ -117,89 +114,6 @@ func (t Table[T]) Get(id string) (*T, error) {
return &obj, nil
}
func (t Table[T]) getWithTimeHandling(id string) (*T, error) {
row := t.db.QueryRow(t.selectbyid, id)
if row == nil {
return nil, sql.ErrNoRows
}
var obj T
ptrVal := reflect.ValueOf(&obj).Elem()
// Create a slice to hold all values, using string for time fields
tempFields := make([]any, len(t.fields))
fieldInfos := make([]struct {
index int
fieldValue reflect.Value
isTime bool
isTimePtr bool
}, len(t.fields))
for i, f := range t.fields {
fieldValue := ptrVal.FieldByName(f.name)
if !fieldValue.IsValid() {
return nil, fmt.Errorf("field %s not found in struct", f.name)
}
// Check if this is a time.Time or *time.Time field
isTime := fieldValue.Type() == reflect.TypeOf(time.Time{})
isTimePtr := fieldValue.Kind() == reflect.Ptr && fieldValue.Type().Elem() == reflect.TypeOf(time.Time{})
fieldInfos[i] = struct {
index int
fieldValue reflect.Value
isTime bool
isTimePtr bool
}{
index: i,
fieldValue: fieldValue,
isTime: isTime,
isTimePtr: isTimePtr,
}
// For time fields, use a string to capture the raw value
if isTime || isTimePtr {
var s sql.NullString
tempFields[i] = &s
} else {
tempFields[i] = fieldValue.Addr().Interface()
}
}
if err := row.Scan(tempFields...); err != nil {
return nil, err
}
// Copy values back, parsing time fields
for _, info := range fieldInfos {
if info.isTime || info.isTimePtr {
s := tempFields[info.index].(*sql.NullString)
if s.Valid {
t, err := time.Parse(time.RFC3339, s.String)
if err != nil {
// Try other common formats
t, err = time.Parse("2006-01-02 15:04:05", s.String)
if err != nil {
t, err = time.Parse("2006-01-02T15:04:05Z", s.String)
if err != nil {
continue // Skip if we can't parse
}
}
}
if info.isTime {
info.fieldValue.Set(reflect.ValueOf(t))
} else if info.isTimePtr {
ptr := reflect.New(info.fieldValue.Type().Elem())
ptr.Elem().Set(reflect.ValueOf(t))
info.fieldValue.Set(ptr)
}
}
}
}
return &obj, nil
}
func (t Table[T]) SelectOne(where string, args ...any) (*T, error) {
query := t.selectwhere + " where " + where + " limit 1"
row := t.db.QueryRow(query, args...)
@@ -210,7 +124,6 @@ func (t Table[T]) SelectOne(where string, args ...any) (*T, error) {
var obj T
ptrVal := reflect.ValueOf(&obj).Elem()
fields := make([]any, len(t.fields))
for i, f := range t.fields {
fieldValue := ptrVal.FieldByName(f.name)
if !fieldValue.IsValid() {
@@ -219,9 +132,9 @@ func (t Table[T]) SelectOne(where string, args ...any) (*T, error) {
fields[i] = fieldValue.Addr().Interface()
}
if err := row.Scan(fields...); err != nil {
if strings.Contains(err.Error(), "unsupported Scan, storing driver.Value type string into type *time.Time") {
return t.selectOneWithTimeHandling(where, args...)
if err := t.dialect.ScanResult(row, fields, t.fields); err != nil {
if err == sql.ErrNoRows {
return nil, sql.ErrNoRows
}
return nil, err
}
@@ -229,16 +142,6 @@ func (t Table[T]) SelectOne(where string, args ...any) (*T, error) {
return &obj, nil
}
func (t Table[T]) selectOneWithTimeHandling(where string, args ...any) (*T, error) {
query := t.selectwhere + " where " + where + " limit 1"
row := t.db.QueryRow(query, args...)
if row == nil {
return nil, sql.ErrNoRows
}
return t.scanWithTimeHandling(row)
}
func (t Table[T]) SelectWhere(where string, args ...any) ([]*T, error) {
query := t.selectwhere
if where != "" {
@@ -265,12 +168,9 @@ func (t Table[T]) SelectWhere(where string, args ...any) ([]*T, error) {
fields[i] = fieldValue.Addr().Interface()
}
if err := rows.Scan(fields...); err != nil {
if strings.Contains(err.Error(), "unsupported Scan, storing driver.Value type string into type *time.Time") {
// Need to re-execute with time handling
rows.Close()
return t.selectWhereWithTimeHandling(where, args...)
}
// Use dialect to scan with proper type handling
// *sql.Rows implements Scanner interface
if err := t.dialect.ScanResult(rows, fields, t.fields); err != nil {
return nil, err
}
results = append(results, &obj)
@@ -283,135 +183,6 @@ func (t Table[T]) SelectWhere(where string, args ...any) ([]*T, error) {
return results, nil
}
func (t Table[T]) selectWhereWithTimeHandling(where string, args ...any) ([]*T, error) {
query := t.selectwhere
if where != "" {
query += " where " + where
}
rows, err := t.db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var results []*T
for rows.Next() {
obj, err := t.scanWithTimeHandling(rows)
if err != nil {
return nil, err
}
results = append(results, obj)
}
if err := rows.Err(); err != nil {
return nil, err
}
return results, nil
}
func (t Table[T]) scanWithTimeHandling(scanner any) (*T, error) {
var obj T
ptrVal := reflect.ValueOf(&obj).Elem()
// Create a slice to hold all values, using string for time fields
tempFields := make([]any, len(t.fields))
fieldInfos := make([]struct {
index int
fieldValue reflect.Value
isTime bool
isTimePtr bool
}, len(t.fields))
for i, f := range t.fields {
fieldValue := ptrVal.FieldByName(f.name)
if !fieldValue.IsValid() {
return nil, fmt.Errorf("field %s not found in struct", f.name)
}
// Check if this is a time.Time or *time.Time field
isTime := fieldValue.Type() == reflect.TypeOf(time.Time{})
var isTimePtr bool
if fieldValue.Kind() == reflect.Ptr {
isTimePtr = fieldValue.Type().Elem() == reflect.TypeOf(time.Time{})
}
fieldInfos[i] = struct {
index int
fieldValue reflect.Value
isTime bool
isTimePtr bool
}{
index: i,
fieldValue: fieldValue,
isTime: isTime,
isTimePtr: isTimePtr,
}
// For time fields, use a string to capture the raw value
if isTime || isTimePtr {
var s sql.NullString
tempFields[i] = &s
} else {
tempFields[i] = fieldValue.Addr().Interface()
}
}
var err error
switch s := scanner.(type) {
case *sql.Row:
err = s.Scan(tempFields...)
case *sql.Rows:
err = s.Scan(tempFields...)
default:
return nil, fmt.Errorf("unsupported scanner type")
}
if err != nil {
return nil, err
}
// Copy values back, parsing time fields
for _, info := range fieldInfos {
if info.isTime || info.isTimePtr {
s := tempFields[info.index].(*sql.NullString)
if s.Valid {
t, err := time.Parse(time.RFC3339, s.String)
if err != nil {
// Try other common formats
t, err = time.Parse("2006-01-02 15:04:05", s.String)
if err != nil {
t, err = time.Parse("2006-01-02T15:04:05Z", s.String)
if err != nil {
// Try SQLite format: YYYY-MM-DD HH:MM:SS
t, err = time.Parse("2006-01-02 15:04:05.999999999-07:00", s.String)
if err != nil {
// Try simpler format
t, err = time.Parse("2006-01-02 15:04:05", s.String)
if err != nil {
continue
}
}
}
}
}
if info.isTime {
info.fieldValue.Set(reflect.ValueOf(t))
} else if info.isTimePtr {
ptr := reflect.New(info.fieldValue.Type().Elem())
ptr.Elem().Set(reflect.ValueOf(t))
info.fieldValue.Set(ptr)
}
}
}
}
return &obj, nil
}
func (t Table[T]) Delete(id string) error {
_, err := t.db.Exec(t.deletebyid, id)
return err