Skip to content

Update / UpdateWithCtx $set: model skips bson omitempty zero-values, leaving stale data in MongoDB #99

Description

@AmanEazydiner

Describe the bug

Collection.Update / UpdateWithCtx persists changes with:

UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$set": model})

When the model is marshaled into $set, fields tagged with bson:",omitempty" that hold Go zero values (false, "", 0, nil slices/maps) are omitted from the update document.

Because those keys are missing from $set, MongoDB does not change the existing values. The in-memory model (and any API response built from it) can show the new zero values, while the database still has the previous non-zero values. Callers often see HTTP 200 / success even though persistence failed for those fields.

This is especially painful for:

  • bool flags cleared to false (e.g. is_primary)
  • string fields cleared to "" (e.g. optional fallback name)

To Reproduce

  1. Define a model with clearable fields using omitempty:
type Template struct {
	mgm.DefaultModel `bson:",inline"`

	IsPrimary         bool   `bson:"is_primary,omitempty"`
	SecondaryFallback string `bson:"secondary_fallback,omitempty"`
	Name              string `bson:"name"`
}
  1. Insert a document with non-zero values:
t := &Template{Name: "welcome", IsPrimary: true, SecondaryFallback: "sms_fallback"}
_ = mgm.Coll(t).CreateWithCtx(ctx, t)
  1. Load it, clear the fields, and update via mgm:
_ = mgm.Coll(t).FirstWithCtx(ctx, bson.M{"_id": t.ID}, t)
t.IsPrimary = false
t.SecondaryFallback = ""
_ = mgm.Coll(t).UpdateWithCtx(ctx, t) // returns nil
  1. Reload from MongoDB:
reloaded := &Template{}
_ = mgm.Coll(reloaded).FindByIDWithCtx(ctx, t.ID, reloaded)
fmt.Println(reloaded.IsPrimary, reloaded.SecondaryFallback)
// still true, "sms_fallback" — not false, ""

Expected behavior

After a successful UpdateWithCtx:

  • is_primary should be false in MongoDB
  • secondary_fallback should be "" (or explicitly unset, if that is the intended clear semantics)

Zero values that the application set on the model should be persisted, or the library should document clearly that $set: model + omitempty cannot clear fields and recommend ReplaceOne / explicit $set / pointer fields.

Environment (please complete the following information):

  • OS: Linux (also reproducible on macOS/Windows)
  • Go: 1.22+
  • mgm: github.com/kamva/mgm/v3 (e.g. v3.5.0)
  • mongo-driver: compatible with mgm v3
  • MongoDB: 6.x / 7.x

Additional context

Relevant implementation (mgm v3):

res, err := c.UpdateOne(ctx, bson.M{field.ID: model.GetID()}, bson.M{"$set": model}, opts...)

Possible directions (for maintainers / discussion):

  1. Document the pitfall — warn that Update cannot clear omitempty zero-values; suggest removing omitempty on clearable fields, using pointers (*bool, *string), or ReplaceOne.
  2. Use ReplaceOne for Update/UpdateWithCtx — treat the Go model as the full document source of truth (breaking change for apps that rely on leaving unknown Mongo-only fields untouched).
  3. Helper / option — e.g. UpdateWithReplace or UpdateOptions{Replace: true} so callers can opt into full-document replace while keeping hook behavior (Updating / Saving / updated_at).

We hit this in production-style admin CRUD: the API returned the cleared values from the in-memory model after UpdateWithCtx, but MongoDB retained the previous values until we removed omitempty on those fields.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions