From 1158cfaba99ed73f69aa926a36c74097fe804163 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:05:13 -0500 Subject: [PATCH 01/20] add freeze fn --- src/xitdb/array_list.clj | 4 ++++ src/xitdb/common.clj | 2 ++ src/xitdb/db.clj | 8 ++++++++ src/xitdb/hash_map.clj | 4 ++++ src/xitdb/hash_set.clj | 4 ++++ src/xitdb/linked_list.clj | 4 ++++ 6 files changed, 26 insertions(+) diff --git a/src/xitdb/array_list.clj b/src/xitdb/array_list.clj index 5204882..59375ec 100644 --- a/src/xitdb/array_list.clj +++ b/src/xitdb/array_list.clj @@ -248,6 +248,10 @@ (-unwrap [this] wal) + common/IReadOnly + (-read-only [this] + (XITDBArrayList. wal)) + Object (toString [this] (str "XITDBWriteArrayList"))) diff --git a/src/xitdb/common.clj b/src/xitdb/common.clj index e94bace..8a34780 100644 --- a/src/xitdb/common.clj +++ b/src/xitdb/common.clj @@ -15,6 +15,8 @@ (defprotocol IUnwrap (-unwrap [this])) +(defprotocol IReadOnly + (-read-only [this])) (defn materialize [v] (cond diff --git a/src/xitdb/db.clj b/src/xitdb/db.clj index ecc7152..7ff84c0 100644 --- a/src/xitdb/db.clj +++ b/src/xitdb/db.clj @@ -231,4 +231,12 @@ :else (throw (IllegalArgumentException. (str "xdb must be an instance of XITDBCursor or XITDBDatabase, got: " (type xdb)))))) +(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] + (-> x common/-unwrap .cursor .db .freeze) + (common/-read-only x)) diff --git a/src/xitdb/hash_map.clj b/src/xitdb/hash_map.clj index e564285..d788839 100644 --- a/src/xitdb/hash_map.clj +++ b/src/xitdb/hash_map.clj @@ -186,6 +186,10 @@ (-unwrap [this] whm) + common/IReadOnly + (-read-only [this] + (XITDBHashMap. whm)) + Object (toString [this] (str "XITDBWriteHashMap"))) diff --git a/src/xitdb/hash_set.clj b/src/xitdb/hash_set.clj index ded6dd1..d1ca3d4 100644 --- a/src/xitdb/hash_set.clj +++ b/src/xitdb/hash_set.clj @@ -146,6 +146,10 @@ (-unwrap [_] whs) + common/IReadOnly + (-read-only [this] + (XITDBHashSet. whs)) + Object (toString [_] (str "XITDBWriteHashSet"))) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index c3120a4..3c62b9b 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -202,6 +202,10 @@ (-unwrap [this] wlal) + common/IReadOnly + (-read-only [this] + (XITDBLinkedArrayList. wlal)) + Object (toString [this] (str "XITDBWriteLinkedArrayList"))) From aa33c453bae156814b07cba899e06144c3224420 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:06:44 -0500 Subject: [PATCH 02/20] improve linked array list --- src/xitdb/linked_list.clj | 23 ++++++++++++++++++++++- src/xitdb/util/operations.clj | 18 ++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index 3c62b9b..ecfed0f 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -43,6 +43,13 @@ (common/-read-from-cursor cursor) not-found))) + clojure.lang.IPersistentVector + (assocN [this i val] + (assoc (common/-materialize-shallow this) i val)) + + (length [this] + (.count rlal)) + clojure.lang.ILookup (valAt [this k] (if (number? k) @@ -122,7 +129,6 @@ (.count wlal)) (cons [this o] - ;; TODO: This should insert at position 0 (operations/linked-array-list-insert-value! wlal 0 (common/unwrap o)) this) @@ -148,6 +154,21 @@ (common/-read-from-cursor (.putCursor wlal i)) not-found)) + clojure.lang.IPersistentVector + (assocN [this i val] + (operations/linked-array-list-assoc-value! wlal i (common/unwrap val)) + this) + + (length [this] + (.count wlal)) + + 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) + clojure.lang.ILookup (valAt [this k] (.valAt this k nil)) diff --git a/src/xitdb/util/operations.clj b/src/xitdb/util/operations.clj index 848c858..729a7d7 100644 --- a/src/xitdb/util/operations.clj +++ b/src/xitdb/util/operations.clj @@ -62,6 +62,24 @@ (.write cursor (conversion/v->slot! cursor v)) wlal)) +(defn ^WriteLinkedArrayList linked-array-list-assoc-value! + "Associates a value at index i in a WriteLinkedArrayList. + Appends the value if the index equals the current count. + Replaces the value at the specified index otherwise. + Throws an IllegalArgumentException if the index is out of bounds." + [^WriteLinkedArrayList wlal i v] + + (assert (= Tag/LINKED_ARRAY_LIST (-> wlal .cursor .slot .tag))) + (assert (number? i)) + + (validation/validate-index-bounds i (.count wlal) "Array list assoc") + + (let [cursor (if (= i (.count wlal)) + (.appendCursor wlal) + (.putCursor wlal i))] + (.write cursor (conversion/v->slot! cursor v))) + wlal) + (defn linked-array-list-insert-value! "Inserts a value at position pos in a WriteLinkedArrayList. Converts the value to an appropriate XitDB representation using v->slot!." From 60016ac863839190cd14d41cb1baa877c67a4d71 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:07:12 -0500 Subject: [PATCH 03/20] fix cons impls --- src/xitdb/array_list.clj | 2 +- src/xitdb/hash_set.clj | 2 +- src/xitdb/linked_list.clj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/xitdb/array_list.clj b/src/xitdb/array_list.clj index 59375ec..192c406 100644 --- a/src/xitdb/array_list.clj +++ b/src/xitdb/array_list.clj @@ -19,7 +19,7 @@ (.count ral)) (cons [this o] - (cons o (common/-materialize-shallow this))) + (. clojure.lang.RT (conj (common/-materialize-shallow this) o))) (empty [this] []) diff --git a/src/xitdb/hash_set.clj b/src/xitdb/hash_set.clj index d1ca3d4..8dc7e1d 100644 --- a/src/xitdb/hash_set.clj +++ b/src/xitdb/hash_set.clj @@ -27,7 +27,7 @@ clojure.lang.IPersistentCollection (cons [this o] - (cons o (common/-materialize-shallow this))) + (. clojure.lang.RT (conj (common/-materialize-shallow this) o))) (empty [this] #{}) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index ecfed0f..b1f0b4d 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -20,7 +20,7 @@ (.count rlal)) (cons [this o] - (cons o (common/-materialize-shallow this))) + (. clojure.lang.RT (conj (common/-materialize-shallow this) o))) (empty [this] '()) From f9dc91ece11dfa35c5b57063aca628b8ad0d88d1 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:18:46 -0500 Subject: [PATCH 04/20] add print-method impls for writeable data --- src/xitdb/array_list.clj | 4 ++++ src/xitdb/hash_map.clj | 3 +++ src/xitdb/hash_set.clj | 4 ++++ src/xitdb/linked_list.clj | 4 ++++ 4 files changed, 15 insertions(+) diff --git a/src/xitdb/array_list.clj b/src/xitdb/array_list.clj index 192c406..aa19a9b 100644 --- a/src/xitdb/array_list.clj +++ b/src/xitdb/array_list.clj @@ -256,6 +256,10 @@ (toString [this] (str "XITDBWriteArrayList"))) +(defmethod print-method XITDBWriteArrayList [o ^java.io.Writer w] + (.write w "#XITDBWriteArrayList") + (print-method (into [] (common/-read-only o)) w)) + ;; Constructors (defn xwrite-array-list [^WriteCursor write-cursor] diff --git a/src/xitdb/hash_map.clj b/src/xitdb/hash_map.clj index d788839..bd4a629 100644 --- a/src/xitdb/hash_map.clj +++ b/src/xitdb/hash_map.clj @@ -194,6 +194,9 @@ (toString [this] (str "XITDBWriteHashMap"))) +(defmethod print-method XITDBWriteHashMap [o ^java.io.Writer w] + (.write w "#XITDBWriteHashMap") + (print-method (into {} (common/-read-only o)) w)) (defn xwrite-hash-map [^WriteCursor write-cursor] (->XITDBWriteHashMap (WriteHashMap. write-cursor))) diff --git a/src/xitdb/hash_set.clj b/src/xitdb/hash_set.clj index 8dc7e1d..58abf3c 100644 --- a/src/xitdb/hash_set.clj +++ b/src/xitdb/hash_set.clj @@ -154,6 +154,10 @@ (toString [_] (str "XITDBWriteHashSet"))) +(defmethod print-method XITDBWriteHashSet [o ^java.io.Writer w] + (.write w "#XITDBWriteHashSet") + (print-method (into #{} (common/-read-only o)) w)) + ;; Constructor functions (defn xwrite-hash-set [^WriteCursor write-cursor] (->XITDBWriteHashSet (WriteHashSet. write-cursor))) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index b1f0b4d..8666d51 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -231,6 +231,10 @@ (toString [this] (str "XITDBWriteLinkedArrayList"))) +(defmethod print-method XITDBWriteLinkedArrayList [o ^java.io.Writer w] + (.write w "#XITDBWriteLinkedArrayList") + (print-method (into [] (common/-read-only o)) w)) + (extend-protocol common/IMaterialize XITDBLinkedArrayList (-materialize [this] From 84236f0f84d852c6796e7feff3208b0e97ae03bb Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:26:48 -0500 Subject: [PATCH 05/20] add test --- test/xitdb/freeze_test.clj | 123 +++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 test/xitdb/freeze_test.clj diff --git a/test/xitdb/freeze_test.clj b/test/xitdb/freeze_test.clj new file mode 100644 index 0000000..7c790ef --- /dev/null +++ b/test/xitdb/freeze_test.clj @@ -0,0 +1,123 @@ +(ns xitdb.freeze-test + "Tests for the `freeze` function" + (:require + [clojure.test :refer :all] + [xitdb.db :as xdb] + [xitdb.test-utils :as tu])) + +(deftest freeze-array-list-test + (testing "without freeze" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits ["apple" "pear" "grape"]) + moment (assoc moment :food (:fruits moment)) + moment (update moment :food conj "eggs" "rice" "fish")] + moment))) + + (is (= {:fruits ["apple" "pear" "grape" "eggs" "rice" "fish"] + :food ["apple" "pear" "grape" "eggs" "rice" "fish"]} + (xdb/materialize @db))))) + + (testing "with freeze" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits ["apple" "pear" "grape"]) + moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (update moment :food conj "eggs" "rice" "fish")] + moment))) + + (is (= {:fruits ["apple" "pear" "grape"] + :food ["apple" "pear" "grape" "eggs" "rice" "fish"]} + (xdb/materialize @db)))))) + +(deftest freeze-linked-array-list-test + (testing "without freeze" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits '("apple" "pear" "grape")) + moment (assoc moment :food (:fruits moment)) + moment (update moment :food conj "eggs" "rice" "fish")] + moment))) + + (is (= {:fruits ["fish" "rice" "eggs" "apple" "pear" "grape"] + :food ["fish" "rice" "eggs" "apple" "pear" "grape"]} + (xdb/materialize @db))))) + + (testing "with freeze" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits '("apple" "pear" "grape")) + moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (update moment :food conj "eggs" "rice" "fish")] + moment))) + + (is (= {:fruits ["apple" "pear" "grape"] + :food ["fish" "rice" "eggs" "apple" "pear" "grape"]} + (xdb/materialize @db)))))) + +(deftest freeze-hash-map-test + (testing "without freeze" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits {:names ["apple" "pear" "grape"]}) + moment (assoc moment :food (:fruits moment)) + moment (update-in moment [:food :names] conj "eggs" "rice" "fish")] + moment))) + + (is (= {:fruits {:names ["apple" "pear" "grape" "eggs" "rice" "fish"]} + :food {:names ["apple" "pear" "grape" "eggs" "rice" "fish"]}} + (xdb/materialize @db))))) + + (testing "with freeze" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits {:names ["apple" "pear" "grape"]}) + moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (update-in moment [:food :names] conj "eggs" "rice" "fish")] + moment))) + + (is (= {:fruits {:names ["apple" "pear" "grape"]} + :food {:names ["apple" "pear" "grape" "eggs" "rice" "fish"]}} + (xdb/materialize @db)))))) + +(deftest freeze-hash-map-test + (testing "without freeze" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits #{"apple" "pear" "grape"}) + moment (assoc moment :food (:fruits moment)) + moment (update moment :food conj "eggs" "rice" "fish")] + moment))) + + (is (= {:fruits #{"apple" "pear" "grape" "eggs" "rice" "fish"} + :food #{"apple" "pear" "grape" "eggs" "rice" "fish"}} + (xdb/materialize @db))))) + + (testing "with freeze" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits #{"apple" "pear" "grape"}) + moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (update moment :food conj "eggs" "rice" "fish")] + moment))) + + (is (= {:fruits #{"apple" "pear" "grape"} + :food #{"apple" "pear" "grape" "eggs" "rice" "fish"}} + (xdb/materialize @db)))))) + From 687ba5c7a6d36b95614d1f5b83be36b7a4031d2e Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:46:39 -0500 Subject: [PATCH 06/20] update readme --- README.md | 57 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 15b8708..0576b63 100644 --- a/README.md +++ b/README.md @@ -59,27 +59,6 @@ For the programmer, a `xitdb` database is like a Clojure atom. (get-in @db [:users "alice" :age]) ;; => 31 ``` -One important distinction from the Clojure atom is that inside a transaction (eg. a `swap!`), -'change' operations on the received `db` argument are mutating the underlying data structure. - -```clojure -(with-db [db (xdb/xit-db :memory)] - (reset! db {}) - (swap! db (fn [db] - (let [db1 (assoc db :foo :bar)] - (println "db1:" db1) - (println "db:" db))))) -``` -prints -``` -db1: {:foo :bar} -db: {:foo :bar} -``` -As you can see, `(assoc db :foo :bar)` changed the value of `db`, in contrast -to how it works with a Clojure persistent map. This is because, inside `swap!`, -`db` is referencing a WriteCursor, which writes the value to the underlying -ArrayList or HashMap objects inside `xit-db-java`. -The value will actually be commited to the database when the `swap!` function returns. ## Data structures are read lazily from the database @@ -154,6 +133,42 @@ values of the database, by setting the `*return-history?*` binding to `true`. (println "new value:" new-value))) ``` +## Freezing + +One important distinction from the Clojure atom is that inside a transaction (eg. a `swap!`), the data is temporarily mutable. This is exactly like Clojure's transients, and it is a very important optimization. However, this can lead to a surprising behavior: + +```clojure +(swap! db (fn [moment] + (let [moment (assoc moment :fruits ["apple" "pear" "grape"]) + moment (assoc moment :food (:fruits moment)) + moment (update moment :food conj "eggs" "rice" "fish")] + moment))) + +;; => + +{:fruits ["apple" "pear" "grape" "eggs" "rice" "fish"] + :food ["apple" "pear" "grape" "eggs" "rice" "fish"]} + +;; the fruits vector was mutated! +``` + +If you want to prevent data from being mutated within a transaction, you must `freeze` it: + +```clojure +(swap! db (fn [moment] + (let [moment (assoc moment :fruits ["apple" "pear" "grape"]) + moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (update moment :food conj "eggs" "rice" "fish")] + moment))) + +;; => + +{:fruits ["apple" "pear" "grape"] + :food ["apple" "pear" "grape" "eggs" "rice" "fish"]} +``` + +Note that this is not doing an expensive copy of the fruits vector. We are benefitting from structural sharing, just like in-memory Clojure data. The reason we have to `freeze` is because the default is different than Clojure; in Clojure, you must opt-in to temporary mutability by using transients, whereas in xitdb you must opt-out of it. + ### Architecture `xitdb-clj` builds on [xitdb-java](https://github.com/radarroark/xitdb-java) which implements: From 3b76580dc9e43d34948dab501e17d2073134654d Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:20:37 -0500 Subject: [PATCH 07/20] fix test name --- test/xitdb/freeze_test.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/xitdb/freeze_test.clj b/test/xitdb/freeze_test.clj index 7c790ef..42d158b 100644 --- a/test/xitdb/freeze_test.clj +++ b/test/xitdb/freeze_test.clj @@ -92,7 +92,7 @@ :food {:names ["apple" "pear" "grape" "eggs" "rice" "fish"]}} (xdb/materialize @db)))))) -(deftest freeze-hash-map-test +(deftest freeze-hash-set-test (testing "without freeze" (with-open [db (xdb/xit-db :memory)] (reset! db {}) From 473aa18c82f46919f635abc6ef7d1e222047dac3 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:33:55 -0500 Subject: [PATCH 08/20] rename freeze to freeze! --- README.md | 6 +++--- src/xitdb/db.clj | 2 +- test/xitdb/freeze_test.clj | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0576b63..3076e49 100644 --- a/README.md +++ b/README.md @@ -152,12 +152,12 @@ One important distinction from the Clojure atom is that inside a transaction (eg ;; the fruits vector was mutated! ``` -If you want to prevent data from being mutated within a transaction, you must `freeze` it: +If you want to prevent data from being mutated within a transaction, you must `freeze!` it: ```clojure (swap! db (fn [moment] (let [moment (assoc moment :fruits ["apple" "pear" "grape"]) - moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (assoc moment :food (xdb/freeze! (:fruits moment))) moment (update moment :food conj "eggs" "rice" "fish")] moment))) @@ -167,7 +167,7 @@ If you want to prevent data from being mutated within a transaction, you must `f :food ["apple" "pear" "grape" "eggs" "rice" "fish"]} ``` -Note that this is not doing an expensive copy of the fruits vector. We are benefitting from structural sharing, just like in-memory Clojure data. The reason we have to `freeze` is because the default is different than Clojure; in Clojure, you must opt-in to temporary mutability by using transients, whereas in xitdb you must opt-out of it. +Note that this is not doing an expensive copy of the fruits vector. We are benefitting from structural sharing, just like in-memory Clojure data. The reason we have to `freeze!` is because the default is different than Clojure; in Clojure, you must opt-in to temporary mutability by using transients, whereas in xitdb you must opt-out of it. ### Architecture `xitdb-clj` builds on [xitdb-java](https://github.com/radarroark/xitdb-java) which implements: diff --git a/src/xitdb/db.clj b/src/xitdb/db.clj index 7ff84c0..f022c3e 100644 --- a/src/xitdb/db.clj +++ b/src/xitdb/db.clj @@ -231,7 +231,7 @@ :else (throw (IllegalArgumentException. (str "xdb must be an instance of XITDBCursor or XITDBDatabase, got: " (type xdb)))))) -(defn freeze +(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 diff --git a/test/xitdb/freeze_test.clj b/test/xitdb/freeze_test.clj index 42d158b..752424b 100644 --- a/test/xitdb/freeze_test.clj +++ b/test/xitdb/freeze_test.clj @@ -1,5 +1,5 @@ (ns xitdb.freeze-test - "Tests for the `freeze` function" + "Tests for the `freeze!` function" (:require [clojure.test :refer :all] [xitdb.db :as xdb] @@ -26,7 +26,7 @@ (swap! db (fn [moment] (let [moment (assoc moment :fruits ["apple" "pear" "grape"]) - moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (assoc moment :food (xdb/freeze! (:fruits moment))) moment (update moment :food conj "eggs" "rice" "fish")] moment))) @@ -55,7 +55,7 @@ (swap! db (fn [moment] (let [moment (assoc moment :fruits '("apple" "pear" "grape")) - moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (assoc moment :food (xdb/freeze! (:fruits moment))) moment (update moment :food conj "eggs" "rice" "fish")] moment))) @@ -84,7 +84,7 @@ (swap! db (fn [moment] (let [moment (assoc moment :fruits {:names ["apple" "pear" "grape"]}) - moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (assoc moment :food (xdb/freeze! (:fruits moment))) moment (update-in moment [:food :names] conj "eggs" "rice" "fish")] moment))) @@ -113,7 +113,7 @@ (swap! db (fn [moment] (let [moment (assoc moment :fruits #{"apple" "pear" "grape"}) - moment (assoc moment :food (xdb/freeze (:fruits moment))) + moment (assoc moment :food (xdb/freeze! (:fruits moment))) moment (update moment :food conj "eggs" "rice" "fish")] moment))) From ab3747223bda0d4eeeac16fae8a915f6681b34cf Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:04:44 -0500 Subject: [PATCH 09/20] do claude's suggestions --- src/xitdb/db.clj | 3 +++ src/xitdb/linked_list.clj | 57 +++++++++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/xitdb/db.clj b/src/xitdb/db.clj index f022c3e..d06f633 100644 --- a/src/xitdb/db.clj +++ b/src/xitdb/db.clj @@ -237,6 +237,9 @@ 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)) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index 8666d51..665ba69 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -30,7 +30,25 @@ (= (count this) (count other)) (every? identity (map = this other)))) - clojure.lang.Sequential ;; Mark as sequential + clojure.lang.Sequential ;; Add this to mark as sequential + + clojure.lang.Associative + (assoc [this k v] + (assoc (common/-materialize-shallow this) k v)) + + (containsKey [this k] + (and (integer? k) (>= k 0) (< k (.count rlal)))) + + (entryAt [this k] + (when (.containsKey this k) + (clojure.lang.MapEntry. k (.valAt this k)))) + + clojure.lang.IPersistentVector + (assocN [this i val] + (assoc (common/-materialize-shallow this) i val)) + + (length [this] + (.count rlal)) clojure.lang.Indexed (nth [_ i] @@ -43,13 +61,6 @@ (common/-read-from-cursor cursor) not-found))) - clojure.lang.IPersistentVector - (assocN [this i val] - (assoc (common/-materialize-shallow this) i val)) - - (length [this] - (.count rlal)) - clojure.lang.ILookup (valAt [this k] (if (number? k) @@ -145,6 +156,14 @@ (range (count this)))) false)) + clojure.lang.IPersistentVector + (assocN [this i val] + (operations/linked-array-list-assoc-value! wlal i (common/unwrap val)) + this) + + (length [this] + (.count wlal)) + clojure.lang.Indexed (nth [this i] (.nth this i nil)) @@ -154,14 +173,6 @@ (common/-read-from-cursor (.putCursor wlal i)) not-found)) - clojure.lang.IPersistentVector - (assocN [this i val] - (operations/linked-array-list-assoc-value! wlal i (common/unwrap val)) - this) - - (length [this] - (.count wlal)) - clojure.lang.Associative (assoc [this k v] (when-not (integer? k) @@ -169,6 +180,13 @@ (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)))) + clojure.lang.ILookup (valAt [this k] (.valAt this k nil)) @@ -236,11 +254,10 @@ (print-method (into [] (common/-read-only o)) w)) (extend-protocol common/IMaterialize - XITDBLinkedArrayList + XITDBWriteLinkedArrayList (-materialize [this] - (apply list - (reduce (fn [a v] - (conj a (common/materialize v))) [] (seq this))))) + (reduce (fn [a v] + (conj a (common/materialize v))) [] (seq this)))) ;; Constructors From 09a34faf0e3cb9807f657c709d770bb22dd9c23d Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:09:47 -0500 Subject: [PATCH 10/20] implement IMaterialize inside of the deftypes --- src/xitdb/array_list.clj | 27 +++++++++++++++------------ src/xitdb/hash_map.clj | 27 +++++++++++++++------------ src/xitdb/hash_set.clj | 22 ++++++++++++---------- src/xitdb/linked_list.clj | 33 +++++++++++++++------------------ 4 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/xitdb/array_list.clj b/src/xitdb/array_list.clj index aa19a9b..b73a037 100644 --- a/src/xitdb/array_list.clj +++ b/src/xitdb/array_list.clj @@ -125,6 +125,16 @@ (-unwrap [this] ral) + common/IMaterialize + (-materialize [this] + (reduce (fn [a v] + (conj a (common/materialize v))) [] (seq this))) + + common/IMaterializeShallow + (-materialize-shallow [this] + (reduce (fn [a v] + (conj a v)) [] (seq this))) + Object (toString [this] (pr-str (into [] this)))) @@ -133,18 +143,6 @@ (.write w "#XITDBArrayList") (print-method (into [] o) w)) -(extend-protocol common/IMaterialize - XITDBArrayList - (-materialize [this] - (reduce (fn [a v] - (conj a (common/materialize v))) [] (seq this)))) - -(extend-protocol common/IMaterializeShallow - XITDBArrayList - (-materialize-shallow [this] - (reduce (fn [a v] - (conj a v)) [] (seq this)))) - ;;----------------------------------------------- (deftype XITDBWriteArrayList [^WriteArrayList wal] @@ -248,6 +246,11 @@ (-unwrap [this] wal) + common/IMaterialize + (-materialize [this] + (reduce (fn [a v] + (conj a (common/materialize v))) [] (seq this))) + common/IReadOnly (-read-only [this] (XITDBArrayList. wal)) diff --git a/src/xitdb/hash_map.clj b/src/xitdb/hash_map.clj index bd4a629..bd3fab4 100644 --- a/src/xitdb/hash_map.clj +++ b/src/xitdb/hash_map.clj @@ -89,6 +89,16 @@ (-unwrap [this] rhm) + common/IMaterialize + (-materialize [this] + (reduce (fn [m [k v]] + (assoc m k (common/materialize v))) {} (seq this))) + + common/IMaterializeShallow + (-materialize-shallow [this] + (reduce (fn [m [k v]] + (assoc m k v)) {} (seq this))) + Object (toString [this] (str (into {} this)))) @@ -97,18 +107,6 @@ (.write w "#XITDBHashMap") (print-method (into {} o) w)) -(extend-protocol common/IMaterialize - XITDBHashMap - (-materialize [this] - (reduce (fn [m [k v]] - (assoc m k (common/materialize v))) {} (seq this)))) - -(extend-protocol common/IMaterializeShallow - XITDBHashMap - (-materialize-shallow [this] - (reduce (fn [m [k v]] - (assoc m k v)) {} (seq this)))) - ;--------------------------------------------------- @@ -186,6 +184,11 @@ (-unwrap [this] whm) + common/IMaterialize + (-materialize [this] + (reduce (fn [m [k v]] + (assoc m k (common/materialize v))) {} (seq this))) + common/IReadOnly (-read-only [this] (XITDBHashMap. whm)) diff --git a/src/xitdb/hash_set.clj b/src/xitdb/hash_set.clj index 58abf3c..e685810 100644 --- a/src/xitdb/hash_set.clj +++ b/src/xitdb/hash_set.clj @@ -76,6 +76,14 @@ (-unwrap [_] rhs) + common/IMaterialize + (-materialize [this] + (into #{} (map common/materialize (seq this)))) + + common/IMaterializeShallow + (-materialize-shallow [this] + (into #{} (seq this))) + Object (toString [this] (str (into #{} this)))) @@ -84,16 +92,6 @@ (.write w "#XITDBHashSet") (print-method (into #{} o) w)) -(extend-protocol common/IMaterialize - XITDBHashSet - (-materialize [this] - (into #{} (map common/materialize (seq this))))) - -(extend-protocol common/IMaterializeShallow - XITDBHashSet - (-materialize-shallow [this] - (into #{} (seq this)))) - ;; Writable version of the set (deftype XITDBWriteHashSet [^WriteHashSet whs] clojure.lang.IPersistentSet @@ -146,6 +144,10 @@ (-unwrap [_] whs) + common/IMaterialize + (-materialize [this] + (into #{} (map common/materialize (seq this)))) + common/IReadOnly (-read-only [this] (XITDBHashSet. whs)) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index 665ba69..826b626 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -112,6 +112,16 @@ (-unwrap [_] rlal) + common/IMaterialize + (-materialize [this] + (reduce (fn [a v] + (conj a (common/materialize v))) [] (seq this))) + + common/IMaterializeShallow + (-materialize-shallow [this] + (reduce (fn [a v] + (conj a v)) [] (seq this))) + Object (toString [this] (pr-str (into [] this)))) @@ -120,18 +130,6 @@ (.write w "#XITDBLinkedArrayList") (print-method (into [] o) w)) -(extend-protocol common/IMaterialize - XITDBLinkedArrayList - (-materialize [this] - (reduce (fn [a v] - (conj a (common/materialize v))) [] (seq this)))) - -(extend-protocol common/IMaterializeShallow - XITDBLinkedArrayList - (-materialize-shallow [this] - (reduce (fn [a v] - (conj a v)) [] (seq this)))) - ;; ----------------------------------------------------------------- (deftype XITDBWriteLinkedArrayList [^WriteLinkedArrayList wlal] @@ -241,6 +239,11 @@ (-unwrap [this] wlal) + common/IMaterialize + (-materialize [this] + (reduce (fn [a v] + (conj a (common/materialize v))) [] (seq this))) + common/IReadOnly (-read-only [this] (XITDBLinkedArrayList. wlal)) @@ -253,12 +256,6 @@ (.write w "#XITDBWriteLinkedArrayList") (print-method (into [] (common/-read-only o)) w)) -(extend-protocol common/IMaterialize - XITDBWriteLinkedArrayList - (-materialize [this] - (reduce (fn [a v] - (conj a (common/materialize v))) [] (seq this)))) - ;; Constructors (defn xlinked-list [^ReadCursor cursor] From 99dfbe10088bb1ed0aab51945a2f7303d5b209a4 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:17:32 -0500 Subject: [PATCH 11/20] mark writeable AL and LAL as Sequential --- src/xitdb/array_list.clj | 4 +++- src/xitdb/linked_list.clj | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/xitdb/array_list.clj b/src/xitdb/array_list.clj index b73a037..7c268b7 100644 --- a/src/xitdb/array_list.clj +++ b/src/xitdb/array_list.clj @@ -29,7 +29,7 @@ (= (count this) (count other)) (every? identity (map = this other)))) - clojure.lang.Sequential ;; Add this to mark as sequential + clojure.lang.Sequential clojure.lang.Associative (assoc [this k v] @@ -182,6 +182,8 @@ (length [this] (.count wal)) + clojure.lang.Sequential + clojure.lang.Associative (assoc [this k v] (when-not (integer? k) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index 826b626..6943ec5 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -30,7 +30,7 @@ (= (count this) (count other)) (every? identity (map = this other)))) - clojure.lang.Sequential ;; Add this to mark as sequential + clojure.lang.Sequential clojure.lang.Associative (assoc [this k v] @@ -171,6 +171,8 @@ (common/-read-from-cursor (.putCursor wlal i)) not-found)) + clojure.lang.Sequential + clojure.lang.Associative (assoc [this k v] (when-not (integer? k) From 8cfbe1056ca0638d92ef6e9f1214c317996dd7e6 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:46:47 -0500 Subject: [PATCH 12/20] no need to implement IMaterialize on writeable types --- src/xitdb/array_list.clj | 5 ----- src/xitdb/hash_map.clj | 5 ----- src/xitdb/hash_set.clj | 4 ---- src/xitdb/linked_list.clj | 5 ----- 4 files changed, 19 deletions(-) diff --git a/src/xitdb/array_list.clj b/src/xitdb/array_list.clj index 7c268b7..c014e2a 100644 --- a/src/xitdb/array_list.clj +++ b/src/xitdb/array_list.clj @@ -248,11 +248,6 @@ (-unwrap [this] wal) - common/IMaterialize - (-materialize [this] - (reduce (fn [a v] - (conj a (common/materialize v))) [] (seq this))) - common/IReadOnly (-read-only [this] (XITDBArrayList. wal)) diff --git a/src/xitdb/hash_map.clj b/src/xitdb/hash_map.clj index bd3fab4..db90b98 100644 --- a/src/xitdb/hash_map.clj +++ b/src/xitdb/hash_map.clj @@ -184,11 +184,6 @@ (-unwrap [this] whm) - common/IMaterialize - (-materialize [this] - (reduce (fn [m [k v]] - (assoc m k (common/materialize v))) {} (seq this))) - common/IReadOnly (-read-only [this] (XITDBHashMap. whm)) diff --git a/src/xitdb/hash_set.clj b/src/xitdb/hash_set.clj index e685810..f0a453e 100644 --- a/src/xitdb/hash_set.clj +++ b/src/xitdb/hash_set.clj @@ -144,10 +144,6 @@ (-unwrap [_] whs) - common/IMaterialize - (-materialize [this] - (into #{} (map common/materialize (seq this)))) - common/IReadOnly (-read-only [this] (XITDBHashSet. whs)) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index 6943ec5..46990c0 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -241,11 +241,6 @@ (-unwrap [this] wlal) - common/IMaterialize - (-materialize [this] - (reduce (fn [a v] - (conj a (common/materialize v))) [] (seq this))) - common/IReadOnly (-read-only [this] (XITDBLinkedArrayList. wlal)) From 7a3409631553f6cde900dc80ccea8c526c7977ad Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 19:53:29 -0500 Subject: [PATCH 13/20] implement dissoc for LAL and add tests --- src/xitdb/linked_list.clj | 9 +++++++ src/xitdb/util/conversion.clj | 32 +++++++--------------- src/xitdb/util/operations.clj | 9 ++++++- test/xitdb/linked_list_test.clj | 48 +++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 test/xitdb/linked_list_test.clj diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index 46990c0..ca6e470 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -50,6 +50,10 @@ (length [this] (.count rlal)) + clojure.lang.IPersistentMap + (without [this k] + (dissoc (common/-materialize-shallow this) k)) + clojure.lang.Indexed (nth [_ i] (let [cursor (.getCursor rlal (long i))] @@ -187,6 +191,11 @@ (when (.containsKey this k) (clojure.lang.MapEntry. k (.valAt this k)))) + clojure.lang.IPersistentMap + (without [this key] + (operations/linked-array-list-remove-value! wlal key) + this) + clojure.lang.ILookup (valAt [this k] (.valAt this k nil)) diff --git a/src/xitdb/util/conversion.clj b/src/xitdb/util/conversion.clj index b53f37f..8934686 100644 --- a/src/xitdb/util/conversion.clj +++ b/src/xitdb/util/conversion.clj @@ -4,7 +4,7 @@ (:import [io.github.radarroark.xitdb Database Database$Bytes Database$Float Database$Int - ReadArrayList ReadCountedHashMap ReadCountedHashSet ReadCursor ReadHashMap + ReadArrayList ReadLinkedArrayList ReadCountedHashMap ReadCountedHashSet ReadCursor ReadHashMap ReadHashSet Slot Tag WriteArrayList WriteCountedHashMap WriteCountedHashSet WriteCursor WriteHashMap WriteHashSet WriteLinkedArrayList] [java.io OutputStream OutputStreamWriter] @@ -146,39 +146,27 @@ (validation/lazy-seq? v) (throw (IllegalArgumentException. "Lazy sequences can be infinite and not allowed!")) - (instance? WriteArrayList v) - (-> ^WriteArrayList v .cursor .slot) + ;; we only need to check for the read-only data structures, + ;; because the writeable data structures inherit from them! - (instance? WriteLinkedArrayList v) - (-> ^WriteLinkedArrayList v .cursor .slot) + (instance? ReadArrayList v) + (-> ^ReadArrayList v .cursor .slot) - (instance? WriteHashMap v) - (-> ^WriteHashMap v .cursor .slot) + (instance? ReadLinkedArrayList v) + (-> ^ReadLinkedArrayList v .cursor .slot) (instance? ReadHashMap v) (-> ^ReadHashMap v .cursor .slot) - (instance? ReadCountedHashMap v) - (-> ^ReadCountedHashMap v .cursor .slot) - - (instance? WriteCountedHashMap v) - (-> ^WriteCountedHashMap v .cursor .slot) - - (instance? ReadArrayList v) - (-> ^ReadArrayList v .cursor .slot) - (instance? ReadHashSet v) (-> ^ReadHashSet v .cursor .slot) + (instance? ReadCountedHashMap v) + (-> ^ReadCountedHashMap v .cursor .slot) + (instance? ReadCountedHashSet v) (-> ^ReadCountedHashSet v .cursor .slot) - (instance? WriteHashSet v) - (-> ^WriteHashSet v .cursor .slot) - - (instance? WriteCountedHashSet v) - (-> ^WriteCountedHashSet v .cursor .slot) - (map? v) (do (.write cursor nil) diff --git a/src/xitdb/util/operations.clj b/src/xitdb/util/operations.clj index 729a7d7..1c6bbfc 100644 --- a/src/xitdb/util/operations.clj +++ b/src/xitdb/util/operations.clj @@ -84,8 +84,15 @@ "Inserts a value at position pos in a WriteLinkedArrayList. Converts the value to an appropriate XitDB representation using v->slot!." [^WriteLinkedArrayList wlal pos v] + (let [cursor (.insertCursor wlal pos)] + (.write cursor (conversion/v->slot! cursor v))) + wlal) + +(defn linked-array-list-remove-value! + "Removes a value at position pos in a WriteLinkedArrayList." + [^WriteLinkedArrayList wlal pos] (let [cursor (-> wlal .cursor)] - (.insert wlal pos (conversion/v->slot! cursor v))) + (.remove wlal pos)) wlal) (defn linked-array-list-pop! diff --git a/test/xitdb/linked_list_test.clj b/test/xitdb/linked_list_test.clj new file mode 100644 index 0000000..7e39266 --- /dev/null +++ b/test/xitdb/linked_list_test.clj @@ -0,0 +1,48 @@ +(ns xitdb.linked-list-test + (:require + [clojure.test :refer :all] + [xitdb.db :as xdb])) + +(deftest LinkedListTest + (testing "Linked list works" + (with-open [db (xdb/xit-db :memory)] + (reset! db '(1 2 3 4 5)) + (swap! db conj 6) + (swap! db assoc (count @db) 7) + + (is (= [6 1 2 3 4 5 7] @db)) + (is (= 7 (count @db))))) + + (testing "Basic operations" + (with-open [db (xdb/xit-db :memory)] + (testing "Creation" + (reset! db '(1 2 3 4 5)) + (is (= '(1 2 3 4 5) @db))) + + (testing "Membership" + (is (= true (contains? @db 3))) + (is (= 4 (get @db 3)))) + + (testing "Adding/Removing" + (swap! db conj 7) + (is (= '(7 1 2 3 4 5) @db)) + (swap! db dissoc 0) + (is (= '(1 2 3 4 5) @db))) + + (testing "Emptying" + (swap! db empty) + (is (= '() @db)) + (is (= 0 (count @db))))))) + +(deftest DataTypes + (testing "Supports nested types" + (with-open [db (xdb/xit-db :memory)] + (reset! db '(1 {:foo :bar} [1 2 3 4] (7 89))) + (is (= '(1 {:foo :bar} [1 2 3 4] (7 89)) + (xdb/materialize @db))) + + (testing "Adding a list to the list" + (swap! db conj '(1 [3 4] {:new :map})) + (is (= '((1 [3 4] {:new :map}) 1 {:foo :bar} [1 2 3 4] (7 89)) + (xdb/materialize @db))))))) + From 57aced9434769b596aa4e17c24b34e24ada94be5 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:17:44 -0500 Subject: [PATCH 14/20] materialize LAL as a list, not a vector --- src/xitdb/linked_list.clj | 20 +++++++++----------- test/xitdb/data_types_test.clj | 4 ++-- test/xitdb/linked_list_test.clj | 4 ++++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index ca6e470..440acbe 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -20,7 +20,7 @@ (.count rlal)) (cons [this o] - (. clojure.lang.RT (conj (common/-materialize-shallow this) o))) + (vec (. clojure.lang.RT (conj (common/-materialize-shallow this) o)))) (empty [this] '()) @@ -34,7 +34,7 @@ clojure.lang.Associative (assoc [this k v] - (assoc (common/-materialize-shallow this) k v)) + (assoc (vec (common/-materialize-shallow this)) k v)) (containsKey [this k] (and (integer? k) (>= k 0) (< k (.count rlal)))) @@ -45,15 +45,11 @@ clojure.lang.IPersistentVector (assocN [this i val] - (assoc (common/-materialize-shallow this) i val)) + (assoc (vec (common/-materialize-shallow this)) i val)) (length [this] (.count rlal)) - clojure.lang.IPersistentMap - (without [this k] - (dissoc (common/-materialize-shallow this) k)) - clojure.lang.Indexed (nth [_ i] (let [cursor (.getCursor rlal (long i))] @@ -118,13 +114,15 @@ common/IMaterialize (-materialize [this] - (reduce (fn [a v] - (conj a (common/materialize v))) [] (seq this))) + (apply list + (reduce (fn [a v] + (conj a (common/materialize v))) [] (seq this)))) common/IMaterializeShallow (-materialize-shallow [this] - (reduce (fn [a v] - (conj a v)) [] (seq this))) + (apply list + (reduce (fn [a v] + (conj a v)) [] (seq this)))) Object (toString [this] diff --git a/test/xitdb/data_types_test.clj b/test/xitdb/data_types_test.clj index ab56756..8d2df88 100644 --- a/test/xitdb/data_types_test.clj +++ b/test/xitdb/data_types_test.clj @@ -132,8 +132,8 @@ (let [db-val @db shallow (common/-materialize-shallow db-val)] - ;; Should be a vector (shallow materialization converts to vector) - (is (vector? shallow)) + ;; Should be a list + (is (list? shallow)) ;; Values are preserved (is (= [1 2 3] shallow)))))) diff --git a/test/xitdb/linked_list_test.clj b/test/xitdb/linked_list_test.clj index 7e39266..6044e7a 100644 --- a/test/xitdb/linked_list_test.clj +++ b/test/xitdb/linked_list_test.clj @@ -29,6 +29,10 @@ (swap! db dissoc 0) (is (= '(1 2 3 4 5) @db))) + (testing "Adding to read-only list" + (is (= [7 1 2 3 4 5] + (conj @db 7)))) + (testing "Emptying" (swap! db empty) (is (= '() @db)) From 9af616021c05d30166859734788f4b80f558f8ac Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:21:36 -0500 Subject: [PATCH 15/20] no need to implement IPersistentVector on linked list --- src/xitdb/linked_list.clj | 9 +-------- test/xitdb/linked_list_test.clj | 8 ++++++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index 440acbe..3ae01e4 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -20,7 +20,7 @@ (.count rlal)) (cons [this o] - (vec (. clojure.lang.RT (conj (common/-materialize-shallow this) o)))) + (. clojure.lang.RT (conj (common/-materialize-shallow this) o))) (empty [this] '()) @@ -43,13 +43,6 @@ (when (.containsKey this k) (clojure.lang.MapEntry. k (.valAt this k)))) - clojure.lang.IPersistentVector - (assocN [this i val] - (assoc (vec (common/-materialize-shallow this)) i val)) - - (length [this] - (.count rlal)) - clojure.lang.Indexed (nth [_ i] (let [cursor (.getCursor rlal (long i))] diff --git a/test/xitdb/linked_list_test.clj b/test/xitdb/linked_list_test.clj index 6044e7a..1497c23 100644 --- a/test/xitdb/linked_list_test.clj +++ b/test/xitdb/linked_list_test.clj @@ -30,8 +30,12 @@ (is (= '(1 2 3 4 5) @db))) (testing "Adding to read-only list" - (is (= [7 1 2 3 4 5] - (conj @db 7)))) + (is (= [6 1 2 3 4 5] + (conj @db 6))) + (is (= [6 2 3 4 5] + (assoc @db 0 6))) + (is (= [1 2 3 4 5 6] + (assoc @db 5 6)))) (testing "Emptying" (swap! db empty) From ad71119a39460ba00d07dbdbc12af2673401fc3f Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:31:09 -0500 Subject: [PATCH 16/20] don't implement Associative on read-only LAL --- src/xitdb/linked_list.clj | 11 ----------- test/xitdb/linked_list_test.clj | 7 +------ 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index 3ae01e4..e0cba69 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -32,17 +32,6 @@ clojure.lang.Sequential - clojure.lang.Associative - (assoc [this k v] - (assoc (vec (common/-materialize-shallow this)) k v)) - - (containsKey [this k] - (and (integer? k) (>= k 0) (< k (.count rlal)))) - - (entryAt [this k] - (when (.containsKey this k) - (clojure.lang.MapEntry. k (.valAt this k)))) - clojure.lang.Indexed (nth [_ i] (let [cursor (.getCursor rlal (long i))] diff --git a/test/xitdb/linked_list_test.clj b/test/xitdb/linked_list_test.clj index 1497c23..4153991 100644 --- a/test/xitdb/linked_list_test.clj +++ b/test/xitdb/linked_list_test.clj @@ -20,7 +20,6 @@ (is (= '(1 2 3 4 5) @db))) (testing "Membership" - (is (= true (contains? @db 3))) (is (= 4 (get @db 3)))) (testing "Adding/Removing" @@ -31,11 +30,7 @@ (testing "Adding to read-only list" (is (= [6 1 2 3 4 5] - (conj @db 6))) - (is (= [6 2 3 4 5] - (assoc @db 0 6))) - (is (= [1 2 3 4 5 6] - (assoc @db 5 6)))) + (conj @db 6)))) (testing "Emptying" (swap! db empty) From abdb7cd718af00273d1e7f084df97c72fe21d340 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:57:38 -0500 Subject: [PATCH 17/20] check for IReadOnly instead of IUnwrap --- src/xitdb/db.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xitdb/db.clj b/src/xitdb/db.clj index d06f633..38c0622 100644 --- a/src/xitdb/db.clj +++ b/src/xitdb/db.clj @@ -237,7 +237,7 @@ outside of a transaction. Returns a read-only version of the given writeable data structure." [x] - (when-not (satisfies? common/IUnwrap x) + (when-not (satisfies? common/IReadOnly x) (throw (IllegalArgumentException. (str "freeze! requires a writeable XITDB data structure, got: " (type x))))) (-> x common/-unwrap .cursor .db .freeze) From 36b99d37b9e25c695651be9b85a44244fb5c6850 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:37:10 -0500 Subject: [PATCH 18/20] add more to freeze tests --- test/xitdb/freeze_test.clj | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/test/xitdb/freeze_test.clj b/test/xitdb/freeze_test.clj index 752424b..075b3e0 100644 --- a/test/xitdb/freeze_test.clj +++ b/test/xitdb/freeze_test.clj @@ -30,6 +30,20 @@ moment (update moment :food conj "eggs" "rice" "fish")] moment))) + (is (= {:fruits ["apple" "pear" "grape"] + :food ["apple" "pear" "grape" "eggs" "rice" "fish"]} + (xdb/materialize @db))))) + + (testing "with freeze and modifying return value" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits ["apple" "pear" "grape"]) + moment (assoc moment :food (conj (xdb/freeze! (:fruits moment)) + "eggs" "rice" "fish"))] + moment))) + (is (= {:fruits ["apple" "pear" "grape"] :food ["apple" "pear" "grape" "eggs" "rice" "fish"]} (xdb/materialize @db)))))) @@ -59,6 +73,20 @@ moment (update moment :food conj "eggs" "rice" "fish")] moment))) + (is (= {:fruits ["apple" "pear" "grape"] + :food ["fish" "rice" "eggs" "apple" "pear" "grape"]} + (xdb/materialize @db))))) + + (testing "with freeze and modifying return value" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits '("apple" "pear" "grape")) + moment (assoc moment :food (conj (xdb/freeze! (:fruits moment)) + "eggs" "rice" "fish"))] + moment))) + (is (= {:fruits ["apple" "pear" "grape"] :food ["fish" "rice" "eggs" "apple" "pear" "grape"]} (xdb/materialize @db)))))) @@ -88,6 +116,21 @@ moment (update-in moment [:food :names] conj "eggs" "rice" "fish")] moment))) + (is (= {:fruits {:names ["apple" "pear" "grape"]} + :food {:names ["apple" "pear" "grape" "eggs" "rice" "fish"]}} + (xdb/materialize @db))))) + + (testing "with freeze and modifying return value" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits {:names ["apple" "pear" "grape"]}) + moment (assoc moment :food (update (xdb/freeze! (:fruits moment)) + :names conj + "eggs" "rice" "fish"))] + moment))) + (is (= {:fruits {:names ["apple" "pear" "grape"]} :food {:names ["apple" "pear" "grape" "eggs" "rice" "fish"]}} (xdb/materialize @db)))))) @@ -117,6 +160,20 @@ moment (update moment :food conj "eggs" "rice" "fish")] moment))) + (is (= {:fruits #{"apple" "pear" "grape"} + :food #{"apple" "pear" "grape" "eggs" "rice" "fish"}} + (xdb/materialize @db))))) + + (testing "with freeze and modifying return value" + (with-open [db (xdb/xit-db :memory)] + (reset! db {}) + + (swap! db (fn [moment] + (let [moment (assoc moment :fruits #{"apple" "pear" "grape"}) + moment (assoc moment :food (conj (xdb/freeze! (:fruits moment)) + "eggs" "rice" "fish"))] + moment))) + (is (= {:fruits #{"apple" "pear" "grape"} :food #{"apple" "pear" "grape" "eggs" "rice" "fish"}} (xdb/materialize @db)))))) From 7d89fc3ab4f26d04f399872203bca0f0c00f36d4 Mon Sep 17 00:00:00 2001 From: radar roark <122068506+radarroark@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:57:25 -0500 Subject: [PATCH 19/20] small readme fixes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3076e49..15b265b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ It is a Clojure interface for [xitdb-java](https://github.com/radarroark/xitdb-j - All heavy lifting done by the bare-to-the-jvm java library. - Database files can be used from other languages, via [xitdb Java library](https://github.com/radarroark/xitdb-java) or the [xitdb Zig library](https://github.com/radarroark/xitdb) -## Quickstart +## Quick Start Add the dependency to your project, start a REPL. @@ -84,7 +84,7 @@ using Clojure functions. Use `materialize` to convert a nested `XITDB` data structure to a native Clojure data structure: ```clojure -(materialize (get-in @db [:users "alice"])) ;; => {:name "Alice" :age 31} +(xdb/materialize (get-in @db [:users "alice"])) ;; => {:name "Alice" :age 31} ``` ## No query language From 51ca43c80b0518495a716cd807f77e92e3cec93c Mon Sep 17 00:00:00 2001 From: Florin Braghis Date: Fri, 16 Jan 2026 09:43:04 +0100 Subject: [PATCH 20/20] Fix docstring in wrong position and unused binding in `linked-array-list-remove-value` --- src/xitdb/linked_list.clj | 2 +- src/xitdb/util/operations.clj | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/xitdb/linked_list.clj b/src/xitdb/linked_list.clj index e0cba69..163335c 100644 --- a/src/xitdb/linked_list.clj +++ b/src/xitdb/linked_list.clj @@ -7,8 +7,8 @@ [io.github.radarroark.xitdb ReadCursor ReadLinkedArrayList WriteCursor WriteLinkedArrayList])) (defn array-seq - [^ReadLinkedArrayList rlal] "The cursors used must implement the IReadFromCursor protocol." + [^ReadLinkedArrayList rlal] (operations/linked-array-seq rlal #(common/-read-from-cursor %))) (deftype XITDBLinkedArrayList [^ReadLinkedArrayList rlal] diff --git a/src/xitdb/util/operations.clj b/src/xitdb/util/operations.clj index 1c6bbfc..9b11e74 100644 --- a/src/xitdb/util/operations.clj +++ b/src/xitdb/util/operations.clj @@ -91,8 +91,7 @@ (defn linked-array-list-remove-value! "Removes a value at position pos in a WriteLinkedArrayList." [^WriteLinkedArrayList wlal pos] - (let [cursor (-> wlal .cursor)] - (.remove wlal pos)) + (.remove wlal pos) wlal) (defn linked-array-list-pop!