Skip to content
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: on confilct with default value #6434

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

black-06
Copy link
Contributor

@black-06 black-06 commented Jun 28, 2023

  • Do only one thing
  • Non breaking API changes
  • Tested

What did this pull request do?

close #6418 ,
and it has a little relationship with #6129

User Case Description

@black-06 black-06 requested a review from a631807682 June 28, 2023 05:35
@jinzhu
Copy link
Member

jinzhu commented Jul 24, 2023

Hi @black-06

Which might cause a column's value overwritten by an empty value, here is an example:

type User struct {
   Name string
  UUID   string `gorm:"default:gen_random_uuid()"`
}

db.Save(&[]User{{Name: "name"}}

@black-06
Copy link
Contributor Author

black-06 commented Jul 25, 2023

Hi @black-06

Which might cause a column's value overwritten by an empty value, here is an example:

type User struct {
   Name string
  UUID   string `gorm:"default:gen_random_uuid()"`
}

db.Save(&[]User{{Name: "name"}}

OK, I will fix it

@black-06
Copy link
Contributor Author

black-06 commented Jul 25, 2023

Hi @black-06

Which might cause a column's value overwritten by an empty value, here is an example:

type User struct {
   Name string
  UUID   string `gorm:"default:gen_random_uuid()"`
}

db.Save(&[]User{{Name: "name"}}

Uh, the same case at playground failed, see go-gorm/playground#629.
This bug may not be caused by this mr.
If they are not related, I will fix it in the new mr

@black-06
Copy link
Contributor Author

Hi @black-06

Which might cause a column's value overwritten by an empty value, here is an example:

type User struct {
   Name string
  UUID   string `gorm:"default:gen_random_uuid()"`
}

db.Save(&[]User{{Name: "name"}}

Sorry, I may not understand what you mean.
I wrote a test and it seems to work fine.

func TestSaveWithDefaultValue(t *testing.T) {
	if DB.Dialector.Name() != "postgres" {
		t.Skip()
	}

	type WithDefaultValue struct {
		Name string `gorm:"primaryKey"`
		UUID string `gorm:"default:gen_random_uuid()"`
	}

	err := DB.Migrator().DropTable(&WithDefaultValue{})
	AssertEqual(t, err, nil)
	err = DB.AutoMigrate(&WithDefaultValue{})
	AssertEqual(t, err, nil)

	// INSERT INTO "with_default_values" ("name") VALUES ('jinzhu') ON CONFLICT ("name") DO NOTHING RETURNING "uuid"
	err = DB.Debug().Save(&[]WithDefaultValue{{Name: "jinzhu"}}).Error
	AssertEqual(t, err, nil)

	err = DB.Debug().Save(&[]WithDefaultValue{{Name: "jinzhu"}}).Error
	AssertEqual(t, err, nil)
}

@a631807682 Would you mind helping me? 😄

@a631807682
Copy link
Member

a631807682 commented Jul 27, 2023

Hi @black-06
Which might cause a column's value overwritten by an empty value, here is an example:

type User struct {
   Name string
  UUID   string `gorm:"default:gen_random_uuid()"`
}

db.Save(&[]User{{Name: "name"}}

Sorry, I may not understand what you mean. I wrote a test and it seems to work fine.

func TestSaveWithDefaultValue(t *testing.T) {
	if DB.Dialector.Name() != "postgres" {
		t.Skip()
	}

	type WithDefaultValue struct {
		Name string `gorm:"primaryKey"`
		UUID string `gorm:"default:gen_random_uuid()"`
	}

	err := DB.Migrator().DropTable(&WithDefaultValue{})
	AssertEqual(t, err, nil)
	err = DB.AutoMigrate(&WithDefaultValue{})
	AssertEqual(t, err, nil)

	// INSERT INTO "with_default_values" ("name") VALUES ('jinzhu') ON CONFLICT ("name") DO NOTHING RETURNING "uuid"
	err = DB.Debug().Save(&[]WithDefaultValue{{Name: "jinzhu"}}).Error
	AssertEqual(t, err, nil)

	err = DB.Debug().Save(&[]WithDefaultValue{{Name: "jinzhu"}}).Error
	AssertEqual(t, err, nil)
}

@a631807682 Would you mind helping me? 😄

I'll check it tomorrow

Copy link
Member

@a631807682 a631807682 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

values.Columns is already filtered data, it must not contain default value, or contain default value and set a value for it at the same time.

The problem here is that if the default value is a database built-in function ( just like gen_random_uuid() or now(), we don't parse DefaultValueInterface), we don't override it. But for custom types, we do not provide a way to override DefaultValueInterface, so it is always excluded.

Here is a smaller example

	type WithDefaultValue struct {
		Name    string            `gorm:"primaryKey"`
		Content datatypes.JSONMap `gorm:"default:'{}'"`
	}

	err := DB.Migrator().DropTable(&WithDefaultValue{})
	AssertEqual(t, err, nil)
	err = DB.AutoMigrate(&WithDefaultValue{})
	AssertEqual(t, err, nil)

	dfv := WithDefaultValue{Name: "jinzhu"}
	err = DB.Save(&dfv).Error
	AssertEqual(t, err, nil)

	dfv.Content["name"] = "jinzhu"
	err = DB.Clauses(clause.OnConflict{UpdateAll: true}).Create(&dfv).Error
	AssertEqual(t, err, nil)

	var result WithDefaultValue
	err = DB.First(&result).Error
	AssertEqual(t, err, nil)
	AssertEqual(t, result.Content["name"], "jinzhu")

@black-06
Copy link
Contributor Author

values.Columns is already filtered data, it must not contain default value, or contain default value and set a value for it at the same time.

The problem here is that if the default value is a database built-in function ( just like gen_random_uuid() or now(), we don't parse DefaultValueInterface), we don't override it. But for custom types, we do not provide a way to override DefaultValueInterface, so it is always excluded.

Here is a smaller example

	type WithDefaultValue struct {
		Name    string            `gorm:"primaryKey"`
		Content datatypes.JSONMap `gorm:"default:'{}'"`
	}

	err := DB.Migrator().DropTable(&WithDefaultValue{})
	AssertEqual(t, err, nil)
	err = DB.AutoMigrate(&WithDefaultValue{})
	AssertEqual(t, err, nil)

	dfv := WithDefaultValue{Name: "jinzhu"}
	err = DB.Save(&dfv).Error
	AssertEqual(t, err, nil)

	dfv.Content["name"] = "jinzhu"
	err = DB.Clauses(clause.OnConflict{UpdateAll: true}).Create(&dfv).Error
	AssertEqual(t, err, nil)

	var result WithDefaultValue
	err = DB.First(&result).Error
	AssertEqual(t, err, nil)
	AssertEqual(t, result.Content["name"], "jinzhu")

I'm still thinking about your comment, but your test didn't fail...

name content
jinzhu {"name": "jinzhu"}

@a631807682
Copy link
Member

values.Columns is already filtered data, it must not contain default value, or contain default value and set a value for it at the same time.
The problem here is that if the default value is a database built-in function ( just like gen_random_uuid() or now(), we don't parse DefaultValueInterface), we don't override it. But for custom types, we do not provide a way to override DefaultValueInterface, so it is always excluded.
Here is a smaller example

	type WithDefaultValue struct {
		Name    string            `gorm:"primaryKey"`
		Content datatypes.JSONMap `gorm:"default:'{}'"`
	}

	err := DB.Migrator().DropTable(&WithDefaultValue{})
	AssertEqual(t, err, nil)
	err = DB.AutoMigrate(&WithDefaultValue{})
	AssertEqual(t, err, nil)

	dfv := WithDefaultValue{Name: "jinzhu"}
	err = DB.Save(&dfv).Error
	AssertEqual(t, err, nil)

	dfv.Content["name"] = "jinzhu"
	err = DB.Clauses(clause.OnConflict{UpdateAll: true}).Create(&dfv).Error
	AssertEqual(t, err, nil)

	var result WithDefaultValue
	err = DB.First(&result).Error
	AssertEqual(t, err, nil)
	AssertEqual(t, result.Content["name"], "jinzhu")

I'm still thinking about your comment, but your test didn't fail...

name content
jinzhu {"name": "jinzhu"}

Sorry, this test is to illustrate the problem with custom types before.
You can use default:gen_random_uuid(), you will find that it keeps updating the UUID.

@black-06
Copy link
Contributor Author

You can use default:gen_random_uuid(), you will find that it keeps updating the UUID.

It didn't... Still this test, It was saved twice, but the uuid did not change.

func TestSaveWithDefaultValue(t *testing.T) {
	if DB.Dialector.Name() != "postgres" {
		t.Skip()
	}

	type WithDefaultValue struct {
		Name string `gorm:"primaryKey"`
		UUID string `gorm:"default:gen_random_uuid()"`
	}

	err := DB.Migrator().DropTable(&WithDefaultValue{})
	AssertEqual(t, err, nil)
	err = DB.AutoMigrate(&WithDefaultValue{})
	AssertEqual(t, err, nil)

	// INSERT INTO "with_default_values" ("name") VALUES ('jinzhu') ON CONFLICT ("name") DO NOTHING RETURNING "uuid"
	err = DB.Debug().Save(&[]WithDefaultValue{{Name: "jinzhu"}}).Error
	AssertEqual(t, err, nil)

	err = DB.Debug().Save(&[]WithDefaultValue{{Name: "jinzhu"}}).Error
	AssertEqual(t, err, nil)
}

@a631807682
Copy link
Member

As #6434 (review) says, if the value is not set, it will not be overridden, whereas if the value is set it should be.

Hi @black-06

Which might cause a column's value overwritten by an empty value, here is an example:

type User struct {
   Name string
  UUID   string `gorm:"default:gen_random_uuid()"`
}

db.Save(&[]User{{Name: "name"}}

Does not rewrite to empty value, I tried other cases, and there is no difference from the current behavior (including the default value for built-in functions, such as gen_random_uuid() or now(), which usually do not need to set the value, but set the value will be overwritten, but this can only depend on how the user uses it).

cc @jinzhu

# Conflicts:
#	tests/create_test.go
Copy link
Contributor

@daheige daheige left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Jsonb field of association isn't properly saved if it has default specified
5 participants