Repository navigation
Conversation
…, and hands back only the end of one BeginTx nested only when KeepOf found the transaction drv runs in. Inside one it could not see as its own -- a generated client's `Client.Tx`, whose driver says it is a transaction and keeps nothing -- it began a second keep over the no-op transaction that driver hands out. That keep answered to the wrong end: its commit wrote what it kept into a transaction it did not end, and its rollback dropped what it kept while the writes it was about stayed, for whoever did end the transaction to commit without it. What a transaction keeps is what makes the rest of it acceptable -- a trail, in lesomnus/payday -- so a write committed that way is a write the record does not have. Now it refuses with ErrForeignTx when the driver is in a transaction (InTx) that keeps nothing (KeepOf), through a DebugDriver as both of those look. Nothing is begun. And what it hands its caller is the end of the transaction and nothing else: a driver.Tx, Commit and Rollback. It was a dialect.Tx, which can also send statements -- straight to the connection, past the driver that asks the keep about each one first (Keep.BeforeStatement), so a statement sent that way did not find what the transaction kept. A transaction begun inside another hands back an end that does nothing, as before. Tested on the doubles: refused as it is and under a DebugDriver, with nothing begun; neither end it hands back, begun or begun inside, sends a statement. On a generated client in entc/integration/stduuid: BeginTx on a `Client.Tx`'s driver is refused. Each fails with its check taken out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
…uses that transaction's commit A generated write joins a transaction it finds itself in rather than nesting one -- there are no savepoints -- and its Close does nothing there, since ending the transaction is not its to do. So when it failed after it had begun writing, whatever it had written stayed in the transaction. PostgreSQL took the transaction with the failed statement; SQLite undoes the statement alone, and a failure that was not a statement's -- a recorder, a read after the write -- undoes nothing on either engine. A caller that went on and committed committed part of a write it had been told had failed. An AddMany of several statements, whose second INSERT is refused, is where lesomnus/payday found it. `Keep.Refuse(err)` says a transaction may not commit: its commit answers err and rolls back, with the kept work not done; the first refusal is the one answered. `ent.Joined` says it for the work. JoinTx, joining a transaction BeginTx began, holds that transaction's keep; the work says `Writing()` before its first statement that writes, and a Close with no Commit after that refuses the joined transaction's commit with ErrPartial. Work that fails before it writes has nothing to take back and refuses nothing, work that began its own transaction is taken back by its Close as before, and a transaction that keeps nothing -- a generated Client.Tx -- is its owner's to answer for. `ent.Db` asks for the client's Driver(), which is where the keep is found; a generated client has always had it. Tested on the doubles: a refusal is the commit's and the kept work is not done, the first refusal is answered, nil refuses nothing, a refusal said while the kept work runs or inside a transaction begun inside is the commit's; a joined write that began writing and failed refuses, one that failed before writing or that held does not, one that began its own transaction rolls back, one in a transaction that keeps nothing tells nobody. On every engine in entc/integration/keep, a refused transaction takes its writes back; on a generated client in entc/integration/stduuid, a joined write that failed partway leaves nothing committed. Each fails with its line taken out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk
This was referenced Oct 21, 2026
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
lesomnus
added a commit
to lesomnus/payday
that referenced
this pull request
Oct 21, 2026
go.mod: protobuf-orm/ent#16 and protoc-gen-orm-ent#14 -- a write that fails partway refuses the commit of the transaction it joined (#84)
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.
Two holes in how a transaction begun with
dialect.BeginTxis ended, found in the review of lesomnus/payday#84's work (T5-4, T5-5).1. BeginTx inside a transaction it did not begin
BeginTxnested only whenKeepOfrecognised the transactiondrvruns in. Inside one it could not recognise — a generated client'sClient.Tx, whose driver says it is a transaction (InTx) and keeps nothing — it began a secondKeepover the no-op transaction that driver hands out. That keep answered to the wrong end:What a transaction keeps is what makes the rest of it acceptable. In lesomnus/payday that is the trail, so a write committed this way is a write the record does not have. Nothing reaches this today; every stack is rebound onto a
BeginTxdriver.Now:
BeginTxrefuses withErrForeignTxand begins nothing when the driver is in a transaction (InTx) that keeps nothing (KeepOf). It looks through aDebugDriver, as both of those do.BeginTxhands its caller is adriver.Tx: Commit and Rollback, nothing else. It was adialect.Tx, which can also send statements — straight to the connection, past the driver that asks the keep about each one (Keep.BeforeStatement). A transaction begun inside another hands back an end that does nothing, as before.Callers that only
CommitandRollbackwhat they are handed compile unchanged. In payday that is the generated batch, rove and roster. One payday test helper is typeddialect.Txand changes with payday's bump.2. Work that failed partway in a transaction it joined
A generated write joins a transaction it finds itself in rather than nesting one; there are no savepoints. Its
Closedoes nothing there, since ending that transaction is not its job. So when the write failed after it had begun writing, what it wrote stayed in the transaction:A caller that went on and committed would commit part of a write it had been told had failed. The case that showed it is an AddMany whose second INSERT of a few hundred rows is refused.
Now:
Keep.Refuse(err)says a transaction may not commit. Its commit answerserrand rolls back, and the kept work is not done. The first refusal is the one answered.ent.Joinedsays it for the work:JoinTxjoins a transactionBeginTxbegan, it holds that transaction's keep.Writing()before its first statement that writes.Closewith noCommitafter that refuses the joined transaction's commit withErrPartial.Close, as before.Client.Tx) is its owner's to answer for.ent.Dbasks for the client'sDriver(), which is where the keep is found. A generated client has always had it.protoc-gen-orm-ent will emit
tx.Writing()in each generated write, in a PR that follows this one.UPSTREAM.md's Transactions row says all of this.Tests
Each test fails with its line taken out (mutations listed after each group).
dialect, on the doubles:BeginTxon a foreign transaction is refused, as it is and under aDebugDriver, and begins nothing.Mutations: no
ErrForeignTxcheck; the nested end being the inner transaction;finishnot reading the refusal;finishreading it only before the work.root (
Joined), on the doubles:ErrPartial).Mutations:
Closenot refusing;Writingnot marking;Commitnot marking.entc/integration/keep, on every engine: a refused transaction takes its writes back.entc/integration/stduuid, on a generated client:BeginTxon aClient.Tx's driver is refused.Locally:
go test ./ ./dialect/....entc/integrationkeep(SQLite) andstduuid.go build ./...in the root,examplesandentc/integration..github/lint.shon the changed files.CI runs the other engines.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KxtnpRW533bdErnW8QPhsk