@@ -100,18 +100,30 @@ func findFieldByDBName(schema *schema.Schema, dbName string) *schema.Field {
100
100
}
101
101
102
102
// Create typed destination for OUT parameters
103
- func createTypedDestination (fieldType reflect. Type ) interface {} {
104
- // Handle pointer types
105
- if fieldType . Kind () == reflect . Ptr {
106
- fieldType = fieldType . Elem ()
103
+ func createTypedDestination (f * schema. Field ) interface {} {
104
+ if f == nil {
105
+ var s string
106
+ return & s
107
107
}
108
108
109
- // Type-safe handling for known GORM types and SQL null types
110
- switch fieldType {
111
- case reflect .TypeOf (gorm.DeletedAt {}):
109
+ ft := f .FieldType
110
+ for ft .Kind () == reflect .Ptr {
111
+ ft = ft .Elem ()
112
+ }
113
+
114
+ if ft == reflect .TypeOf (gorm.DeletedAt {}) {
112
115
return new (sql.NullTime )
113
- case reflect .TypeOf (time.Time {}):
116
+ }
117
+ if ft == reflect .TypeOf (time.Time {}) {
118
+ if ! f .NotNull { // nullable column => keep NULLs
119
+ return new (sql.NullTime )
120
+ }
114
121
return new (time.Time )
122
+ }
123
+
124
+ switch ft {
125
+ case reflect .TypeOf (sql.NullTime {}):
126
+ return new (sql.NullTime )
115
127
case reflect .TypeOf (sql.NullInt64 {}):
116
128
return new (sql.NullInt64 )
117
129
case reflect .TypeOf (sql.NullInt32 {}):
@@ -120,33 +132,28 @@ func createTypedDestination(fieldType reflect.Type) interface{} {
120
132
return new (sql.NullFloat64 )
121
133
case reflect .TypeOf (sql.NullBool {}):
122
134
return new (sql.NullBool )
123
- case reflect .TypeOf (sql.NullTime {}):
124
- return new (sql.NullTime )
125
135
}
126
136
127
- // Handle primitive types by Kind
128
- switch fieldType .Kind () {
137
+ switch ft .Kind () {
138
+ case reflect .String :
139
+ return new (string )
140
+
141
+ case reflect .Bool :
142
+ return new (int64 )
143
+
129
144
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
130
- return new (int64 ) // Oracle returns NUMBER as int64
131
- case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 :
145
+ return new (int64 )
146
+
147
+ case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 , reflect .Uintptr :
132
148
return new (uint64 )
149
+
133
150
case reflect .Float32 , reflect .Float64 :
134
- return new (float64 ) // Oracle returns FLOAT as float64
135
- case reflect .Bool :
136
- return new (int64 ) // Oracle NUMBER(1) for boolean
137
- case reflect .String :
138
- return new (string )
139
- case reflect .Struct :
140
- // For time.Time specifically
141
- if fieldType == reflect .TypeOf (time.Time {}) {
142
- return new (time.Time )
143
- }
144
- // For other structs, use string as safe fallback
145
- return new (string )
146
- default :
147
- // For unknown types, use string as safe fallback
148
- return new (string )
151
+ return new (float64 )
149
152
}
153
+
154
+ // Fallback
155
+ var s string
156
+ return & s
150
157
}
151
158
152
159
// Convert values for Oracle-specific types
@@ -182,7 +189,7 @@ func convertValue(val interface{}) interface{} {
182
189
183
190
// Convert Oracle values back to Go types
184
191
func convertFromOracleToField (value interface {}, field * schema.Field ) interface {} {
185
- if value == nil {
192
+ if value == nil || field == nil {
186
193
return nil
187
194
}
188
195
@@ -194,7 +201,6 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
194
201
195
202
var converted interface {}
196
203
197
- // Handle special types first using type-safe comparisons
198
204
switch targetType {
199
205
case reflect .TypeOf (gorm.DeletedAt {}):
200
206
if nullTime , ok := value .(sql.NullTime ); ok {
@@ -203,7 +209,31 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
203
209
converted = gorm.DeletedAt {}
204
210
}
205
211
case reflect .TypeOf (time.Time {}):
206
- converted = value
212
+ switch vv := value .(type ) {
213
+ case time.Time :
214
+ converted = vv
215
+ case sql.NullTime :
216
+ if vv .Valid {
217
+ converted = vv .Time
218
+ } else {
219
+ // DB returned NULL
220
+ if isPtr {
221
+ return nil // -> *time.Time(nil)
222
+ }
223
+ // non-pointer time.Time: represent NULL as zero time
224
+ return time.Time {}
225
+ }
226
+ default :
227
+ converted = value
228
+ }
229
+
230
+ case reflect .TypeOf (sql.NullTime {}):
231
+ if nullTime , ok := value .(sql.NullTime ); ok {
232
+ converted = nullTime
233
+ } else {
234
+ converted = sql.NullTime {}
235
+ }
236
+
207
237
case reflect .TypeOf (sql.NullInt64 {}):
208
238
if nullInt , ok := value .(sql.NullInt64 ); ok {
209
239
converted = nullInt
@@ -228,48 +258,24 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
228
258
} else {
229
259
converted = sql.NullBool {}
230
260
}
231
- case reflect .TypeOf (sql.NullTime {}):
232
- if nullTime , ok := value .(sql.NullTime ); ok {
233
- converted = nullTime
234
- } else {
235
- converted = sql.NullTime {}
236
- }
237
261
default :
238
- // Handle primitive types
262
+ // primitives and everything else
239
263
converted = convertPrimitiveType (value , targetType )
240
264
}
241
265
242
- // Handle pointer types
243
- if isPtr && converted != nil {
244
- if isZeroValueForPointer ( converted , targetType ) {
266
+ // Pointer targets: nil for "zero-ish", else allocate and set.
267
+ if isPtr {
268
+ if isZeroFor ( targetType , converted ) {
245
269
return nil
246
270
}
247
271
ptr := reflect .New (targetType )
248
272
ptr .Elem ().Set (reflect .ValueOf (converted ))
249
- converted = ptr .Interface ()
273
+ return ptr .Interface ()
250
274
}
251
275
252
276
return converted
253
277
}
254
278
255
- // Helper function to check if a value should be treated as nil for pointer fields
256
- func isZeroValueForPointer (value interface {}, targetType reflect.Type ) bool {
257
- v := reflect .ValueOf (value )
258
- if ! v .IsValid () || v .Kind () != targetType .Kind () {
259
- return false
260
- }
261
-
262
- switch targetType .Kind () {
263
- case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
264
- return v .Int () == 0
265
- case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 :
266
- return v .Uint () == 0
267
- case reflect .Float32 , reflect .Float64 :
268
- return v .Float () == 0.0
269
- }
270
- return false
271
- }
272
-
273
279
// Helper function to handle primitive type conversions
274
280
func convertPrimitiveType (value interface {}, targetType reflect.Type ) interface {} {
275
281
switch targetType .Kind () {
@@ -442,3 +448,28 @@ func isNullValue(value interface{}) bool {
442
448
return false
443
449
}
444
450
}
451
+
452
+ func isZeroFor (t reflect.Type , v interface {}) bool {
453
+ if v == nil {
454
+ return true
455
+ }
456
+ rv := reflect .ValueOf (v )
457
+ if ! rv .IsValid () {
458
+ return true
459
+ }
460
+ // exact type match?
461
+ if rv .Type () == t {
462
+ // special-case time.Time
463
+ if t == reflect .TypeOf (time.Time {}) {
464
+ return rv .Interface ().(time.Time ).IsZero ()
465
+ }
466
+ // generic zero check
467
+ z := reflect .Zero (t )
468
+ return reflect .DeepEqual (rv .Interface (), z .Interface ())
469
+ }
470
+ // If types differ (e.g., sql.NullTime), treat invalid as zero
471
+ if nt , ok := v .(sql.NullTime ); ok {
472
+ return ! nt .Valid
473
+ }
474
+ return false
475
+ }
0 commit comments