Repository navigation
server: a write that fails partway in a transaction it joined refuses that one's commit, and AddMany records each statement's rows as it holds - #14
Merged
Conversation
… that one's commit, and AddMany records each statement's rows as it holds A generated write inside a transaction somebody else began joins it, and its Close does nothing there: there are no savepoints, and ending that transaction is not the write's to do. So a write that failed after it had begun writing left what it wrote in the transaction -- the row of an Add whose record was refused, the UPDATE of an Apply whose read after it failed, the first few hundred rows of an AddMany whose next INSERT was refused. PostgreSQL takes the transaction with a refused statement, but SQLite undoes the statement alone, and a failure that is not a statement's undoes nothing on either. A caller that went on and committed committed part of a write it had been told had failed. And AddMany wrote every statement first and recorded after, so the rows of a statement that held sat in the transaction with nothing recorded of them until the last statement did -- which, when a later one was refused, was never. Every write now says `tx.Writing()` before its first statement that writes (protobuf-orm/ent#16): Add before its INSERT, AddMany before its first, Apply before its UPDATE when the document writes, Erase before its DELETE or its stamp. A Close with no Commit after that refuses the commit of the transaction it joined (`ent.ErrPartial`), when that one was begun with `dialect.BeginTx`. Said before the statement rather than after, so that a statement refused on its own is answered on SQLite as PostgreSQL answers it. A write that fails before it writes refuses nothing, and one that began its own transaction is taken back by its Close as before. AddMany records the rows of each statement as soon as it holds, before the next is sent: nothing is in the transaction that is not recorded, and what a failure leaves is refused its commit. `internal/apptest` pins protobuf-orm/ent at #16's head. Tested in `x-partial_test.go`, in a transaction begun with BeginTx: an AddMany whose second statement is refused has recorded the 500 rows of the first, and its commit is refused; so is the commit after an Add whose record is refused, an Add the database refuses, an Apply and an Erase whose record is refused -- and nothing of any of them holds. A write refused before it wrote, and one that held, refuse nothing. Each fails with its line taken out of the generated server: Writing in AddMany, Add, apply and Erase, and the record after every statement rather than after each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
The pin was #16's head, d22eff2; this is the merge commit, the same tree. Generated code does not move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 21, 2026
…write that fails partway refuses the commit of the transaction it joined (#84) The two halves of the review's T5-4 and T5-5, merged, and what payday's apptest is regenerated with: - protobuf-orm/ent#16 (b333b8f): `dialect.BeginTx` refuses to begin inside a transaction it did not begin (`dialect.ErrForeignTx`), hands back the end of a transaction and nothing that sends statements (a `driver.Tx`), and lets work that cannot take back what it did refuse the transaction's commit (`Keep.Refuse`); `ent.Joined` does that for a write that joined a kept transaction and failed after it began writing (`ent.ErrPartial`). - protobuf-orm/protoc-gen-orm-ent#14 (f86fc4a): every generated write says `tx.Writing()` before its first statement that writes, and an AddMany records each statement's rows as soon as it holds. - protobuf-orm/protoc-gen-orm-ent#13 comes with it: an AddMany's rule hands the request on whole. - The fork's master since 5460d90 comes with it too: a mutation that assigns a SQL expression, setting a field after clearing it, generated files importing what they use, views removed. So an AddMany in an app's operation or a batch whose second INSERT is refused leaves nothing for that transaction to commit: its commit is refused, and neither the five hundred rows the first INSERT wrote nor what the trail kept of them holds. On SQLite, which undoes a refused statement alone, they were committed with the transaction. `cmd/access_test.go` holds what BeginTx hands back as a `driver.Tx`. Tested: `TestAnAddManyThatFailsPartwayIsNotCommitted` -- an AddMany of 501 robots in a transaction begun with BeginTx, whose last is taken -- has its commit refused, with no robot and no line of the trail of it held; it fails with `tx.Writing()` taken out of the generated AddMany. `pd gen --check` is clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 21, 2026
…write that fails partway refuses the commit of the transaction it joined (#84) The two halves of the review's T5-4 and T5-5, merged, and what payday's apptest is regenerated with: - protobuf-orm/ent#16 (b333b8f): `dialect.BeginTx` refuses to begin inside a transaction it did not begin (`dialect.ErrForeignTx`), hands back the end of a transaction and nothing that sends statements (a `driver.Tx`), and lets work that cannot take back what it did refuse the transaction's commit (`Keep.Refuse`); `ent.Joined` does that for a write that joined a kept transaction and failed after it began writing (`ent.ErrPartial`). - protobuf-orm/protoc-gen-orm-ent#14 (f86fc4a): every generated write says `tx.Writing()` before its first statement that writes, and an AddMany records each statement's rows as soon as it holds. - protobuf-orm/protoc-gen-orm-ent#13 comes with it: an AddMany's rule hands the request on whole. - The fork's master since 5460d90 comes with it too: a mutation that assigns a SQL expression, setting a field after clearing it, generated files importing what they use, views removed. So an AddMany in an app's operation or a batch whose second INSERT is refused leaves nothing for that transaction to commit: its commit is refused, and neither the five hundred rows the first INSERT wrote nor what the trail kept of them holds. On SQLite, which undoes a refused statement alone, they were committed with the transaction. `cmd/access_test.go` holds what BeginTx hands back as a `driver.Tx`. Tested: `TestAnAddManyThatFailsPartwayIsNotCommitted` -- an AddMany of 501 robots in a transaction begun with BeginTx, whose last is taken -- has its commit refused, with no robot and no line of the trail of it held; it fails with `tx.Writing()` taken out of the generated AddMany. `pd gen --check` is clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 21, 2026
…write that fails partway refuses the commit of the transaction it joined (#84) The two halves of the review's T5-4 and T5-5, merged, and what payday's apptest is regenerated with: - protobuf-orm/ent#16 (b333b8f): `dialect.BeginTx` refuses to begin inside a transaction it did not begin (`dialect.ErrForeignTx`), hands back the end of a transaction and nothing that sends statements (a `driver.Tx`), and lets work that cannot take back what it did refuse the transaction's commit (`Keep.Refuse`); `ent.Joined` does that for a write that joined a kept transaction and failed after it began writing (`ent.ErrPartial`). - protobuf-orm/protoc-gen-orm-ent#14 (f86fc4a): every generated write says `tx.Writing()` before its first statement that writes, and an AddMany records each statement's rows as soon as it holds. - protobuf-orm/protoc-gen-orm-ent#13 comes with it: an AddMany's rule hands the request on whole. - The fork's master since 5460d90 comes with it too: a mutation that assigns a SQL expression, setting a field after clearing it, generated files importing what they use, views removed. So an AddMany in an app's operation or a batch whose second INSERT is refused leaves nothing for that transaction to commit: its commit is refused, and neither the five hundred rows the first INSERT wrote nor what the trail kept of them holds. On SQLite, which undoes a refused statement alone, they were committed with the transaction. `cmd/access_test.go` holds what BeginTx hands back as a `driver.Tx`. Tested: `TestAnAddManyThatFailsPartwayIsNotCommitted` -- an AddMany of 501 robots in a transaction begun with BeginTx, whose last is taken -- has its commit refused, with no robot and no line of the trail of it held; it fails with `tx.Writing()` taken out of the generated AddMany. `pd gen --check` is clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 21, 2026
…write that fails partway refuses the commit of the transaction it joined (#84) The two halves of the review's T5-4 and T5-5, merged, and what payday's apptest is regenerated with: - protobuf-orm/ent#16 (b333b8f): `dialect.BeginTx` refuses to begin inside a transaction it did not begin (`dialect.ErrForeignTx`), hands back the end of a transaction and nothing that sends statements (a `driver.Tx`), and lets work that cannot take back what it did refuse the transaction's commit (`Keep.Refuse`); `ent.Joined` does that for a write that joined a kept transaction and failed after it began writing (`ent.ErrPartial`). - protobuf-orm/protoc-gen-orm-ent#14 (f86fc4a): every generated write says `tx.Writing()` before its first statement that writes, and an AddMany records each statement's rows as soon as it holds. - protobuf-orm/protoc-gen-orm-ent#13 comes with it: an AddMany's rule hands the request on whole. - The fork's master since 5460d90 comes with it too: a mutation that assigns a SQL expression, setting a field after clearing it, generated files importing what they use, views removed. So an AddMany in an app's operation or a batch whose second INSERT is refused leaves nothing for that transaction to commit: its commit is refused, and neither the five hundred rows the first INSERT wrote nor what the trail kept of them holds. On SQLite, which undoes a refused statement alone, they were committed with the transaction. `cmd/access_test.go` holds what BeginTx hands back as a `driver.Tx`. Tested: `TestAnAddManyThatFailsPartwayIsNotCommitted` -- an AddMany of 501 robots in a transaction begun with BeginTx, whose last is taken -- has its commit refused, with no robot and no line of the trail of it held; it fails with `tx.Writing()` taken out of the generated AddMany. `pd gen --check` is clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 21, 2026
…write that fails partway refuses the commit of the transaction it joined (#84) The two halves of the review's T5-4 and T5-5, merged, and what payday's apptest is regenerated with: - protobuf-orm/ent#16 (b333b8f): `dialect.BeginTx` refuses to begin inside a transaction it did not begin (`dialect.ErrForeignTx`), hands back the end of a transaction and nothing that sends statements (a `driver.Tx`), and lets work that cannot take back what it did refuse the transaction's commit (`Keep.Refuse`); `ent.Joined` does that for a write that joined a kept transaction and failed after it began writing (`ent.ErrPartial`). - protobuf-orm/protoc-gen-orm-ent#14 (f86fc4a): every generated write says `tx.Writing()` before its first statement that writes, and an AddMany records each statement's rows as soon as it holds. - protobuf-orm/protoc-gen-orm-ent#13 comes with it: an AddMany's rule hands the request on whole. - The fork's master since 5460d90 comes with it too: a mutation that assigns a SQL expression, setting a field after clearing it, generated files importing what they use, views removed. So an AddMany in an app's operation or a batch whose second INSERT is refused leaves nothing for that transaction to commit: its commit is refused, and neither the five hundred rows the first INSERT wrote nor what the trail kept of them holds. On SQLite, which undoes a refused statement alone, they were committed with the transaction. `cmd/access_test.go` holds what BeginTx hands back as a `driver.Tx`. Tested: `TestAnAddManyThatFailsPartwayIsNotCommitted` -- an AddMany of 501 robots in a transaction begun with BeginTx, whose last is taken -- has its commit refused, with no robot and no line of the trail of it held; it fails with `tx.Writing()` taken out of the generated AddMany. `pd gen --check` is clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
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.
From the review of lesomnus/payday#84's work, item T5-4.
Builds on protobuf-orm/ent#16, which is merged:
internal/apptestpins its merge commit, b333b8f.What was wrong
A generated write inside a transaction somebody else began joins it, and its
Closedoes nothing there. There are no savepoints, and ending that transaction is not the write's job. So a write that failed after it had begun writing left what it wrote in the transaction:PostgreSQL takes the transaction with a refused statement. SQLite undoes the statement alone, and a failure that is not a statement's undoes nothing on either engine. A caller that went on and committed committed part of a write it had been told had failed.
AddMany also wrote every statement first and recorded after. The rows of a statement that held sat in the transaction with nothing recorded of them until the last statement held; when a later statement was refused, that never happened. payday's trail is such a recorder, so those rows were written past the trail.
What changes
Every write says
tx.Writing()before its first statement that writes (ent.Joined.Writing, dialect: a transaction's end is its owner's -- BeginTx refuses a foreign one, and joined work that failed partway refuses the commit ent#16):A
Closewith noCommitafter that refuses the commit of the transaction the write joined (ent.ErrPartial), when that transaction was begun withdialect.BeginTx.What does not refuse:
Close, as before.Client.Tx) is its owner's to answer for.Writing()goes before the statement, not after it. A statement refused on its own is then answered the same way on SQLite as PostgreSQL answers it.AddMany records the rows of each statement as soon as it holds, before the next statement is sent. Nothing is in the transaction that is not recorded, and what a failure leaves behind is refused its commit.
Docs: the README and the AddMany doc say so.
Tests
internal/apptest/server/bare/x-partial_test.goruns each case in a transaction begun withBeginTx(SQLite, where a refused statement does not end the transaction):An AddMany of 501 whose second statement is refused has recorded the 500 rows of the first, its commit is refused, and nothing holds.
The commit is also refused after:
Nothing of any of them holds.
A write refused before it wrote (an AddMany item that cannot be a row) refuses nothing.
A write that held refuses nothing either.
Mutations, each made in the generated
user.g.goand each caught:Writingtaken out of AddMany, of Add (both Add cases fail), ofapply, and of Erase;Locally:
buf generatetwice andgo generate ./...ininternal/apptest, with nothing moved;go test ./...in the root,runtimeandinternal/apptest, all passing.🤖 Generated with Claude Code
https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk