Skip to content

native: save() merges only the changed values (stacked on #194) - #246

Closed
tae898 wants to merge 1 commit into
fix/native-save-versionfrom
feat/native-save-echo
Closed

tae898 wants to merge 1 commit into
fix/native-save-versionfrom
feat/native-save-echo

Conversation

@tae898

@tae898 tae898 commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Closes #245.

Stacked on #194 (fix/native-save-version, head ea2b429): that PR changed the save path this one edits, so the base is its branch and the diff here is one commit. Retarget to main when #194 merges.

save() of a saved record sent every property in UPDATE :rid MERGE :row. The stored row is already compared with the copy of the values as read before the statement runs (#194), so a value that is as read is already stored. The statement now carries the changed values only, as the caller holds them (an ndarray stays an array so the codec's binary path still applies). A save that changes nothing still runs its UPDATE with every value, so update events fire as before.

Not changed: the insert reply (the record adopts the stored row, since the engine converts to declared types) and the update read-back, which #194 already skips for values stored as sent. Command rows are already pulled but not encoded until read. A cheaper insert reply would need a native library change (image rebuild) and is not part of this PR.

Tests: tests/test_native_save_merge.py (native-only, listed in tests/conftest.py): one changed property is the only one merged, a save of nothing still runs its UPDATE, an ndarray goes as the array, a tuple equal to what was read is not merged, a new and a changed property merge together and the next save merges only its own change, a stale save is still refused and writes nothing, repeated saves merge one change each. CI only.

Draft: the claim (fewer microseconds per save) is measured by a timed A/B against #194's head, pre-registered in the maintainer's study notes before its first run; nothing is claimed here until it is read.

🤖 Generated with Claude Code

The stored row equals the copy of the values as read (save() checks that before it writes), so a value that is as read is already stored.
The UPDATE carried every property of the record; it now carries the changed ones, as the caller holds them (an ndarray stays an array).
A save that changes nothing still runs its UPDATE with every value, so update events fire as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tae898

tae898 commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

Test runs for this PR, read on 2026-11-04:

Run Branch Code Result
41742617187 feat/native-save-echo (head 324e41dd6e) this PR every job green (all 42 checks)
41741573239 tests-only/native-save-merge (head 37120f255a) PR 194's head plus only the new test file test_native_save_merge.py: 5 failed, 2 passed; every other file passes (linux-x86_64 py3.12 parity: 2007 passed, 5 failed, 0 errors)

The five that fail on #194's code are the merge-scope tests: test_one_changed_property_is_the_only_one_merged, test_a_changed_vector_goes_as_the_caller_holds_it, test_a_tuple_equal_to_what_was_read_is_not_merged, test_a_new_property_and_a_changed_one_are_merged_together, test_repeated_saves_of_one_object_merge_one_change_each. The two that pass there check what must not change: a save that changes nothing still runs its UPDATE, and a stale save is still refused and writes nothing. The tests-only branch is deleted after this read. The step-0 probe survived its drop rule (merge-all minus merge-changed was 18.3% of a medium update save, untimed); the timed A/B is queued.

@tae898

tae898 commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

Closing by the pre-written rule of the timed A/B (GRAY, final): measurements in #245 (comment). save() keeps the behaviour of #194.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant