Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 38 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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

Expand All @@ -105,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
Expand Down Expand Up @@ -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:

Expand Down
36 changes: 22 additions & 14 deletions src/xitdb/array_list.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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]
[])
Expand All @@ -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]
Expand Down Expand Up @@ -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))))
Expand All @@ -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]
Expand Down Expand Up @@ -184,6 +182,8 @@
(length [this]
(.count wal))

clojure.lang.Sequential

clojure.lang.Associative
(assoc [this k v]
(when-not (integer? k)
Expand Down Expand Up @@ -248,10 +248,18 @@
(-unwrap [this]
wal)

common/IReadOnly
(-read-only [this]
(XITDBArrayList. wal))

Object
(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]
Expand Down
2 changes: 2 additions & 0 deletions src/xitdb/common.clj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
(defprotocol IUnwrap
(-unwrap [this]))

(defprotocol IReadOnly
(-read-only [this]))

(defn materialize [v]
(cond
Expand Down
11 changes: 11 additions & 0 deletions src/xitdb/db.clj
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,15 @@
: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]
(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)
(common/-read-only x))

29 changes: 17 additions & 12 deletions src/xitdb/hash_map.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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))))
Expand All @@ -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))))

;---------------------------------------------------


Expand Down Expand Up @@ -186,10 +184,17 @@
(-unwrap [this]
whm)

common/IReadOnly
(-read-only [this]
(XITDBHashMap. whm))

Object
(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)))
Expand Down
28 changes: 17 additions & 11 deletions src/xitdb/hash_set.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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]
#{})
Expand Down Expand Up @@ -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))))
Expand All @@ -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
Expand Down Expand Up @@ -146,10 +144,18 @@
(-unwrap [_]
whs)

common/IReadOnly
(-read-only [this]
(XITDBHashSet. whs))

Object
(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)))
Expand Down
Loading