Skip to content

Conversation

@radarroark
Copy link
Collaborator

@radarroark radarroark commented Jan 15, 2026

  • Adds a freeze! function which makes data created in a transaction immutable, along with unit tests.
  • Adds a bunch of fixes to LinkedArrayList which should've been in a separate PR but oh well :^D
  • Fixes the cons implementations; I found that they were still not correct because they were not returning the correct type. Now they all call conj (which the HashMap is already doing).
  • Adds print-method implementations for the writeable data structures.
  • Updates the README to show how to use freeze!.

@codeboost
Copy link
Owner

@claude Can you review the PR and let us know if there's anything worth addressing ?

@claude
Copy link

claude bot commented Jan 15, 2026

Claude finished @codeboost's task in 1m 35s —— View job


Code Review Complete ✓

I've reviewed the PR and the implementation looks solid overall! Here are my findings:


Summary

This PR successfully implements the freeze! function to make data structures immutable within a transaction, along with several other improvements to the data structure implementations. The code is well-tested and the README documentation is clear.


Positive Findings

  1. Well-tested implementation: The test suite in freeze_test.clj covers all four data structure types (ArrayList, LinkedArrayList, HashMap, HashSet) with both "without freeze" and "with freeze" scenarios. This demonstrates the issue and validates the fix.

  2. Consistent implementation: The IReadOnly protocol and -read-only method are consistently implemented across all writable data structures.

  3. Good documentation: The README provides a clear example of the surprising behavior and how freeze! solves it.

  4. Correct cons fix: The change from (cons o ...) to (. clojure.lang.RT (conj ... o)) is correct - cons always returns a clojure.lang.Cons type, while conj preserves the collection type.

  5. Useful print-method implementations: These will make debugging much easier in the REPL.


Issues Found

1. Potential Issue: freeze! implementation doesn't validate input (Minor)

Location: src/xitdb/db.clj:234-241

The freeze! function calls common/-unwrap and then accesses .cursor without checking if the value implements the required protocols. If someone calls freeze! on a regular Clojure collection or primitive value, this will throw an unclear error.

Suggestion: Add validation to provide a clearer error message:

(defn freeze!
  "Prevents all data written in the current transaction from
  being mutated by any remaining changes. Throws if called
  outside of a transaction. Returns a read-only version of the
  given writeable data structure."
  [x]
  (when-not (satisfies? common/IUnwrap x)
    (throw (IllegalArgumentException. 
            (str "freeze! requires a writeable XITDB data structure, got: " (type x)))))
  (-> x common/-unwrap .cursor .db .freeze)
  (common/-read-only x))

2. Duplicate protocol implementation for XITDBLinkedArrayList (Bug)

Location: src/xitdb/linked_list.clj:238-243

There are two IMaterialize protocol implementations for XITDBLinkedArrayList - one at line 112-116 and another at line 238-243. The second one returns a list while the first returns a vector. This is likely a copy-paste error.

The second implementation (lines 238-243) should probably be deleted, as it:

  • Is redundant (protocol can only be extended once)
  • Returns a different type (list vs vector)
  • May not even be taking effect depending on load order

Fix: Remove the duplicate at lines 238-243, or if the intent was to return a list, update the first implementation instead.

3. Missing containsKey and entryAt for write types (Minor Inconsistency)

Location: src/xitdb/linked_list.clj:126+

The read-only XITDBLinkedArrayList doesn't implement clojure.lang.Associative, but the writable XITDBWriteLinkedArrayList does (lines 165-170). However, the writable version is missing containsKey and entryAt methods that are part of the Associative interface.

Looking at XITDBWriteArrayList in array_list.clj:187-199, it correctly implements all three methods: assoc, containsKey, and entryAt.

Fix: Add the missing methods to XITDBWriteLinkedArrayList:

clojure.lang.Associative
(assoc [this k v]
  (when-not (integer? k)
    (throw (IllegalArgumentException. "Key must be integer")))
  (operations/linked-array-list-assoc-value! wlal k (common/unwrap v))
  this)

(containsKey [this k]
  (and (integer? k) (>= k 0) (< k (.count wlal))))

(entryAt [this k]
  (when (.containsKey this k)
    (clojure.lang.MapEntry. k (.valAt this k))))

4. Inconsistent Associative implementation for read-only LinkedArrayList (Minor)

Location: src/xitdb/linked_list.clj:14-107

The read-only XITDBLinkedArrayList implements IPersistentVector and Indexed (lines 46-62) but doesn't implement Associative, unlike XITDBArrayList which implements all three (see array_list.clj:34-50).

According to the PR description: "Implements clojure.lang.IPersistentVector and clojure.lang.Associative for LinkedArrayList", but only IPersistentVector was added to the read-only version.

Fix: Add Associative implementation to XITDBLinkedArrayList similar to XITDBArrayList.


Recommendations

  1. Fix the duplicate IMaterialize protocol - This is the most important issue as it's a bug.

  2. Complete the Associative implementation - For consistency with ArrayList and with the PR description.

  3. Add validation to freeze! - For better developer experience with clear error messages.

  4. Consider adding a test for calling freeze! with invalid input to ensure good error messages.


Questions

  1. Line 48 in freeze_test.clj: The expected result shows ["fish" "rice" "eggs" "apple" "pear" "grape"] - is this the intended order for lists? It appears elements are being prepended (LIFO) which makes sense for a linked list, but the order is interesting since conj is being used.

  2. Structural sharing comment in README (line 170): The comment says "this is not doing an expensive copy" - is this guaranteed by the Java implementation? It would be good to verify that freeze indeed creates a read-only view with structural sharing rather than a deep copy.


Overall Assessment

The implementation is well-designed and solves a real usability issue. The test coverage is excellent. The issues identified are minor and mostly about consistency and error handling. The core functionality appears correct.

Recommendation: Merge after addressing the duplicate IMaterialize protocol bug. The other issues are nice-to-haves that could be addressed in follow-up PRs.


@radarroark
Copy link
Collaborator Author

radarroark commented Jan 16, 2026

@codeboost I got all of claude's suggestions in. I also added a bunch of linked array list fixes. I removed some interface impls from the read-only LinkedArrayList (specificaly IPersistentList and Associative) because they were forcing me to return a vector, whereas we want the LinkedArrayList to materialize to a clojure linked list. That's a small detail tho...I am still implementing those interfaces on the WriteLinkedArrayList which is the one that matters most.

Copy link
Owner

@codeboost codeboost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTMT 👍

@radarroark
Copy link
Collaborator Author

Cool, I have no more changes to add so feel free to merge

@codeboost codeboost merged commit 980bfe8 into master Jan 16, 2026
2 checks passed
@radarroark radarroark deleted the freeze branch January 16, 2026 12:53
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.

3 participants