@@ -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
@@ -222,6 +231,10 @@ func convertValue(val interface{}) interface{} {
222231 rv = rv .Elem ()
223232 val = rv .Interface ()
224233 }
234+ isNil := false
235+ if rv .Kind () == reflect .Ptr && rv .IsNil () {
236+ isNil = true
237+ }
225238
226239 switch v := val .(type ) {
227240 case json.RawMessage :
@@ -238,7 +251,7 @@ func convertValue(val interface{}) interface{} {
238251 case * uuid.UUID , * datatypes.UUID :
239252 // Convert nil pointer to a UUID to empty string so that it is stored in the database as NULL
240253 // rather than "00000000-0000-0000-0000-000000000000"
241- if rv . IsNil () {
254+ if isNil {
242255 return ""
243256 }
244257 return val
@@ -249,15 +262,31 @@ func convertValue(val interface{}) interface{} {
249262 return 0
250263 }
251264 case string :
252- if len (v ) > math .MaxInt16 {
265+ // Store strings longer than 4000 characters as CLOB
266+ if len (v ) > 4000 {
253267 return godror.Lob {IsClob : true , Reader : strings .NewReader (v )}
254268 }
255269 return v
256270 case []byte :
257- if len (v ) > math .MaxInt16 {
271+ // Store byte slices longer than 4000 bytes as BLOB
272+ if len (v ) > 4000 {
258273 return godror.Lob {IsClob : false , Reader : bytes .NewReader (v )}
259274 }
260275 return v
276+ case driver.Valuer :
277+ // Unwrap driver.Valuer to its underlying type by recursing into
278+ // convertValue until we get a non-Valuer type
279+ if v == nil || isNil {
280+ return val
281+ }
282+ unwrappedValue , err := v .Value ()
283+ if err != nil {
284+ return val
285+ }
286+ return convertValue (unwrappedValue )
287+ case clause.Expr :
288+ // If we get a clause.Expr, convert it to nil; it should be handled elsewhere
289+ return nil
261290 default :
262291 return val
263292 }
@@ -285,6 +314,13 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
285314 targetType = field .FieldType .Elem ()
286315 }
287316
317+ // When PL/SQL LOBs are returned, skip conversion.
318+ // LOB addresses are freed by the driver after the query, so we cannot read their content
319+ // from the return value. If you need to read stored LOB content, do it in a separate query.
320+ if _ , ok := value .(godror.Lob ); ok {
321+ return nil
322+ }
323+
288324 switch targetType {
289325 case reflect .TypeOf (gorm.DeletedAt {}):
290326 if nullTime , ok := value .(sql.NullTime ); ok {
@@ -318,6 +354,16 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
318354 default :
319355 converted = value
320356 }
357+ case reflect .TypeOf (uuid.UUID {}), reflect .TypeOf (datatypes.UUID {}):
358+ uuidStr , ok := value .(string )
359+ if ! ok {
360+ return nil
361+ }
362+ parsed , err := uuid .Parse (uuidStr )
363+ if err != nil {
364+ return nil
365+ }
366+ converted = parsed
321367
322368 case reflect .TypeOf (time.Time {}):
323369 switch vv := value .(type ) {
@@ -388,6 +434,12 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
388434}
389435
390436func isJSONField (f * schema.Field ) bool {
437+ // Support detecting JSON fields through the struct's "type" tag.
438+ // Also support jsonb for compatibility with other databases.
439+ if f .DataType == "json" || f .DataType == "jsonb" {
440+ return true
441+ }
442+
391443 _rawMsgT := reflect .TypeOf (json.RawMessage {})
392444 _gormJSON := reflect .TypeOf (datatypes.JSON {})
393445 if f == nil {
0 commit comments