Several statements as one unit of work - #14
Merged
Merged
Conversation
`conn.transaction()` starts a span and hands back a `Transaction` whose
`commit()` and `rollback()` end it. The statements in between stay the
connection's, because the span belongs to the connection rather than to
a second handle on it, and `conn.inTransaction` says whether one is
open. `{ readOnly: true }` starts a span that refuses the statement that
writes. Nesting is refused by the engine, with the engine's own
condition, rather than by a rule kept here that would have to be held in
step with it.
`await using tx` rolls back. That is the opposite of what the Python
client's `with` block does, and the difference is in the language and
not in the database: a Python context manager is handed the exception
unwinding through it, and a JavaScript disposal is told nothing at all.
A disposal that committed would commit half the work of a block that
threw, which is the one thing a transaction exists to prevent. So the
commit is the word the caller writes, a block that forgets it loses its
work loudly, and the alternative was losing half of it quietly.
`inTransaction` is an `AtomicBool` beside the connection's lock, written
out of the session's own answer by every statement that runs. A tally
kept here would have been a second thing to keep in step, and a caller
who writes `START TRANSACTION` as a statement would have defeated it.
The three places that take the lock now go through one helper, so what
an empty slot means, which is a stream holding the connection rather
than a closed one, is decided once.
The engine pin moves from #338 to current main, which brings in #385:
reading a property no column holds is null rather than an error, so
nodes match edges and ISO 20.11. The misuse test that asserted the old
behaviour moves to the tests for programs that look like misuse and are
not, with what it costs written down beside it, which is that a
misspelled property is null for every row.
33 tasks
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.
conn.transaction()starts a span and hands back aTransactionwhosecommit()androllback()end it. The statements in between stay the connection's, because the span belongs to the connection rather than to a second handle on it, andconn.inTransactionsays whether one is open.{ readOnly: true }starts a span that refuses the statement that writes. Nesting is refused by the engine, with the engine's own condition, rather than by a rule kept here that would have to be held in step with it.The one decision worth arguing about
await using txrolls back. That is the opposite of what the Python client'swithblock does, and the difference is in the language and not in the database. A Python context manager is handed the exception that is unwinding through it, so it can tell a block that ended well from one that failed and commit only the first. A JavaScript disposal is told nothing at all. So a disposal that committed would commit half the work of a block that threw, which is the one thing a transaction exists to prevent, and the commit has to be the word the caller writes.A block that ends well and forgets to commit loses its work, which is a loud kind of wrong and shows up the first time the code runs. The alternative was a block that failed and kept half of what it did, which is a quiet kind and shows up in production. The reasoning is in the module doc as well as here, so it does not get fixed later by somebody reading the Python client beside it.
How
inTransactionknowsAn
AtomicBoolbeside the connection's lock, written out of the session's own answer by every statement that runs. A tally kept in this client would have been a second thing to hold in step with the engine, and a caller who writesSTART TRANSACTIONas a statement would have defeated it. The getter reads the atomic, so it is exact without taking a lock on the event loop thread.The three places that take that lock, a statement, a stream and now a transaction's end, go through one helper. What an empty slot means, which is a stream holding the connection rather than a connection that is closed, is decided once instead of three times.
The engine pin
Moves from #338 to current main, which brings in tamnd/zu#385: reading a property no column holds is null rather than an error, so nodes match edges and ISO 20.11. One misuse test asserted the old behaviour. It moves to the tests for programs that look like misuse and are not, with what the change costs written down beside it, which is that a misspelled property is null for every row rather than an error on the first.
What is tested
Seventeen cases: commit, rollback,
await usingwithout a commit,await usingafter one, a throw inside the block,inTransactionacross a span and after a lone statement, a span opened by hand with the three words, a double commit and a rollback after a commit both refused asZuUsageErrorwith no GQLSTATUS to branch on, a read-only span refusing a write, nesting refused by the engine, a transaction on a closed connection, a disposal whose connection is already gone, an option that is not a boolean refused with what arrived,undefinedread as unwritten, and a rollback, commit, rollback loop leaving the connection ready each time.npm testis 140 tests, 131 passing and 9 skipped, 0 failing.cargo clippy --all-features -- -D warnings,cargo fmt --check,npm run check:types,npm run check:api,npm run check:packageandnpm run referenceare all clean.Milestone: tamnd/zu#169, item 3.