@@ -41,9 +41,8 @@ package oracle
4141import (
4242 "bytes"
4343 "database/sql"
44+ "database/sql/driver"
4445 "encoding/json"
45- "fmt"
46- "math"
4746 "reflect"
4847 "strings"
4948 "time"
@@ -52,50 +51,50 @@ import (
5251 "github.com/google/uuid"
5352 "gorm.io/datatypes"
5453 "gorm.io/gorm"
54+ "gorm.io/gorm/clause"
5555 "gorm.io/gorm/schema"
5656)
5757
5858// Extra data types for the data type that are not declared in the
5959// default DataType list
6060const (
61- JSON schema.DataType = "json"
6261 Timestamp schema.DataType = "timestamp"
6362 TimestampWithTimeZone schema.DataType = "timestamp with time zone"
6463)
6564
6665// Helper function to get Oracle array type for a field
67- func getOracleArrayType (field * schema.Field , values []any ) string {
68- switch field .DataType {
69- case schema .Bool :
70- return "TABLE OF NUMBER(1)"
71- case schema .Int , schema .Uint :
72- return "TABLE OF NUMBER"
73- case schema .Float :
74- return "TABLE OF NUMBER"
75- case JSON :
76- // PL/SQL does not yet allow declaring collections of JSON (TABLE OF JSON) directly.
77- // Workaround for JSON type
78- fallthrough
79- case schema .String :
80- if field .Size > 0 && field .Size <= 4000 {
81- return fmt .Sprintf ("TABLE OF VARCHAR2(%d)" , field .Size )
82- } else {
83- for _ , value := range values {
84- if strValue , ok := value .(string ); ok {
85- if len (strValue ) > 4000 {
86- return "TABLE OF CLOB"
87- }
88- }
66+ func getOracleArrayType (values []any ) string {
67+ arrayType := "TABLE OF VARCHAR2(4000)"
68+ for _ , val := range values {
69+ if val == nil {
70+ continue
71+ }
72+ switch v := val .(type ) {
73+ case bool :
74+ arrayType = "TABLE OF NUMBER(1)"
75+ case int , int8 , int16 , int32 , int64 , uint , uint8 , uint16 , uint32 , uint64 , float32 , float64 :
76+ arrayType = "TABLE OF NUMBER"
77+ case time.Time :
78+ arrayType = "TABLE OF TIMESTAMP WITH TIME ZONE"
79+ case godror.Lob :
80+ if v .IsClob {
81+ return "TABLE OF CLOB"
82+ } else {
83+ return "TABLE OF BLOB"
84+ }
85+ case []byte :
86+ // Store byte slices longer than 4000 bytes as BLOB
87+ if len (v ) > 4000 {
88+ return "TABLE OF BLOB"
89+ }
90+ case string :
91+ // Store strings longer than 4000 characters as CLOB
92+ if len (v ) > 4000 {
93+ return "TABLE OF CLOB"
8994 }
9095 }
91- return "TABLE OF VARCHAR2(4000)"
92- case schema .Time :
93- return "TABLE OF TIMESTAMP WITH TIME ZONE"
94- case schema .Bytes :
95- return "TABLE OF BLOB"
96- default :
97- return "TABLE OF " + strings .ToUpper (string (field .DataType ))
9896 }
97+ return arrayType
9998}
10099
101100// Helper function to get all column names for a table
@@ -131,6 +130,12 @@ func createTypedDestination(f *schema.Field) interface{} {
131130 return new (string )
132131 }
133132
133+ // To differentiate between bool fields stored as NUMBER(1) and bool fields stored as actual BOOLEAN type,
134+ // check the struct's "type" tag.
135+ if f .DataType == "boolean" {
136+ return new (bool )
137+ }
138+
134139 // If the field has a serializer, the field type may not be directly related to the column type in the database.
135140 // In this case, determine the destination type using the field's data type, which is the column type in the
136141 // database.
@@ -204,13 +209,17 @@ func createTypedDestination(f *schema.Field) interface{} {
204209
205210 case reflect .Float32 , reflect .Float64 :
206211 return new (float64 )
212+
213+ case reflect .Slice :
214+ if ft .Elem ().Kind () == reflect .Uint8 { // []byte
215+ return new ([]byte )
216+ }
207217 }
208218
209219 // Fallback
210220 return new (string )
211221}
212222
213- // Convert values for Oracle-specific types
214223func convertValue (val interface {}) interface {} {
215224 if val == nil {
216225 return nil
@@ -226,6 +235,10 @@ func convertValue(val interface{}) interface{} {
226235 rv = rv .Elem ()
227236 val = rv .Interface ()
228237 }
238+ isNil := false
239+ if rv .Kind () == reflect .Ptr && rv .IsNil () {
240+ isNil = true
241+ }
229242
230243 switch v := val .(type ) {
231244 case json.RawMessage :
@@ -242,7 +255,7 @@ func convertValue(val interface{}) interface{} {
242255 case * uuid.UUID , * datatypes.UUID :
243256 // Convert nil pointer to a UUID to empty string so that it is stored in the database as NULL
244257 // rather than "00000000-0000-0000-0000-000000000000"
245- if rv . IsNil () {
258+ if isNil {
246259 return ""
247260 }
248261 return val
@@ -253,15 +266,31 @@ func convertValue(val interface{}) interface{} {
253266 return 0
254267 }
255268 case string :
256- if len (v ) > math .MaxInt16 {
269+ // Store strings longer than 4000 characters as CLOB
270+ if len (v ) > 4000 {
257271 return godror.Lob {IsClob : true , Reader : strings .NewReader (v )}
258272 }
259273 return v
260274 case []byte :
261- if len (v ) > math .MaxInt16 {
275+ // Store byte slices longer than 4000 bytes as BLOB
276+ if len (v ) > 4000 {
262277 return godror.Lob {IsClob : false , Reader : bytes .NewReader (v )}
263278 }
264279 return v
280+ case driver.Valuer :
281+ // Unwrap driver.Valuer to its underlying type by recursing into
282+ // convertValue until we get a non-Valuer type
283+ if v == nil || isNil {
284+ return val
285+ }
286+ unwrappedValue , err := v .Value ()
287+ if err != nil {
288+ return val
289+ }
290+ return convertValue (unwrappedValue )
291+ case clause.Expr :
292+ // If we get a clause.Expr, convert it to nil; it should be handled elsewhere
293+ return nil
265294 default :
266295 return val
267296 }
@@ -289,6 +318,13 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
289318 targetType = field .FieldType .Elem ()
290319 }
291320
321+ // When PL/SQL LOBs are returned, skip conversion.
322+ // LOB addresses are freed by the driver after the query, so we cannot read their content
323+ // from the return value. If you need to read stored LOB content, do it in a separate query.
324+ if _ , ok := value .(godror.Lob ); ok {
325+ return nil
326+ }
327+
292328 switch targetType {
293329 case reflect .TypeOf (gorm.DeletedAt {}):
294330 if nullTime , ok := value .(sql.NullTime ); ok {
@@ -322,6 +358,16 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
322358 default :
323359 converted = value
324360 }
361+ case reflect .TypeOf (uuid.UUID {}), reflect .TypeOf (datatypes.UUID {}):
362+ uuidStr , ok := value .(string )
363+ if ! ok {
364+ return nil
365+ }
366+ parsed , err := uuid .Parse (uuidStr )
367+ if err != nil {
368+ return nil
369+ }
370+ converted = parsed
325371
326372 case reflect .TypeOf (time.Time {}):
327373 switch vv := value .(type ) {
@@ -392,6 +438,12 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
392438}
393439
394440func isJSONField (f * schema.Field ) bool {
441+ // Support detecting JSON fields through the struct's "type" tag.
442+ // Also support jsonb for compatibility with other databases.
443+ if f .DataType == "json" || f .DataType == "jsonb" {
444+ return true
445+ }
446+
395447 _rawMsgT := reflect .TypeOf (json.RawMessage {})
396448 _gormJSON := reflect .TypeOf (datatypes.JSON {})
397449 if f == nil {
0 commit comments