HIBERNATE-217: support StatelessSession.upsert on unversioned entities - #199
Open
jyemin wants to merge 2 commits into
Open
HIBERNATE-217: support StatelessSession.upsert on unversioned entities#199jyemin wants to merge 2 commits into
jyemin wants to merge 2 commits into
Conversation
Translate the OptionalTableUpdate built for StatelessSession.upsert and upsertMultiple into a single update statement: filter from the key bindings, $set over value bindings that are both insertable and updatable, $setOnInsert for insertable-only ones, upsert: true, multi: false. visitOptionalTableUpdate serves two callers that hand it the same node: the boot-time forced version increment wants a plain update, the dialect wants an upsert. The caller's intent travels as the requested value descriptor, tested with expects(...). The dialect translates eagerly at SessionFactory build time and defers every rejection to performMutation, since an eager throw would fail boot for entities that work today for everything except upsert: @Version entities throw TODO-HIBERNATE-216, attributes that are updatable but not insertable and entities whose only persistent attribute is the identifier throw bare messages (MQL has no matched-path-only write), and optional tables keep the TODO-HIBERNATE-69 rejection. AstUpdateCommand now holds a list of {q, u, upsert?, multi} statements and renders byte-identically for every existing caller; AstDocumentUpdate gains $setOnInsert, rendering each operator only when non-empty and asserting the update is never empty; MongoStatement accepts upsert in update statements and extends the JDBC row count to matched plus upserted.
Hibernate's merge coordinator decomposes an aggregate embeddable into one binding per leaf field, so the upsert translation emitted one parameter per leaf, named addr.city, addr.zipCode, and so on. UpdateCoordinatorStandard binds a single value for the aggregate as a whole, under the aggregate's own selection expression. No parameter carried that name, so every upsert of an entity holding a @struct failed with UnknownParameterException, while insert, update and stateful persist all succeeded for the same mapping. Collapse each aggregate's leaf bindings into a single field update named after the aggregate column, backed by one synthesized ColumnValueParameter carrying the aggregate's mapping, so MongoStructJdbcType serializes the whole struct exactly as it does on the standard update path. The leaves' value expressions are deliberately not visited: each visit would append a parameter binder without a matching placeholder and shift every later binding. $set versus $setOnInsert is decided by the aggregate column's own updatable flag rather than the leaves', since the aggregate is what the coordinator binds. The root cause is upstream, HHH-20754, and reproduces on PostgreSQL with no MongoDB code involved. The collapsing code is labelled with that issue and with the condition under which it becomes deletable. Tests cover the shapes the collapsing logic branches on: two aggregates in one entity, a struct containing an array, a struct with a @parent back-reference, a null struct value, a struct mapped to a renamed column, the dot boundary that keeps an aggregate distinct from a scalar whose column name shares its prefix, and upsertMultiple over aggregates.
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.
Translate the OptionalTableUpdate built for StatelessSession.upsert and upsertMultiple into a single update statement: filter from the key bindings, $set over value bindings that are both insertable and updatable, $setOnInsert for insertable-only ones, upsert: true, multi: false.
visitOptionalTableUpdate serves two callers that hand it the same node: the boot-time forced version increment wants a plain update, the dialect wants an upsert. The caller's intent travels as the requested value descriptor, tested with expects(...).
The dialect translates eagerly at SessionFactory build time and defers every rejection to performMutation, since an eager throw would fail boot for entities that work today for everything except upsert: @Version entities throw TODO-HIBERNATE-216, attributes that are updatable but not insertable and entities whose only persistent attribute is the identifier throw bare messages (MQL has no matched-path-only write), and optional tables keep the TODO-HIBERNATE-69 rejection.
AstUpdateCommand now holds a list of {q, u, upsert?, multi} statements and renders byte-identically for every existing caller; AstDocumentUpdate gains $setOnInsert, rendering each operator only when non-empty and asserting the update is never empty; MongoStatement accepts upsert in update statements and extends the JDBC row count to matched plus upserted.