From ed1df495f0889dfc87cfa59f038272f3b9fa7807 Mon Sep 17 00:00:00 2001 From: Christian Weilbach Date: Tue, 21 Apr 2020 03:59:37 -0700 Subject: [PATCH] Add java.util.Date support to the comparator and add test. Update README. --- README.md | 134 +++++++++++++++++++-------- project.clj | 2 +- src/hitchhiker/tree/codec/nippy.cljc | 58 +++++++++--- src/hitchhiker/tree/key_compare.cljc | 30 +++++- src/hitchhiker/tree/messaging.cljc | 2 + test/hitchhiker/konserve_test.cljc | 93 ++++++++++++------- test/hitchhiker/redis_test.clj | 8 +- test/hitchhiker/tree/core_test.clj | 35 +++++++ 8 files changed, 269 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 91d13c8b..136f25c8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Hitchhiker Tree -Hitchhiker trees are a newly invented (by @dgrnbrg) datastructure, synthesizing fractal trees and functional data structures, to create fast, snapshottable, massively scalable databases. +Hitchhiker trees are a datastructure [invented by David Greenberg](https://github.com/datacrypt-project/hitchhiker-tree), synthesizing fractal trees and functional data structures, to create fast, snapshottable, massively scalable databases. -[Watch the talk from Strange Loop](https://www.youtube.com/watch?v=jdn617M3-P4) to learn more, especially about the concept! +We documented our extended design [here](https://blog.datopia.io/2018/11/03/hitchhiker-tree/). This repository currently reflects its development as a backend for [Datahike](https://github.com/replikativ/datahike). This respository adds ClojureScript support and provides a [core.async](https://github.com/clojure/core.async) backend for [konserve](https://github.com/replikativ/konserve) including a [Merkle](https://en.wikipedia.org/wiki/Merkle_tree) variant of the tree, effectively making the hitchhiker much more portable and apt for distributed infrastructure. ## What's in this Repository? @@ -10,12 +10,98 @@ The hitchhiker namespaces contain a complete implementation of a persistent, ser This is a sorted key-value datastructure, like a scalable `sorted-map`. It can incrementally persist and automatically lazily load itself from any backing store which implements a simple protocol. -Outboard is a sample application for the hitchhiker tree. -It includes an implementation of the IO subsystem backed by Redis, and it manages all of the incremental serialization and flushing. +## Usage -The hitchhiker tree is designed very similarly to how Datomic's backing trees must work--I would love to see integration with [DataScript](https://github.com/tonsky/datascript) for a fully open source [Datomic](http://www.datomic.com). +Add this dependency to your project: + +[![Clojars Project](http://clojars.org/io.replikativ/hitchhiker-tree/latest-version.svg)](http://clojars.org/io.replikativ/hitchhiker-tree) + +## Current API + +We use [tree.cljc](src/hitchhiker/tree.cljc) and [messaging.cljc](src/hitchhiker/tree/messaging.cljc) as the core API. The following snippet is extracted from the [konserve tests](test/hitchhiker/konserve_test.cljc). + +```clojure +(ns hitchhiker-tree.sandbox + (:require [hitchhiker.tree :as core] + [hitchhiker.tree.messaging :as msg] + [hitchhiker.tree.bootstrap.konserve :as kons] + [hitchhiker.tree.utils.async :as ha] + [konserve.cache :as kc] + [konserve.filestore :refer [new-fs-store delete-store list-keys]] + [clojure.core.async :as async] + [clojure.test :refer [deftest testing run-tests is] + ])) + + +(let [folder "/tmp/async-hitchhiker-tree-test" + _ (delete-store folder) + store (kons/add-hitchhiker-tree-handlers + (kc/ensure-cache (async/KonserveBackend store) + config (core/->Config 1 3 (- 3 1)) + flushed (ha/ node + (nilify [:storage-addr :*last-key-cache]) + (assoc :children (mapv encode (:children node))))) + +(defn encode-data-node + [node] + (nilify node + [:storage-addr + :*last-key-cache])) + +(defn encode-address + [node] + (nilify node + [:store + :storage-addr])) + +(defn encode + [node] + (cond + (tree/index-node? node) (encode-index-node node) + (tree/data-node? node) (encode-data-node node) + (n/address? node) (encode-address node) + :else node)) + + (defonce install* (delay #?@(:clj [(nippy/extend-freeze hitchhiker.tree.IndexNode :b-tree/index-node - [{:keys [storage-addr cfg children op-buf]} data-output] - (nippy/freeze-to-out! data-output cfg) - (nippy/freeze-to-out! data-output children) - (nippy/freeze-to-out! data-output (into [] op-buf))) + [node data-output] + (nippy/freeze-to-out! data-output (into {} (encode node)))) (nippy/extend-thaw :b-tree/index-node [data-input] - (let [cfg (nippy/thaw-from-in! data-input) - children (nippy/thaw-from-in! data-input) - op-buf (nippy/thaw-from-in! data-input)] - (tree/index-node children op-buf cfg))) + (tree/map->IndexNode (nippy/thaw-from-in! data-input))) (nippy/extend-freeze hitchhiker.tree.DataNode :b-tree/data-node - [{:keys [cfg children]} data-output] - (nippy/freeze-to-out! data-output cfg) - (nippy/freeze-to-out! data-output children)) + [node data-output] + (nippy/freeze-to-out! data-output (into {} (encode node)))) (nippy/extend-thaw :b-tree/data-node [data-input] - (let [cfg (nippy/thaw-from-in! data-input) - children (nippy/thaw-from-in! data-input)] - (tree/data-node children cfg)))]))) + (tree/map->DataNode (nippy/thaw-from-in! data-input)))]))) (defn ensure-installed! [] diff --git a/src/hitchhiker/tree/key_compare.cljc b/src/hitchhiker/tree/key_compare.cljc index 5a523530..059224bc 100644 --- a/src/hitchhiker/tree/key_compare.cljc +++ b/src/hitchhiker/tree/key_compare.cljc @@ -44,6 +44,9 @@ java.util.UUID (-order-on-edn-types [_] 8) + java.util.Date + (-order-on-edn-types [_] 9) + nil (-order-on-edn-types [_] 10000) @@ -77,6 +80,9 @@ cljs.core/UUID (-order-on-edn-types [_] 8) + js/Date + (-order-on-edn-types [_] 9) + nil (-order-on-edn-types [_] 10000) @@ -164,6 +170,23 @@ (catch ClassCastException e (- (n/-order-on-edn-types key2) (n/-order-on-edn-types key1)))))) + + java.util.Date + (-compare [^java.util.Date key1 key2] + (if (instance? java.util.Date key2) + (cond + (< (.getTime key1) (.getTime key2)) -1 + (= (.getTime key1) (.getTime key2)) 0 + :else 1) + (try + (compare key1 key2) + (catch ClassCastException e + (- (n/-order-on-edn-types key2) + (n/-order-on-edn-types key1)))))) + nil + (-compare [^java.lang.Null key1 key2] + (- (n/-order-on-edn-types key2) + (n/-order-on-edn-types key1))) ] :cljs [number @@ -189,4 +212,9 @@ (- (n/-order-on-edn-types key2) (n/-order-on-edn-types key1)))) (- (n/-order-on-edn-types key2) - (n/-order-on-edn-types key1))))])) + (n/-order-on-edn-types key1)))) + nil + (-compare [key1 key2] + (- (n/-order-on-edn-types key2) + (n/-order-on-edn-types key1))) + ])) diff --git a/src/hitchhiker/tree/messaging.cljc b/src/hitchhiker/tree/messaging.cljc index 52b59c96..53dbe432 100644 --- a/src/hitchhiker/tree/messaging.cljc +++ b/src/hitchhiker/tree/messaging.cljc @@ -228,3 +228,5 @@ (drop-while (fn [[k v]] (neg? (c/-compare k key))) (forward-iterator path))))))) + + diff --git a/test/hitchhiker/konserve_test.cljc b/test/hitchhiker/konserve_test.cljc index 7480f7ab..e54605c8 100644 --- a/test/hitchhiker/konserve_test.cljc +++ b/test/hitchhiker/konserve_test.cljc @@ -34,6 +34,35 @@ (msg/forward-iterator iter-ch path key)) (ha/KonserveBackend store) + config (core/->Config 1 3 (- 3 1)) + flushed (ha/KonserveBackend store))) -;; t (:tree flushed)] -;; [t (ha/Config 3 3 2))) nil #{}] -;; ops))] -;; (let [b-tree-order (seq (map first (ha/KonserveBackend store))) + t (:tree flushed)] + [t (ha/Config 3 3 2))) nil #{}] + ops))] + (let [b-tree-order (seq (map first (ha/Config 3 3 2))) + (concat (range 5) + [#inst "2020-04-21T09:50:15.257-00:00" + #inst "2020-04-21T09:51:15.257-00:00" + #uuid "58cc42f0-ccf7-41de-ab2e-79fc325d51db" + nil + -0.2 + 1.9E10 + true + :foo + "bar" + 'bar + :bar + 'baz + false + "Foo" + 9N + 10.0M + ]))] + (is (= (map second (lookup-fwd-iter root nil)) + '(nil + #inst "2020-04-21T09:50:15.257-00:00" + #inst "2020-04-21T09:51:15.257-00:00" + #uuid "58cc42f0-ccf7-41de-ab2e-79fc325d51db" + false true + :bar :foo + bar baz + "Foo" "bar" + -0.2 0 1 2 3 4 9N 10.0M 1.9E10))))) + (deftest insert-test (let [data1 (data-node (sorted-map 1 "1" 2 "2" 3 "3" 4 "4") (->Config 3 3 2)) @@ -115,7 +147,10 @@ [] (->Config 3 3 2))] + (is (= (map second (lookup-fwd-iter (