Guides

TableTheory v3.0.5 Create() migration

TableTheory v3.0.5 changed the ordinary Go Create() operation from a conditional guard used only for some models to an always-conditional strict create. Every ordinary Create() now sends attribute_not_exists for the partition key and returns ErrConditionFailed when an item with that key already exists. This behavior remains in place; it prevents a create call from silently replacing persisted state.

What changed and why

Before v3.0.5, an ordinary Create() could issue an unconditional DynamoDB PutItem and overwrite an existing item. Transactional creates already guarded the partition key, so the two create paths disagreed. The correctness repair in #523 and #524 made ordinary Create() match the transactional strict-create contract.

Who is affected

Only Go callers that used Create() as an overwrite or upsert are affected. Typical examples are repeated writes to a caller-chosen key, setters that replace an existing value, and functions named Update, Upsert, or Store whose implementation ends in Create().

Callers that mean “insert this item only if its partition key is unused” already have the intended behavior and require no change.

Migration

Change only the overwrite/upsert-flavored call sites to CreateOrUpdate():

// Strict create: keep this when an existing item must make the write fail.
err := db.Model(&record).Create()

// Intentional overwrite/upsert: use this when the same key may already exist.
err := db.Model(&record).CreateOrUpdate()

Do not broadly replace every Create() call. Keeping strict creates strict preserves duplicate-write detection and the silent-overwrite fix from #523.

Find affected call sites

  1. Search the Go consumer for .Create() calls, for example with rg '\.Create\('.
  2. Classify each call as strict create or intentional overwrite/upsert based on the access pattern, not the method name alone.
  3. Pay particular attention to repeated writes with stable caller-chosen keys and to Set, Update, Upsert, or Store paths.
  4. Search error handling for condition-check failures that are logged as warnings or discarded. A swallowed error can make the operation appear successful while the stored item stops changing.

At runtime, an affected write returns a condition-check failure. TableTheory wraps the error so callers can test it with errors.Is(err, theorydbErrors.ErrConditionFailed).

Test the distinction

The simple doubles in pkg/mocks replay configured calls and returns; they do not build or evaluate DynamoDB condition expressions. A mock-based unit test therefore cannot naturally surface the v3.0.5 duplicate-create failure. Use the state-backed pkg/testing/fakedb fake for condition-sensitive tests: it stores items and evaluates the supported condition expressions, including the Create() partition-key not-exists guard. DynamoDB Local remains the integration-test boundary for behavior outside the fake’s scenario-validated scope.

See the Testing Guide for the test-double selection rule and setup example. Release-note visibility for this migration is tracked in #552.