Skip to content

Commit fe954eb

Browse files
Merge pull request #69 from oracle-samples/joboon-InsertNilUUIDAsNull
Add support for nil pointer uuid
2 parents 46a5293 + 0d8aee6 commit fe954eb

File tree

4 files changed

+84
-44
lines changed

4 files changed

+84
-44
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.24.4
44

55
require (
66
github.com/godror/godror v0.49.3
7+
github.com/google/uuid v1.6.0
78
gorm.io/datatypes v1.2.6
89
gorm.io/gorm v1.31.0
910
)
@@ -14,7 +15,6 @@ require (
1415
github.com/go-logfmt/logfmt v0.6.0 // indirect
1516
github.com/go-sql-driver/mysql v1.8.1 // indirect
1617
github.com/godror/knownpb v0.3.0 // indirect
17-
github.com/google/uuid v1.6.0 // indirect
1818
github.com/jinzhu/inflection v1.0.0 // indirect
1919
github.com/jinzhu/now v1.1.5 // indirect
2020
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect

oracle/common.go

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646
"strings"
4747
"time"
4848

49+
"github.com/google/uuid"
4950
"gorm.io/datatypes"
5051
"gorm.io/gorm"
5152
"gorm.io/gorm/schema"
@@ -165,10 +166,10 @@ func convertValue(val interface{}) interface{} {
165166
}
166167

167168
// Dereference pointers
168-
v := reflect.ValueOf(val)
169-
for v.Kind() == reflect.Ptr && !v.IsNil() {
170-
v = v.Elem()
171-
val = v.Interface()
169+
rv := reflect.ValueOf(val)
170+
for rv.Kind() == reflect.Ptr && !rv.IsNil() {
171+
rv = rv.Elem()
172+
val = rv.Interface()
172173
}
173174

174175
switch v := val.(type) {
@@ -183,6 +184,13 @@ func convertValue(val interface{}) interface{} {
183184
}
184185
b := []byte(*v)
185186
return b
187+
case *uuid.UUID, *datatypes.UUID:
188+
// Convert nil pointer to a UUID to empty string so that it is stored in the database as NULL
189+
// rather than "00000000-0000-0000-0000-000000000000"
190+
if rv.IsNil() {
191+
return ""
192+
}
193+
return val
186194
case bool:
187195
if v {
188196
return 1
@@ -203,30 +211,13 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
203211
}
204212

205213
targetType := field.FieldType
206-
isPtr := targetType.Kind() == reflect.Ptr
214+
var converted any
215+
216+
// dereference the field if it's a pointer
217+
isPtr := field.FieldType.Kind() == reflect.Ptr
207218
if isPtr {
208-
targetType = targetType.Elem()
209-
}
210-
if field.FieldType == reflect.TypeOf(json.RawMessage{}) {
211-
switch v := value.(type) {
212-
case []byte:
213-
return json.RawMessage(v) // from BLOB
214-
case *[]byte:
215-
if v == nil {
216-
return json.RawMessage(nil)
217-
}
218-
return json.RawMessage(*v)
219-
}
219+
targetType = field.FieldType.Elem()
220220
}
221-
if isJSONField(field) {
222-
switch v := value.(type) {
223-
case string:
224-
return datatypes.JSON([]byte(v))
225-
case []byte:
226-
return datatypes.JSON(v)
227-
}
228-
}
229-
var converted interface{}
230221

231222
switch targetType {
232223
case reflect.TypeOf(gorm.DeletedAt{}):
@@ -235,6 +226,33 @@ func convertFromOracleToField(value interface{}, field *schema.Field) interface{
235226
} else {
236227
converted = gorm.DeletedAt{}
237228
}
229+
230+
case reflect.TypeOf(json.RawMessage{}):
231+
if field.FieldType == reflect.TypeOf(json.RawMessage{}) {
232+
switch vv := value.(type) {
233+
case []byte:
234+
converted = json.RawMessage(vv) // from BLOB
235+
case *[]byte:
236+
if vv == nil {
237+
converted = json.RawMessage(nil)
238+
}
239+
converted = json.RawMessage(*vv)
240+
case string:
241+
return datatypes.JSON([]byte(vv))
242+
default:
243+
converted = value
244+
}
245+
}
246+
case reflect.TypeOf(datatypes.JSON{}):
247+
switch vv := value.(type) {
248+
case string:
249+
converted = datatypes.JSON([]byte(vv))
250+
case []byte:
251+
converted = datatypes.JSON(vv)
252+
default:
253+
converted = value
254+
}
255+
238256
case reflect.TypeOf(time.Time{}):
239257
switch vv := value.(type) {
240258
case time.Time:
@@ -309,7 +327,12 @@ func isJSONField(f *schema.Field) bool {
309327
if f == nil {
310328
return false
311329
}
330+
312331
ft := f.FieldType
332+
if ft.Kind() == reflect.Ptr {
333+
ft = ft.Elem()
334+
}
335+
313336
return ft == _rawMsgT || ft == _gormJSON
314337
}
315338

oracle/query.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@
3939
package oracle
4040

4141
import (
42-
"gorm.io/gorm"
4342
"regexp"
4443
"strings"
44+
45+
"gorm.io/gorm"
4546
)
4647

4748
// Identifies the table name alias provided as
@@ -63,5 +64,4 @@ func BeforeQuery(db *gorm.DB) {
6364
}
6465
}
6566
}
66-
return
6767
}

tests/json_bulk_test.go

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ import (
5050

5151
func TestBasicCRUD_JSONText(t *testing.T) {
5252
type JsonRecord struct {
53-
ID uint `gorm:"primaryKey;autoIncrement;column:record_id"`
54-
Name string `gorm:"column:name"`
55-
Properties datatypes.JSON `gorm:"column:properties"`
53+
ID uint `gorm:"primaryKey;autoIncrement;column:record_id"`
54+
Name string `gorm:"column:name"`
55+
Properties datatypes.JSON `gorm:"column:properties"`
56+
PropertiesPtr *datatypes.JSON `gorm:"column:propertiesPtr"`
5657
}
5758

5859
DB.Migrator().DropTable(&JsonRecord{})
@@ -61,9 +62,11 @@ func TestBasicCRUD_JSONText(t *testing.T) {
6162
}
6263

6364
// INSERT
65+
json := datatypes.JSON([]byte(`{"env":"prod","owner":"team-x"}`))
6466
rec := JsonRecord{
65-
Name: "json-text",
66-
Properties: datatypes.JSON([]byte(`{"env":"prod","owner":"team-x"}`)),
67+
Name: "json-text",
68+
Properties: json,
69+
PropertiesPtr: &json,
6770
}
6871
if err := DB.Create(&rec).Error; err != nil {
6972
t.Fatalf("create failed: %v", err)
@@ -73,20 +76,23 @@ func TestBasicCRUD_JSONText(t *testing.T) {
7376
}
7477

7578
// UPDATE (with RETURNING)
79+
updateJson := datatypes.JSON([]byte(`{"env":"staging","owner":"team-y","flag":true}`))
7680
var ret JsonRecord
7781
if err := DB.
7882
Clauses(clause.Returning{
7983
Columns: []clause.Column{
8084
{Name: "record_id"},
8185
{Name: "name"},
8286
{Name: "properties"},
87+
{Name: "propertiesPtr"},
8388
},
8489
}).
8590
Model(&ret).
8691
Where("\"record_id\" = ?", rec.ID).
8792
Updates(map[string]any{
88-
"name": "json-text-upd",
89-
"properties": datatypes.JSON([]byte(`{"env":"staging","owner":"team-y","flag":true}`)),
93+
"name": "json-text-upd",
94+
"properties": updateJson,
95+
"propertiesPtr": &updateJson,
9096
}).Error; err != nil {
9197
t.Fatalf("update returning failed: %v", err)
9298
}
@@ -103,6 +109,7 @@ func TestBasicCRUD_JSONText(t *testing.T) {
103109
{Name: "record_id"},
104110
{Name: "name"},
105111
{Name: "properties"},
112+
{Name: "propertiesPtr"},
106113
},
107114
}).
108115
Delete(&deleted).Error; err != nil {
@@ -122,20 +129,23 @@ func TestBasicCRUD_JSONText(t *testing.T) {
122129

123130
func TestBasicCRUD_RawMessage(t *testing.T) {
124131
type RawRecord struct {
125-
ID uint `gorm:"primaryKey;autoIncrement;column:record_id"`
126-
Name string `gorm:"column:name"`
127-
Properties json.RawMessage `gorm:"column:properties"`
132+
ID uint `gorm:"primaryKey;autoIncrement;column:record_id"`
133+
Name string `gorm:"column:name"`
134+
Properties json.RawMessage `gorm:"column:properties"`
135+
PropertiesPtr *json.RawMessage `gorm:"column:propertiesPtr"`
128136
}
129137

130138
DB.Migrator().DropTable(&RawRecord{})
131139
if err := DB.AutoMigrate(&RawRecord{}); err != nil {
132140
t.Fatalf("migrate failed: %v", err)
133141
}
134142

143+
rawMsg := json.RawMessage(`{"a":1,"b":"x"}`)
135144
// INSERT
136145
rec := RawRecord{
137-
Name: "raw-json",
138-
Properties: json.RawMessage(`{"a":1,"b":"x"}`),
146+
Name: "raw-json",
147+
Properties: rawMsg,
148+
PropertiesPtr: &rawMsg,
139149
}
140150
if err := DB.Create(&rec).Error; err != nil {
141151
t.Fatalf("create failed: %v", err)
@@ -145,24 +155,30 @@ func TestBasicCRUD_RawMessage(t *testing.T) {
145155
}
146156

147157
// UPDATE (with RETURNING)
158+
upatedRawMsg := json.RawMessage(`{"a":2,"c":true}`)
148159
var ret RawRecord
149160
if err := DB.
150161
Clauses(clause.Returning{
151162
Columns: []clause.Column{
152163
{Name: "record_id"},
153164
{Name: "name"},
154165
{Name: "properties"},
166+
{Name: "propertiesPtr"},
155167
},
156168
}).
157169
Model(&ret).
158170
Where("\"record_id\" = ?", rec.ID).
159171
Updates(map[string]any{
160-
"name": "raw-json-upd",
161-
"properties": json.RawMessage(`{"a":2,"c":true}`),
172+
"name": "raw-json-upd",
173+
"properties": upatedRawMsg,
174+
"propertiesPtr": &upatedRawMsg,
162175
}).Error; err != nil {
163176
t.Fatalf("update returning failed: %v", err)
164177
}
165-
if ret.ID != rec.ID || ret.Name != "raw-json-upd" || len(ret.Properties) == 0 {
178+
if ret.ID != rec.ID ||
179+
ret.Name != "raw-json-upd" ||
180+
len(ret.Properties) == 0 ||
181+
ret.PropertiesPtr == nil || (ret.PropertiesPtr != nil && len(*ret.PropertiesPtr) == 0) {
166182
t.Fatalf("unexpected returning row: %#v", ret)
167183
}
168184

@@ -175,6 +191,7 @@ func TestBasicCRUD_RawMessage(t *testing.T) {
175191
{Name: "record_id"},
176192
{Name: "name"},
177193
{Name: "properties"},
194+
{Name: "propertiesPtr"},
178195
},
179196
}).
180197
Delete(&deleted).Error; err != nil {

0 commit comments

Comments
 (0)