Repository navigation
Conversation
…ossings, not three (refs ArcadeData#317) The transaction check of a saved record's save() cannot refuse while auto-transaction is on, yet it still crossed into the library (arcadedb_n_tx) before the SELECT and the UPDATE. These tests pin two crossings there, bare and inside a transaction, with and without the read-back of the row, and pin what must not move: the check is back the moment auto-transaction is off (first, before any read), a stale record is refused under auto-transaction too, and the stored values and the next save's baseline are those of an update inside a transaction for NULL, float, text, date-time, date, Decimal, nested values, a float32 array and a value set unchanged. The two-crossing tests fail on the code before the change (three crossings); the rest pass on both. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…te when auto-transaction is on (refs ArcadeData#317) _save_update asked is_transaction_active() (an arcadedb_n_tx crossing) first and only then looked at the auto-transaction flag, so the answer was thrown away whenever the flag was on. The operands are swapped: the flag is a Python attribute, the state is a crossing. With auto-transaction on, an update of a saved record is two crossings (the SELECT of the stale-record check and the UPDATE), not three. With it off nothing changes: the check still comes first, outside a transaction and inside one. The check is not replaced by the engine's refusal for the default mode: the engine runs the before-update callbacks and the property validation before it looks for a transaction (read in LocalDatabase.updateRecord of the 26.12.1-SNAPSHOT source), and an UPDATE of a record that is gone raises nothing, so a save outside a transaction would run callbacks, or pass silently, instead of raising "Transaction not begun" before it reads. A Python-side record of the transaction state is not exact either: a SQL COMMIT or ROLLBACK statement ends the transaction without passing the wheel's transaction calls. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Nov 13, 2026
Collaborator
Author
tae898
added a commit
that referenced
this pull request
Nov 14, 2026
…, and the stale statements (refs #315) (#399) JVM iter_dicts parses a batch with no tagged value without the object hook (#382); JVM insert_arrow and insert_dataframe carry a timestamp or date column as one long[] of epoch ticks (#383); native transaction calls return the state as one integer (#384); native save() of a saved record with auto-transaction on skips the transaction check (#385). The figures are copied by script from the scored gate study and each is checked against the public comment on #315. The n_update_one figure ran with auto-transaction off, so the page says off (the comment says on). The save() paragraph said three calls without the auto-transaction case. The quickstart was re-run on both published 26.11.1.post3 packages on Python 3.12 and printed the output shown. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
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.
What does this PR do?
Part of #317 and #315. This is a small, exact piece of the update half of #317, and it leaves the default case at three entries. I am opening it as a draft so that the rest can be decided with the facts below.
On the native wheel,
save()of a saved record (record.set(...)thensave()) makes three entries into the library, as I read_save_updateinsrc/arcadedb/_native/records.py:is_transaction_active()(arcadedb_n_tx),query(SELECT FROM :rid).first()for the stale-record check, and theUPDATE ... MERGE(exec_query, orarcadedb_n_exec_binwith a float array). The JVM wheel does it in two. #329 did the create half and moved the UPDATE to the eager form; it kept these three on purpose.The change: the check was
not db.is_transaction_active() and not db._auto_transaction, so the crossing was made first and its answer thrown away whenever auto-transaction is on. The operands are swapped, so the state is not asked for when the flag is on (a Python attribute is read instead of a crossing).save()of a saved record, auto-transaction off (the engine default), inside a transactionI did not count the foreign calls inside one entry, so I make no claim per entry. Nothing observable changes: with auto-transaction off the error, its message and its place (before any read) are the same, and with it on a closed database raises the same "Database is closed", from the SELECT instead of from the state call.
Why the default case stays at three
I looked for a way to get to two without a Java change and found none that keeps every behaviour.
LocalDatabase.updateRecordthe validation and the before-update events run before the engine looks for a transaction, so a save outside a transaction would run Python callbacks (or be swallowed by a veto) before it raised, and an UPDATE of a record that is gone raises nothing. I read this in the 26.12.1-SNAPSHOT source; I have not checked the 26.11.1 tag.begin,commitandrollback, which all go through_tx_text, but a SQLCOMMITorROLLBACKsent throughcommandends the transaction without passing there, so a stale "active" would skip a refusal the user relies on.RETURN BEFOREshows the conflict only after the write is in the caller's transaction (Native: a created or updated record reads its row in the statement's crossing (closes #317) #329 said the same). A conditional UPDATE would compare with the engine's=, not with_same(1against1.0, NaN,0.0against-0.0, a Decimal's scale, a missing key against a null), and a count of zero cannot tell a stale record from one that is gone.What would need a native change, as a separate PR if you want it: the reply of
arcadedb_n_execcarries"active":<bool>under a flag bit (db.isTransactionActive()is a thread-local read), so the SELECT of the stale-record check answers the transaction question too, and the default case is two entries. One entry would be a dedicated entry point that does the check, the compare and the merge in the engine, with the rules of_sameported to Java. I did not add either here.Related issues
Part of #317 and #315. No closing keyword: #317 stays open.
Performance changes
Not measured yet: the speed claim waits for a timed A/B of a candidate wheel against 26.11.1.post2. This PR makes no speed claim, and it only changes a path that is off by default.
Tests
In
tests/test_native_one_row_crossings.py(native backend only), in a first commit of their own:["arcadedb_n_tx"]), and still comes first inside a transaction; a stale record is refused withConcurrentModificationExceptionunder auto-transaction too and nothing is written; an update under auto-transaction stores what the same update inside a transaction stores, and leaves the same baseline (the next save of the same object is no conflict), for NULL, float, text, date-time, date, Decimal, a list and map, a float32 array and a value set unchanged.Docs
None needs it: no page or README section describes the entries of
save().How I verified it
Only light checks here, no build and no test run:
compile,black26.10.1,isort9.0.2 (--profile black),ruff0.17.4 andbandit1.9.4 on the two files, all clean. The tests run in CI only;test-native.ymlwas started on my fork for this branch (a draft skips the platform jobs here).Checklist
uvx pre-commit run --filesitself not run here🤖 Generated with Claude Code