Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pathwise.clj diff
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## 1.5.0 (2025-06-20)

* Allow unsorted maps and sets to be used as keys in paths using codes `0xa0` `0xa1`.
* Reserve all codes `0xa2` - `0xaf` for future use.

Sorted maps and sets remain unsupported in paths. This is for several reasons including that the sorting criteria cannot be reliably encoded.

*Note: if you have user-defined encoding types using codes between `0xa0` and `0xaf` a warning will be printed but as long as you do not use the new supported types (e.g. maps & sets) in paths your code should continue to work.*

## 1.4.2 & 1.4.3 (2025-06-08)

* bugfix: remove clj-time from project.clj
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Codax is an idiomatic transactional embedded database for clojure. A codax datab

[![Clojars Project](http://clojars.org/codax/latest-version.svg)](http://clojars.org/codax)

Version 1.5.0 allows unsorted sets and maps to be used as [path](#paths) keys. It also reserves pathwise encoding codes `0xa0` - `0xaf`.

Version 1.4.1 removes built-in support for clj-time. If you have an older database using clj-time see the [changelog](CHANGELOG.md)

Version 1.4.0 implements [upgradable transactions](doc/upgradable-transactions.md). (using `with-upgradable-transaction` macro) and **fixes an RCE vulnerability**.
Expand Down Expand Up @@ -102,6 +104,9 @@ A `path` is a vector of keys similar to the `[k & ks]` used in function like `as
- false
- nil
- java.time.Instant
- unsorted maps
- unsorted sets

- the path can only target nested maps, and **cannot be used to descend into other data structures (e.g. vectors)**.
- you can get the empty path (e.g. `(get-at db [])` returns the full database) but you cannot modify it (e.g. `(assoc-at [] :foo)` throws an error)

Expand Down
3 changes: 2 additions & 1 deletion doc/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ It takes the following parameters:

- `hex-code` - a number used for type identification in the database
- `0x0` is used internally and off limits
- built-in codes which should **not be overwritten** are: `0x10` , `0x20`, `0x21`, `0x24`, `0x25`, `0x30`, `0x31`, `0x32`, `0x68`, `0x69`, `0x70`, `0xa0`. Attempts to overwrite them (or generally change encodings) should print a warning.
- built-in codes which should **not be overwritten** are: `0x10` , `0x20`, `0x21`, `0x24`, `0x25`, `0x30`, `0x31`, `0x32`, `0x68`, `0x69`, `0x70`, `0xa0`. Attempts to overwrite them (or generally change encodings) prints a warning.
- `0xa0` - `0xaf` are reserved for future use as of v1.5.0
- `types` - the types to be encoded (generally there should only be one)
- `encoder` - a function which takes elements of the types specified in `types` and **returns a string representation**
- `decoder` - a function which takes the string reprensentations generated by the encoder and reconstructs a value of the appropriate type
Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject codax "1.4.3"
(defproject codax "1.5.0"
:description "Codax is an idiomatic transactional embedded database for clojure"
:url "https://github.com/dscarpetti/codax"
:license {:name "Eclipse Public License"
Expand Down
Binary file modified src/codax/pathwise.clj
Binary file not shown.
73 changes: 66 additions & 7 deletions test/codax/pathwise_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@

;;;; encoding decoding errors

(deftest no-encoder-for-element
#_(deftest no-encoder-for-element
(try
(do
(encode #{})
Expand All @@ -156,7 +156,7 @@
(is (= type clojure.lang.PersistentHashSet))
(is (= element #{}))))))

(deftest no-decoder-for-element
#_(deftest no-decoder-for-element
(try
(do
(decode (str (char 0x2) #{:unrecognized} (char 0x00)))
Expand Down Expand Up @@ -213,12 +213,14 @@
(reduce str "" (take length (iterate (fn [_] (char (+ min-char (int (rand max-char))))) nil)))))


(declare gen-random-vector gen-random-element)
(declare gen-random-vector gen-random-element gen-random-set gen-random-map)

(defn gen-random-element [& {:keys [max-vector-length]}]
(let [num (rand 12)]
(defn gen-random-element [& {:keys [max-length]}]
(let [num (rand 16)]
(cond
(> num 10) (gen-random-vector :max-length max-vector-length)
(> num 14) (gen-random-map :max-length max-length)
(> num 12) (gen-random-set :max-length max-length)
(> num 10) (gen-random-vector :max-length max-length)
(> num 9) nil
(> num 8) true
(> num 7) false
Expand All @@ -235,9 +237,27 @@
(let [max-length (if max-length (dec max-length) 15)
length (int (Math/floor (rand max-length)))]
(if (> length 0)
(vec (take (inc length) (rest (iterate (fn [_] (gen-random-element :max-vector-length max-length)) nil))))
(vec (take (inc length) (rest (iterate (fn [_] (gen-random-element :max-length max-length)) nil))))
[])))

(defn gen-random-set [& {:keys [max-length]}]
(let [max-length (if max-length (dec max-length) 15)
length (int (Math/floor (rand max-length)))]
(if (> length 0)
(set (take (inc length) (rest (iterate (fn [_] (gen-random-element :max-length max-length)) nil))))
#{})))

(defn gen-random-map [& {:keys [max-length]}]
(let [max-length (if max-length (dec max-length) 15)
length (int (Math/floor (rand max-length)))]
(if (> length 0)
(->> (iterate (fn [_] (gen-random-element :max-length max-length)) nil)
rest
(take (inc length))
(partition 2)
(map vec)
(into {}))
{})))

(defn test-random [& {:keys [hide-success]}]
(test-encoding (gen-random-vector) :hide-success hide-success))
Expand All @@ -260,3 +280,42 @@

(deftest random-path-test
(is (test-many-random 100 :hide-success true)))

(deftest map-ordering-1
(is (= (encode {:a 1, {:k :b} 2})
(encode {{:k :b} 2 :a 1}))))

(deftest map-ordering-2
(let [f1 {:a 1, {:k :b} 2}
e1 (encode f1)
d1 (decode e1)
f2 {{:k :b} 2 :a 1}
e2 (encode f2)
d2 (decode e2)]
(is (= e1 e2))
(is (= f1 d1 f2 d2))))

(defn randomize-map-ordering [x]
(cond
(map? x) (into {} (shuffle (map (fn [[k v]] [k (randomize-map-ordering v)]) x)))
(set? x) (into #{} (map randomize-map-ordering x))
(vector? x) (mapv randomize-map-ordering x)
:else x))

(deftest random-map-ordering
(loop [n 1000]
(when (pos? n)
(let [m1 (gen-random-map :max-length 12)
e1 (encode m1)
d1 (decode e1)

m2 (randomize-map-ordering m1)
e2 (encode m2)
d2 (decode e2)]
(is (= e1 e2))
(is (= m1 m2 d1 d2))
(recur (dec n))))))

(deftest set-ordering-1
(is (= (encode #{#{:a :b} :c {:a 1}})
(encode #{{:a 1} :c #{:b :a}}))))