déplacement des spécifiques dans le dialecte
This commit is contained in:
@@ -3,7 +3,9 @@ package lasebuche
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SqliteDialect struct{}
|
type SqliteDialect struct{}
|
||||||
@@ -59,3 +61,229 @@ func (d *SqliteDialect) ColumnSpec(f dbfield) (string, error) {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%s %s not null default %s", f.dbname, convertType(f), defaultValue(f)), nil
|
return fmt.Sprintf("%s %s not null default %s", f.dbname, convertType(f), defaultValue(f)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScanResult scans a single row into the provided fields slice.
|
||||||
|
// For SQLite, this handles time.Time fields which are returned as strings.
|
||||||
|
func (d *SqliteDialect) ScanResult(row interface{ Scan(dest ...any) error }, fields []any, fieldTypes []dbfield) error {
|
||||||
|
// SQLite returns time.Time as strings, so we need special handling
|
||||||
|
// Check if any field is a time type
|
||||||
|
hasTimeField := false
|
||||||
|
for _, f := range fieldTypes {
|
||||||
|
if f.stype == "time.Time" || f.stype == "*time.Time" {
|
||||||
|
hasTimeField = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasTimeField {
|
||||||
|
// No time fields, use standard scan
|
||||||
|
return row.Scan(fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have time fields - need custom handling
|
||||||
|
// Create temp destinations for all fields
|
||||||
|
tempFields := make([]any, len(fields))
|
||||||
|
fieldInfos := make([]struct {
|
||||||
|
index int
|
||||||
|
dest any
|
||||||
|
isTime bool
|
||||||
|
isTimePtr bool
|
||||||
|
}, len(fields))
|
||||||
|
|
||||||
|
for i, f := range fieldTypes {
|
||||||
|
isTime := f.stype == "time.Time"
|
||||||
|
isTimePtr := f.stype == "*time.Time"
|
||||||
|
|
||||||
|
fieldInfos[i] = struct {
|
||||||
|
index int
|
||||||
|
dest any
|
||||||
|
isTime bool
|
||||||
|
isTimePtr bool
|
||||||
|
}{
|
||||||
|
index: i,
|
||||||
|
dest: fields[i],
|
||||||
|
isTime: isTime,
|
||||||
|
isTimePtr: isTimePtr,
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTime || isTimePtr {
|
||||||
|
var s sql.NullString
|
||||||
|
tempFields[i] = &s
|
||||||
|
} else {
|
||||||
|
tempFields[i] = fields[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := row.Scan(tempFields...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy time values back to original destinations
|
||||||
|
for _, info := range fieldInfos {
|
||||||
|
if info.isTime || info.isTimePtr {
|
||||||
|
s := tempFields[info.index].(*sql.NullString)
|
||||||
|
if s.Valid {
|
||||||
|
// Try to parse the string as time
|
||||||
|
t, err := tryParseTime(s.String)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse time field: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value in the original destination
|
||||||
|
destVal := reflect.ValueOf(info.dest).Elem()
|
||||||
|
if info.isTime {
|
||||||
|
// dest is *time.Time, set the value
|
||||||
|
destVal.Set(reflect.ValueOf(t))
|
||||||
|
} else if info.isTimePtr {
|
||||||
|
// dest is **time.Time, allocate and set
|
||||||
|
ptr := reflect.New(destVal.Type().Elem())
|
||||||
|
ptr.Elem().Set(reflect.ValueOf(t))
|
||||||
|
destVal.Set(ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanRows scans multiple rows using the dialect-specific logic
|
||||||
|
func (d *SqliteDialect) ScanRows(rows *sql.Rows, fieldDest func() []any, fieldTypes []dbfield) ([]any, error) {
|
||||||
|
// Check if any field is a time type
|
||||||
|
hasTimeField := false
|
||||||
|
for _, f := range fieldTypes {
|
||||||
|
if f.stype == "time.Time" || f.stype == "*time.Time" {
|
||||||
|
hasTimeField = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasTimeField {
|
||||||
|
// No time fields, use standard scan
|
||||||
|
var results []any
|
||||||
|
for rows.Next() {
|
||||||
|
fields := fieldDest()
|
||||||
|
if err := rows.Scan(fields...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = append(results, fields...)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have time fields - need custom handling
|
||||||
|
var results []any
|
||||||
|
for rows.Next() {
|
||||||
|
fields := fieldDest()
|
||||||
|
|
||||||
|
// Create temp destinations for all fields
|
||||||
|
tempFields := make([]any, len(fields))
|
||||||
|
fieldInfos := make([]struct {
|
||||||
|
index int
|
||||||
|
dest any
|
||||||
|
isTime bool
|
||||||
|
isTimePtr bool
|
||||||
|
}, len(fields))
|
||||||
|
|
||||||
|
for i, f := range fieldTypes {
|
||||||
|
isTime := f.stype == "time.Time"
|
||||||
|
isTimePtr := f.stype == "*time.Time"
|
||||||
|
|
||||||
|
fieldInfos[i] = struct {
|
||||||
|
index int
|
||||||
|
dest any
|
||||||
|
isTime bool
|
||||||
|
isTimePtr bool
|
||||||
|
}{
|
||||||
|
index: i,
|
||||||
|
dest: fields[i],
|
||||||
|
isTime: isTime,
|
||||||
|
isTimePtr: isTimePtr,
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTime || isTimePtr {
|
||||||
|
var s sql.NullString
|
||||||
|
tempFields[i] = &s
|
||||||
|
} else {
|
||||||
|
tempFields[i] = fields[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Scan(tempFields...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy time values back to original destinations
|
||||||
|
for _, info := range fieldInfos {
|
||||||
|
if info.isTime || info.isTimePtr {
|
||||||
|
s := tempFields[info.index].(*sql.NullString)
|
||||||
|
if s.Valid {
|
||||||
|
t, err := tryParseTime(s.String)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse time field: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value in the original destination
|
||||||
|
destVal := reflect.ValueOf(info.dest).Elem()
|
||||||
|
if info.isTime {
|
||||||
|
// dest is *time.Time, set the value
|
||||||
|
destVal.Set(reflect.ValueOf(t))
|
||||||
|
} else if info.isTimePtr {
|
||||||
|
// dest is **time.Time, allocate and set
|
||||||
|
ptr := reflect.New(destVal.Type().Elem())
|
||||||
|
ptr.Elem().Set(reflect.ValueOf(t))
|
||||||
|
destVal.Set(ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseTime attempts to parse a time string in various common formats
|
||||||
|
// SQLite driver returns time as "2006-01-02 15:04:05.999999999 -0700 MST m=+0.000000000"
|
||||||
|
// We need to strip the monotonic clock part (m=...) before parsing
|
||||||
|
func tryParseTime(s string) (time.Time, error) {
|
||||||
|
// Remove monotonic clock part if present
|
||||||
|
// Format: "2006-01-02 15:04:05.999999999 -0700 MST m=+0.000000000"
|
||||||
|
if idx := strings.Index(s, " m="); idx != -1 {
|
||||||
|
s = s[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try common formats
|
||||||
|
formats := []string{
|
||||||
|
time.RFC3339,
|
||||||
|
time.RFC3339Nano,
|
||||||
|
"2006-01-02T15:04:05Z07:00",
|
||||||
|
"2006-01-02 15:04:05.999999999-07:00",
|
||||||
|
"2006-01-02 15:04:05",
|
||||||
|
"2006-01-02T15:04:05",
|
||||||
|
"2006-01-02 15:04:05+00:00",
|
||||||
|
// Go's default time.String() format (without monotonic clock)
|
||||||
|
"2006-01-02 15:04:05.999999999 -0700 MST",
|
||||||
|
// SQLite without timezone
|
||||||
|
"2006-01-02 15:04:05.999999",
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var t time.Time
|
||||||
|
for _, format := range formats {
|
||||||
|
t, err = time.Parse(format, s)
|
||||||
|
if err == nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}, fmt.Errorf("unable to parse time: %s", s)
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ type dbfield struct {
|
|||||||
type Dialect interface {
|
type Dialect interface {
|
||||||
TableExists(db *sql.DB, tableName string) (bool, error)
|
TableExists(db *sql.DB, tableName string) (bool, error)
|
||||||
ColumnSpec(f dbfield) (string, 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 {
|
type Table[T any] struct {
|
||||||
@@ -89,27 +92,21 @@ func (t Table[T]) Get(id string) (*T, error) {
|
|||||||
}
|
}
|
||||||
var obj T
|
var obj T
|
||||||
|
|
||||||
// Use a slice of pointers to struct fields for scanning
|
// Create field destinations
|
||||||
// We need to pass pointers to each field in the order of the SQL columns
|
|
||||||
ptrVal := reflect.ValueOf(&obj).Elem()
|
ptrVal := reflect.ValueOf(&obj).Elem()
|
||||||
fields := make([]any, len(t.fields))
|
fields := make([]any, len(t.fields))
|
||||||
|
|
||||||
for i, f := range t.fields {
|
for i, f := range t.fields {
|
||||||
fieldValue := ptrVal.FieldByName(f.name)
|
fieldValue := ptrVal.FieldByName(f.name)
|
||||||
if !fieldValue.IsValid() {
|
if !fieldValue.IsValid() {
|
||||||
return nil, fmt.Errorf("field %s not found in struct", f.name)
|
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()
|
fields[i] = fieldValue.Addr().Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := row.Scan(fields...); err != nil {
|
// Use dialect to scan with proper type handling
|
||||||
// Handle time scanning manually for SQLite which returns dates as strings
|
if err := t.dialect.ScanResult(row, fields, t.fields); err != nil {
|
||||||
if strings.Contains(err.Error(), "unsupported Scan, storing driver.Value type string into type *time.Time") {
|
if err == sql.ErrNoRows {
|
||||||
return t.getWithTimeHandling(id)
|
return nil, sql.ErrNoRows
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -117,89 +114,6 @@ func (t Table[T]) Get(id string) (*T, error) {
|
|||||||
return &obj, nil
|
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) {
|
func (t Table[T]) SelectOne(where string, args ...any) (*T, error) {
|
||||||
query := t.selectwhere + " where " + where + " limit 1"
|
query := t.selectwhere + " where " + where + " limit 1"
|
||||||
row := t.db.QueryRow(query, args...)
|
row := t.db.QueryRow(query, args...)
|
||||||
@@ -210,7 +124,6 @@ func (t Table[T]) SelectOne(where string, args ...any) (*T, error) {
|
|||||||
var obj T
|
var obj T
|
||||||
ptrVal := reflect.ValueOf(&obj).Elem()
|
ptrVal := reflect.ValueOf(&obj).Elem()
|
||||||
fields := make([]any, len(t.fields))
|
fields := make([]any, len(t.fields))
|
||||||
|
|
||||||
for i, f := range t.fields {
|
for i, f := range t.fields {
|
||||||
fieldValue := ptrVal.FieldByName(f.name)
|
fieldValue := ptrVal.FieldByName(f.name)
|
||||||
if !fieldValue.IsValid() {
|
if !fieldValue.IsValid() {
|
||||||
@@ -219,9 +132,9 @@ func (t Table[T]) SelectOne(where string, args ...any) (*T, error) {
|
|||||||
fields[i] = fieldValue.Addr().Interface()
|
fields[i] = fieldValue.Addr().Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := row.Scan(fields...); err != nil {
|
if err := t.dialect.ScanResult(row, fields, t.fields); err != nil {
|
||||||
if strings.Contains(err.Error(), "unsupported Scan, storing driver.Value type string into type *time.Time") {
|
if err == sql.ErrNoRows {
|
||||||
return t.selectOneWithTimeHandling(where, args...)
|
return nil, sql.ErrNoRows
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -229,16 +142,6 @@ func (t Table[T]) SelectOne(where string, args ...any) (*T, error) {
|
|||||||
return &obj, nil
|
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) {
|
func (t Table[T]) SelectWhere(where string, args ...any) ([]*T, error) {
|
||||||
query := t.selectwhere
|
query := t.selectwhere
|
||||||
if where != "" {
|
if where != "" {
|
||||||
@@ -265,12 +168,9 @@ func (t Table[T]) SelectWhere(where string, args ...any) ([]*T, error) {
|
|||||||
fields[i] = fieldValue.Addr().Interface()
|
fields[i] = fieldValue.Addr().Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Scan(fields...); err != nil {
|
// Use dialect to scan with proper type handling
|
||||||
if strings.Contains(err.Error(), "unsupported Scan, storing driver.Value type string into type *time.Time") {
|
// *sql.Rows implements Scanner interface
|
||||||
// Need to re-execute with time handling
|
if err := t.dialect.ScanResult(rows, fields, t.fields); err != nil {
|
||||||
rows.Close()
|
|
||||||
return t.selectWhereWithTimeHandling(where, args...)
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
results = append(results, &obj)
|
results = append(results, &obj)
|
||||||
@@ -283,135 +183,6 @@ func (t Table[T]) SelectWhere(where string, args ...any) ([]*T, error) {
|
|||||||
return results, nil
|
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 {
|
func (t Table[T]) Delete(id string) error {
|
||||||
_, err := t.db.Exec(t.deletebyid, id)
|
_, err := t.db.Exec(t.deletebyid, id)
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user