Skip to content

Lifecycle: results and records keep their Database and raise once it is closed; failed create_vertices rolls back; set reads back as a list (#117, #121, #122) - #130

Merged
tae898 merged 4 commits into
mainfrom
fix-lifecycle
Oct 3, 2026
Merged

tae898 merged 4 commits into
mainfrom
fix-lifecycle

Conversation

@tae898

@tae898 tae898 commented Oct 3, 2026 •

Copy link
Copy Markdown

Fixes #121, #122, and #117. The engine halves are filed upstream: ArcadeData#9038 (BinarySerializer swallowing DatabaseIsClosedException, for #117) and ArcadeData#9040 (GraphBatch.createVertices rolling back only for a NeedRetryException, for #121).

#121. GraphBatch.create_vertices opens its transaction inside the engine and the engine rolls it back only for a retryable error, so a duplicate key (or a KeyboardInterrupt) left it active and a later write outside any transaction was accepted and lost at close. The wrapper now rolls back, as create_vertex does after ArcadeData#8828, when the call started the transaction, and leaves one the caller already had open alone. Three tests: JSON-bulk and property-matrix paths, the caller's own transaction, and a KeyboardInterrupt after the engine's transaction began.

#117. ResultSet, Result, and the record wrappers now hold the Database that produced them and check it on every read, so a read after close() raises ArcadeDBError("Database is closed") as db.query does, instead of returning {} or None or a raw TransactionException. Holding the reference also keeps the database open while a result is alive, which is what stops Database.__del__ from closing it under a returned result. A result set read to its end still reads as empty. The consequence is documented and tested: while a result is alive the engine refuses a second open_database() of the same path ("already in use"), and with every result and record gone the wrapper's __del__ closes the database and the path opens again. Only a row that is a record (SELECT FROM T) needs the open database, so a projection or command Result that was already returned keeps its own values and stays readable after close(), and an unread result set raises. My first version refused every Result; the examples job caught that: example 16 reads the IMPORT DATABASE result after closing its database (macOS, and Linux with Python 3.14), which is fixed in the last commit and covered by a test.

#122. The conversion page said a set reads back as a set. That holds only inside the transaction that set it: the engine has no set type, so the HashSet is serialized as a list at commit. The tables, examples, and collection section are corrected, and a test pins the behavior so it fails if the engine ever keeps sets.

Verified: the new tests fail on origin/main (4 of 6 new tests for #117, 3 of 4 for #121; the rest are guards against regressing the other way) and pass with the change, the full suite passes (521 passed, 3 skipped, 3 xfailed), example 16 passes at its CI arguments, and bandit is clean at CI strictness.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DeKEhHXJTy1H1GgbfBZ8mL

tae898 and others added 3 commits October 3, 2026 21:02
create_vertices opens its transaction inside the engine and the engine rolls
it back only for a retryable error. A duplicate key (or a KeyboardInterrupt)
left it active, so a later write outside any transaction was accepted and lost
at close. Roll back, as create_vertex does, when the call started the
transaction, and leave one the caller already had open alone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeKEhHXJTy1H1GgbfBZ8mL
…s closed

ResultSet, Result, and the record wrappers took only the Java object, so
Database.close() (or Database.__del__ when the wrapper was collected) left them
reading through a closed database: a record row came back as {}, a record
property as None, and a plain scan raised a raw TransactionException, with the
engine logging "Possible corrupted record" for each read.

The wrappers now hold the Database that produced them. That keeps the database
open while any of them is alive, which is what stops Database.__del__ from
closing it under a returned result, and every read checks the flag and raises
ArcadeDBError("Database is closed"), as db.query does. A result set read to its
end still reads as empty. The engine half (BinarySerializer swallowing
DatabaseIsClosedException) is reported upstream separately.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeKEhHXJTy1H1GgbfBZ8mL
…ter the commit

The conversion page said a set becomes a HashSet and reads back as a set. That
holds only for the record of the transaction that set it: the engine has no set
type, so the HashSet is serialized as a list at commit and every later read
returns a list. Correct the tables and examples, add a note to the collection
section, and pin the behavior with a test that fails if the engine ever keeps
sets.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeKEhHXJTy1H1GgbfBZ8mL
…tabase is closed

The examples job caught it: example 16 reads the Result of IMPORT DATABASE after
closing the database it ran on, and the rule that refused every Result after
close made it fail on macOS and on Linux with Python 3.14. Only a row that is a
record (SELECT FROM T) loads its properties lazily from the open database; a
projection or a command result holds its own values. Result._check_open now
raises only for a record row. A result SET that was not read to its end still
raises, since its remaining rows may be read lazily from the engine.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeKEhHXJTy1H1GgbfBZ8mL
@tae898
tae898 merged commit 7f48b93 into main Oct 3, 2026
48 checks passed
@tae898
tae898 deleted the fix-lifecycle branch October 3, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant