Fix key hashing, collection equality, and compaction from a stale handle - #25
Merged
Merged
Conversation
Hash Date keys by epoch milliseconds to prevent collisions and timezone-dependent lookups. Reject Date subclasses that could lose precision. Replace printed collection-key hashes with canonical structural encoding, independent of printer settings, iteration order, and wrapper types. Implement value equality and hashing across read and transaction views so distinct, group-by, sets, and map lookups produce correct results. Add regression tests, document hashing helpers, and name encoding tags.
keySet, values, and entrySet on the map wrappers copied the whole map into a native Clojure map on every call. That lost sort order on sorted maps past eight entries, returned snapshots instead of views, and threw AbstractMethodError on the write wrappers. The throw is a pre-existing hole: IPersistentMap extends Iterable, but XITDBWriteHashMap, XITDBWriteSortedMap, and XITDBWriteLinkedArrayList never implemented iterator, so reduce and into failed on any of them inside a transaction. They now iterate their own seq. The views are now small AbstractSet/AbstractCollection proxies backed by the wrapper's seq, count, containsKey, and entryAt, modeled on clojure.lang.APersistentMap, shared from xitdb.util.collection. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Stamp "clj1" into the database header so files written with the old printer-dependent key hashes are rejected on open instead of silently missing every collection and Date key. The check does not modify the file, and compaction preserves the header ID. README documents how to export and re-import old files. Native and Java map equality call containsKey on the stored view with keys the database cannot encode (symbols, ratios, arbitrary objects). Those now read as absent instead of throwing, so = and equals return false in both operand orders. Map.get goes through entryAt for the same reason. Storage failures still propagate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 tasks
codeboost
added this pull request to stack #27
September 15, 2026 08:40
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.
Summary
Fixes four correctness bugs found in the 2026-09-10 database audit. All were reproduced through the public API before being fixed test-first. The database is still in development, so no migration is provided for the previous key encoding; instead, files written with the old encoding are now rejected on open.
java.util.Datekeys were hashed from their display string, which drops milliseconds and depends on the default timezone. They now hash from epoch milliseconds.Datesubclasses such asjava.sql.Timestampare rejected as keys rather than silently truncated.[1 2]were hashed fromproutput, so*print-length*, map iteration order, and wrapper print methods all changed the stored identity. Newxitdb.util.key-hashnamespace hashes collections canonically: type tags, fixed-width child digests, and sorted digests for maps and sets. Native and database-backed collections share the encoding.IHashEq,hashCode,equals,MapEquivalence, and thejava.util.Map/Setinterfaces, sodistinct,set,group-by, and native hash-map lookups work on stored collections, and=holds in both operand orders. Keys the database cannot encode (symbols, ratios, arbitrary objects) read as absent fromcontainsKeyandget, so comparing a native map that holds such a key against a stored view returns false instead of throwing.compactcould publish an empty copy of a populated file. A handle opened before another handle initialized the file still carried an uninitialized writer header.compactnow refreshes the source writer's header before copying.Key-hash format is now versioned. The database header records the Clojure key encoding as
clj1. Opening an older, unversioned file throws with instructions to export using the version that created it and import into a new database. The check does not modify the file, and compaction preserves the header ID. The README documents the upgrade path. A fixture written by master with xitdb 0.38.0 is checked in undertest-resourcesto pin the rejection.Also: top-level
Doublekeys are now accepted and hash the same way as nested doubles, dead branches in the scalar key-hash path are removed, and the xitdb Java dependency is bumped to 0.39.0.Test plan
clojure -M:test: 235 tests, 2,989 assertions, 0 failures on xitdb 0.39.0Datesubclass rejection, double keys across reopen, wrapper equality/hash in both operand orders and after in-transaction mutation, compaction from a handle opened before initialization, unsupported keys comparing unequal in both directions for hash and sorted maps, rejection of a legacy file without modifying it, and the current format surviving reopen and compaction🤖 Generated with Claude Code