-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Assigning nil value to *uuid.UUID field in Updates #7099
base: master
Are you sure you want to change the base?
Conversation
This PR adds handling for the assignment of a nil value to a *uuid.UUID field (resolved as a reflect.Ptr to a reflect.Array), to ensure that the model object reflects the correct value after Updates() has completed. This PR also adds few supporting test cases for Updates() with a map and uuid.UUID column.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR.
Why is Array special, what about Slice? I think we shouldn't provide unique compatibility for external packages.
Actually the issue is whether we should update nil values to the original object when performing Update. If so, how should the Embedded object handle it?
Actually this PR still doesn't work in Embedded objects.
Hey @a631807682 thank you so much for your review. My responses to your questions are as below.
The
Yes, we should surely update nil values in the original object when performing Update, since original object needs to be consistent with the underlying database changes performed in the update operation. It's usually a best practice in ORMs to keep the model entity object (i.e. the original object) consistent with the underlying database, as developers may continue using the original object in code for further operations after the update, assuming that it's already in sync with the database.
This PR specifically fixes only the issue with |
For this issue, my first instinct is to use Customize Data Types: package datatypes
import (
"database/sql/driver"
"github.com/google/uuid"
"gorm.io/gorm/schema"
)
type UUID uuid.UUID
func (UUID) GormDataType() string {
return string(schema.String)
}
func (u *UUID) Scan(value interface{}) error {
var result uuid.UUID
if err := result.Scan(value); err != nil {
return err
}
*u = UUID(result)
return nil
}
func (u UUID) Value() (driver.Value, error) {
return uuid.UUID(u).Value()
}
func (u UUID) String() string {
return uuid.UUID(u).String()
} then use var uuidPtr *datatypes.UUID = nil
DB.Model(&p).Updates(map[string]interface{}{"unique_id": uuidPtr}).Error
// UPDATE "p" SET "unique_id"=NULL WHERE "id" = 1 |
Yes, we should try to support it, but I would like it to be in a more general way.
This test describes the problem with updating the original object in an embedded model, and the behavior that this PR breaks. The problem here is that to set the value of a field of an embedded object, you need to instantiate the object, even if it is a nil value. In my opinion, a more general solution may be to use Scanner and Valuer as @iTanken said (if feasible, it can be included in the https://github.com/go-gorm/datatypes project), or to check whether the embedded object of pointer type is zero value after setting all the fields of the object. gorm/tests/embedded_struct_test.go Line 101 in 4a50b36
type Author struct {
ID string
Name string
Email string
Age int
Content Content
ContentPtr *Content
Birthday time.Time
BirthdayPtr *time.Time
+ UUID *uuid.UUID
} |
369e5e5
to
4cd55f6
Compare
Hey @a631807682 @iTanken, thank you both for your comments. Yes I totally agree that we should keep it more general, so as per both your suggestions above, I've raised this PR on the @a631807682 I've also added handling for embedded structs and have included the test case that you provided above (refer here). That test case is passing now. Also have replaced Note: PR check diff --git a/tests/update_test.go b/tests/update_test.go
index a4f902b..84d41c9 100644
--- a/tests/update_test.go
+++ b/tests/update_test.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/google/uuid"
+ "gorm.io/datatypes"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/utils"
@@ -185,7 +186,7 @@ func TestUpdates(t *testing.T) {
user3.Age += 100
AssertObjEqual(t, user4, user3, "UpdatedAt", "Age")
- // Updates() with map and uuid.UUID - Case 1 - Update with UUID value
+ // Updates() with map and datatypes.UUID - Case 1 - Update with UUID value
uuidVal, uuidErr := uuid.NewUUID()
if uuidErr != nil {
t.Errorf("No error should occur while generating UUID, but got %v", uuidErr)
@@ -198,8 +199,8 @@ func TestUpdates(t *testing.T) {
// Expecting the model object (user4) to reflect the UUID value assignment.
AssertEqual(t, user4.UserUUID, uuidVal)
- // Updates() with map and uuid.UUID - Case 2 - Update with UUID nil pointer
- var nilUUIDPtr *uuid.UUID = nil
+ // Updates() with map and datatypes.UUID - Case 2 - Update with UUID nil pointer
+ var nilUUIDPtr *datatypes.UUID = nil
uuidErr = tx.Updates(map[string]interface{}{"user_uuid": nilUUIDPtr}).Error
if uuidErr != nil {
t.Errorf("No error should occur while updating with nil UUID pointer, but got %v", uuidErr)
@@ -207,12 +208,13 @@ func TestUpdates(t *testing.T) {
// Expecting the model object (user4) to reflect the UUID nil pointer assignment.
AssertEqual(t, user4.UserUUID, nilUUIDPtr)
- // Updates() with map and uuid.UUID - Case 3 - Update with a non-nil UUID pointer
+ // Updates() with map and datatypes.UUID - Case 3 - Update with a non-nil UUID pointer
uuidVal2, uuidErr := uuid.NewUUID()
if uuidErr != nil {
t.Errorf("No error should occur while generating UUID, but got %v", uuidErr)
}
- var nonNilUUIDPtr *uuid.UUID = &uuidVal2
+ castedUUIDVal2 := datatypes.UUID(uuidVal2)
+ var nonNilUUIDPtr *datatypes.UUID = &castedUUIDVal2
uuidErr = tx.Updates(map[string]interface{}{"user_uuid": nonNilUUIDPtr}).Error
if uuidErr != nil {
t.Errorf("No error should occur while updating with non-nil UUID pointer, but got %v", uuidErr)
diff --git a/utils/tests/models.go b/utils/tests/models.go
index 7690641..060467c 100644
--- a/utils/tests/models.go
+++ b/utils/tests/models.go
@@ -4,7 +4,7 @@ import (
"database/sql"
"time"
- "github.com/google/uuid"
+ "gorm.io/datatypes"
"gorm.io/gorm"
)
@@ -31,7 +31,7 @@ type User struct {
Languages []Language `gorm:"many2many:UserSpeak;"`
Friends []*User `gorm:"many2many:user_friends;"`
Active bool
- UserUUID *uuid.UUID
+ UserUUID *datatypes.UUID
}
type Account struct {
|
Fixes #7090.
What did this pull request do?
This PR adds handling for the assignment of a
nil
value to a*uuid.UUID
field (resolved as areflect.Ptr
to areflect.Array
), to ensure that the model object reflects the correct value after Updates() has completed.This PR also adds few supporting test cases for
Updates()
with a map anduuid.UUID
column.User Case Description
This fix is necessary to keep the gorm model object consistent with the database operation performed via
Updates()
. Which means that ifnil
value is set to a UUID column in the database, then the same should reflect in the model object as well.Other Notes
Please note that I had to inevitably add the
github.com/google/uuid
package to the maingo.mod
file here since theUserUUID
column in the testUser
model needed it as an import. TheUserUUID
field is being used in the new test cases that I've added in this PR.