Skip to content

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!

@jinzhu jinzhu requested a review from Copilot October 30, 2025 12:06
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes an issue where fields with JSON default values were not being properly updated during upsert operations with OnConflict{UpdateAll: true}. The fix simplifies the logic in the OnConflict handler by removing an overly restrictive condition that was incorrectly filtering out columns that should be updated.

Key changes:

  • Simplified the OnConflict UpdateAll logic to include all non-primary-key columns in updates
  • Removed unnecessary condition checking for default values that was causing JSON fields with defaults to be skipped
  • Added test coverage for JSON default fields in both create and association scenarios

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
callbacks/create.go Simplified OnConflict UpdateAll logic by removing the complex HasDefaultValue check and removed the now-unused strings import
tests/create_test.go Fixed typo "OnConfilct" → "OnConflict" and added new test for OnConflict with JSON default values
tests/associations_test.go Added test for FullSaveAssociations with JSON default values to verify the fix works with associations

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


value.Dep.Params["foo"] = "new bar"
if err := DB.Session(&gorm.Session{FullSaveAssociations: true}).Save(&value).Error; err != nil {
t.Errorf("failed to svae value, got err: %v", err)
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'svae' to 'save'.

Suggested change
t.Errorf("failed to svae value, got err: %v", err)
t.Errorf("failed to save value, got err: %v", err)
Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

6 participants