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

Update operation sets boolean fields to NULL instead of false, violating non-null constraint #922

Open
vichu opened this issue Oct 20, 2023 · 1 comment

Comments

@vichu
Copy link

vichu commented Oct 20, 2023

Hello,

I'm currently using bun in a project and I've encountered an issue with boolean fields defaulting to NULL instead of false during updates.

Here's the bun model struct I'm using:

type Entity struct {
    ID            int               `bun:"id,autoincrement"`
    IsActive      bool              `bun:"is_active,notnull,default:true"` // corresponds to is_active BOOLEAN NOT NULL DEFAULT TRUE in the schema
}

And here's the function I'm using to update the Entity:

func (r *EntityRepository) UpdateEntity(ctx context.Context, id int, isActive bool) (*Entity, error) {
    // ...
    entity := &Entity{
        ID: id,
        IsActive: isActive,
    }
    _, err := r.DB.NewUpdate().Model(entity).Column("is_active").WherePK().Exec(ctx)
    // ...
}

In my test case, when I'm setting IsActive to false explicitly and try to update the record, I get an error: null value in column "is_active" violates not-null constraint error, which indicates that the is_active column is being set to NULL.

I've checked/debugged my code and can confirm that IsActive is being set to false and not NULL.

This issue seems to occur only during updates when the boolean value is explicitly set to false.

Thank you.

@vichu vichu changed the title Boolean fields defaulting to NULL instead of false during update Update operation sets boolean fields to NULL instead of false, violating non-null constraint Oct 20, 2023
@bevzzz
Copy link
Contributor

bevzzz commented Nov 28, 2023

tl;dr: Try using .Value() method to control which value gets appended to the query.
The following should work:

db.NewUpdate().
  Model(entity).
  Column("is_active").
  Value("is_active", "false"). // will substitute to is_active = false
  WherePK().
  Exec(ctx)

This is a workaround, because the behaviour you've described looks buggy. For more about that, read on.


This happens because any field that has a bun:"default:x" tag also gets a "nullzero" tag.:

bun/schema/table.go

Lines 378 to 381 in 8a43835

if s, ok := tag.Option("default"); ok {
field.SQLDefault = s
field.NullZero = true
}

Why? Because for INSERT queries bun needs to be able to correctly decide to append DEFAULT placeholder for a field that has a zero value:

bun/query_insert.go

Lines 335 to 338 in 8a43835

case (f.IsPtr && f.HasNilValue(strct)) || (f.NullZero && f.HasZeroValue(strct)):
if q.db.features.Has(feature.DefaultPlaceholder) {
b = append(b, "DEFAULT"...)
} else if f.SQLDefault != "" {

That's helpful, even though not all dialects support this feature (looking at you, SQLite).
But then, once we try to set a field that has a default value to false (zero boolean value) in UpdateQuery we get the bug you've described. It happens here:

bun/schema/field.go

Lines 97 to 99 in 8a43835

if (f.IsPtr && fv.IsNil()) || (f.NullZero && f.IsZero(fv)) {
return dialect.AppendNull(b)
}

I think adding a HasDefault bool field to schema.Field and using it checking for SQLDefault != "" in the InsertQuery instead would be a good long-term solution.

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

No branches or pull requests

2 participants