feat: dependency-aware write sets for mixed-type entity graphs#270
Merged
Conversation
Add WriteSet, obtained via writeSet() on ORMTemplate and repositories:
insert/update/upsert/remove plus AndFetch variants over collections that
may span multiple entity types. Insert and upsert extend the explicit
members with their insertion closure (unsaved entities transitively
reachable through insertable foreign key fields, including inline
components and entity-wrapped refs). Execution is ordered by foreign key
dependencies, batched per type per dependency level, and generated keys
propagate to dependents by instance identity via record reconstruction.
Remove deletes children before parents, correlating members by type and
primary key. Generated keys are only fetched when a dependent or the
caller consumes them.
Ref.of(entity) now accepts unsaved entities so refs can carry graph
edges. The unsaved-foreign-key error message points to writeSet().
Kotlin adds a scoped writeSet { } block; single-root typed variants are
default methods on the shared interface.
Fixes #269
…on tables A non-insertable FK component whose column value is carried by an insertable component of a composite primary key (the junction table pattern) can now join the insertion closure: the generated key is written into the carrying key component instead of failing fast.
Pass the repository lookup explicitly to the constructor instead of capturing WriteSetImpl.this, and make collectFkEdges static; it uses no instance state.
# Conflicts: # website/static/llms-full.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Inserting an object graph that spans several tables requires hand-rolled choreography: insert parents, correlate generated keys back by position, rebuild children against those keys, repeat per level. The same ordering problem exists in reverse for deletes. Storm's fail-fast validation correctly rejects unsaved references, but the only remedy it could suggest was manual ordering.
Design
A write set applies one write operation to a heterogeneous collection of entities, obtained via
orm.writeSet()(also available on repositories, delegating to the template):Contract:
Unsaved is the deterministic local test Storm already uses: default primary key with an auto-generation strategy. No session, no cache involvement, no annotation-driven cascade; per-row semantics (callbacks, dirty checking, native upsert, joined inheritance) are exactly those of the per-repository operations, which the executor delegates to.
Fail-fast before any statement executes: unorderable dependency cycles, id-only refs with default ids, unsaved members in update/remove, and unsaved entities behind non-insertable foreign key components.
Implementation notes
st.orm.WriteSetinterface instorm-foundation; executorWriteSetImplinstorm-core;writeSet()onRepositoryLookup/Repositoryin core, mirrored instorm-java21andstorm-kotlin.Ref.of(entity)no longer requires a persisted id, so entity-wrapped refs can carry unsaved instances as graph edges. Key propagation rebuilds ref components by wrapping the persisted instance, preserving concrete subtypes.writeSet().writeSet { }block (eager; groups calls, never defers or reorders).Docs and AI assets
docs/write-sets.md(sidebar: Operations), README docs table row, comparison table updates (Cascade persistcell +Graph persistenceaspect row),llms-full.txtregenerated (page added to the generation script's list), and Write Sets sections in thestorm-repository-{java,kotlin}skills served at orm.st/skills.Tests
24 tests across four suites:
WriteSetIntegrationTest(storm-core, 18: closure, shared parents, inline-component and wrapped-ref edges, keyed-member ordering, input-order fetch, equal-but-distinct instances, update/remove isolation, repository access incl. proxy dispatch, single-root variants, fail-fast paths),WriteSetLevelingTest(cycle detection),WriteSetTest.kt(storm-kotlin, data classes, block DSL),H2WriteSetTest(storm-h2, real MERGE upserts, sequence keys, upsert ordering precedence). Full reactor green.Known limitations
Fixes #269