diff --git a/src/asami/durable/flat.cljc b/src/asami/durable/flat.cljc index 539ef6b..b8ac860 100644 --- a/src/asami/durable/flat.cljc +++ b/src/asami/durable/flat.cljc @@ -5,4 +5,5 @@ (defprotocol FlatStore (write-object! [this obj] "Writes an object to storage. Returns an ID") (get-object [this id] "Reads and object from storage, based on an ID") - (force! [this] "Ensures that all written data is fully persisted")) + (force! [this] "Ensures that all written data is fully persisted") + (close [this] "Releases any resources associated with the store")) diff --git a/src/asami/durable/flat_file.clj b/src/asami/durable/flat_file.clj index 39c66bb..17cc2a8 100644 --- a/src/asami/durable/flat_file.clj +++ b/src/asami/durable/flat_file.clj @@ -15,6 +15,9 @@ (def file-name "raw.dat") +(defprotocol Clearable + (clear! [this] "Clears out any resources which may be held")) + ;; These functions do update the PagedFile state, but only to expand the mapped region. (defrecord PagedFile [^RandomAccessFile f regions region-size] Paged @@ -115,7 +118,9 @@ (doto (.asReadOnlyBuffer region) (.position region-offset) (.get bytes))) - bytes)))) + bytes))) + Clearable + (clear! [this] (reset! regions nil))) (defn paged-file "Creates a paged file reader" @@ -127,7 +132,7 @@ ;; rfile: A file that will only be appended to ;; paged: A paged reader for the file -(defrecord FlatFile [rfile paged] +(defrecord FlatFile [^RandomAccessFile rfile paged] FlatStore (write-object! [this obj] @@ -140,7 +145,10 @@ [this id] (decoder/read-object paged id)) (force! [this] - (.force (.getChannel rfile true)))) + (.force (.getChannel rfile) true)) + (close [this] + (clear! paged) + (.close rfile))) (defn flat-store "Creates a flat file store. This wraps an append-only file and a paged reader." @@ -152,4 +160,4 @@ file-length (.length raf)] (when-not (zero? file-length) (.seek raf file-length)) - (->FlatFile raf (paged-file raf) file-length))) + (->FlatFile raf (paged-file raf)))) diff --git a/test/asami/durable/test_flat.cljc b/test/asami/durable/test_flat.cljc new file mode 100644 index 0000000..ffe7204 --- /dev/null +++ b/test/asami/durable/test_flat.cljc @@ -0,0 +1,48 @@ +(ns ^{:doc "Tests flat store functionality, saving and retrieving data" + :author "Paula Gearon"} + asami.durable.test-flat + (:require [asami.durable.flat :refer [write-object! get-object force!]] + #?(:clj [asami.durable.flat-file :as ff]) + #?(:clj [clojure.java.io :as io]) + [clojure.test :refer [deftest is]]) + #?(:clj (:import [java.net URI]))) + +(def store-name "test-fstore") + +(defn uri [s] #?(:clj (URI. s) :cljs (goog/Uri. s))) + +(def data + ["Hello" + "Goodbye" + :the + :quick + (uri "http://brown.com/fox") + "jumps over the lazy dog." + 1812 + "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.\n\nHowever little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered the rightful property of some one or other of their daughters.\n\n“My dear Mr. Bennet,” said his lady to him one day, “have you heard that Netherfield Park is let at last?”\n\nMr. Bennet replied that he had not.\n\n“But it is,” returned she; “for Mrs. Long has just been here, and she told me all about it.”\n\nMr. Bennet made no answer.\n\n“Do you not want to know who has taken it?” cried his wife impatiently.\n\n“You want to tell me, and I have no objection to hearing it.”\n\nThis was invitation enough."]) + +(deftest test-store + (let [fmapped (volatile! nil)] + (is (not (.exists (io/file store-name ff/file-name)))) + (with-open [store (ff/flat-store store-name)] + (vreset! fmapped (reduce-kv (fn [m k v] + (let [id (write-object! store v)] + (assoc m k id))) + {} data)) + (is (= (count data) (count @fmapped))) + (doseq [[n id] @fmapped] + (is (= (nth data n) (get-object store id)))) + (doseq [[n id] (shuffle (seq @fmapped))] + (is (= (nth data n) (get-object store id)))) + (force! store)) + + (with-open [store (ff/flat-store store-name)] + (doseq [[n id] @fmapped] + (is (= (nth data n) (get-object store id)))) + (doseq [[n id] (shuffle (seq @fmapped))] + (is (= (nth data n) (get-object store id)))))) + #?(:clj + (let [f (io/file store-name ff/file-name) + d (io/file store-name)] + (is (.delete f)) + (is (.delete d)))))