diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..5249c4a --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,43 @@ +version: 2.1 + +orbs: + tools: replikativ/clj-tools@0 + +workflows: + build-test-and-deploy: + jobs: + - tools/setup: + context: docker-deploy + setup_cljs: false + - tools/build: + context: docker-deploy + build_cljs: false + requires: + - tools/setup + - tools/format: + context: docker-deploy + requires: + - tools/setup +# - tools/unittest: +# context: docker-deploy +# requires: +# - tools/build + - tools/deploy: + context: + - clojars-deploy + - docker-deploy + filters: + branches: + only: main + requires: + - tools/build +# - tools/unittest + - tools/release: + context: + - github-token + - docker-deploy + filters: + branches: + only: main + requires: + - tools/deploy diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..bbeb446 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: replikativ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8caf671 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +pom.xml.asc +*.jar +*.class +/lib/ +/classes/ +/target/ +/checkouts/ +.lein-deps-sum +.lein-repl-history +.lein-plugins/ +.lein-failures +.nrepl-port +.cpcache/ +temp/ +datahike.mv.db +.idea/ +datahike-redis.iml +.clj-kondo/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f735bee --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8ea972 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# Datahike Redis Backend + +

+ + + + +

+ +The goal of this backend is to support [Redis](https://redis.io). + +## Configuration +Please read the [Datahike configuration docs](https://github.com/replikativ/datahike/blob/master/doc/config.md) on how to configure your backend. Details about the backend configuration can be found in [konserve-redis](https://github.com/replikativ/konserve-redis).A sample configuration is +`create-database`, `connect` and `delete-database`: +```clojure +{:store {:backend :redis + :uri "redis://localhost:9475"}} +``` +This same configuration can be achieved by setting one environment variable for the redis backend +and one environment variable for the configuration of the redis backend: +```bash +DATAHIKE_STORE_BACKEND=redis +DATAHIKE_STORE_CONFIG='{:uri "redis://localhost:9475"}' +``` + +## Usage +Add to your Leiningen or Boot dependencies: +[![Clojars Project](https://img.shields.io/clojars/v/io.replikativ/datahike-redis.svg)](https://clojars.org/io.replikativ/datahike-redis) + +Now require the Datahike API and the datahike-redis namespace in your editor or REPL using the +keyword `:redis`. If you want to use other backends than S3 please refer to the official +[Datahike docs](https://github.com/replikativ/datahike/blob/master/doc/config.md). + +### Run Datahike in your REPL +```clojure + (ns project.core + (:require [datahike.api :as d] + [datahike-redis.core])) + + (def cfg {:store {:backend :redis + :bucket "redis://localhost:9475"}}) + + ;; Create a database at this place, by default configuration we have a strict + ;; schema validation and keep historical data + (d/create-database cfg) + + (def conn (d/connect cfg)) + + ;; The first transaction will be the schema we are using: + (d/transact conn [{:db/ident :name + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one } + {:db/ident :age + :db/valueType :db.type/long + :db/cardinality :db.cardinality/one }]) + + ;; Let's add some data and wait for the transaction + (d/transact conn [{:name "Alice", :age 20 } + {:name "Bob", :age 30 } + {:name "Charlie", :age 40 } + {:age 15 }]) + + ;; Search the data + (d/q '[:find ?e ?n ?a + :where + [?e :name ?n] + [?e :age ?a]] + @conn) + ;; => #{[3 "Alice" 20] [4 "Bob" 30] [5 "Charlie" 40]} + + ;; Clean up the database if it is not needed any more + (d/delete-database cfg) +``` + +## Run Tests + +```bash + bash -x ./bin/run-integration-tests +``` + +## License + +Copyright © 2023 lambdaforge UG (haftungsbeschränkt) + +This program and the accompanying materials are made available under the terms of the Eclipse Public License 1.0. diff --git a/bin/run-integrationtests b/bin/run-integrationtests new file mode 100755 index 0000000..cba579b --- /dev/null +++ b/bin/run-integrationtests @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -o errexit +set -o pipefail + +TIMBRE_LEVEL=':warn' DATAHIKE_STORE_BACKEND=redis DATAHIKE_STORE_CONFIG='{:uri "redis://localhost:9475"}' clojure -A:test diff --git a/build.clj b/build.clj new file mode 100644 index 0000000..9d69da5 --- /dev/null +++ b/build.clj @@ -0,0 +1,82 @@ +(ns build + (:refer-clojure :exclude [test]) + (:require [clojure.tools.build.api :as b] + [borkdude.gh-release-artifact :as gh] + [deps-deploy.deps-deploy :as dd]) + (:import [clojure.lang ExceptionInfo])) + +(def org "replikativ") +(def lib 'io.replikativ/datahike-redis) +(def current-commit (b/git-process {:git-args "rev-parse HEAD"})) +(def version (format "0.1.%s" (b/git-count-revs nil))) +(def class-dir "target/classes") +(def basis (b/create-basis {:project "deps.edn"})) +(def jar-file (format "target/%s-%s.jar" (name lib) version)) + +(defn clean + [_] + (b/delete {:path "target"})) + +(defn jar + [_] + (b/write-pom {:class-dir class-dir + :src-pom "./template/pom.xml" + :lib lib + :version version + :basis basis + :src-dirs ["src"]}) + (b/copy-dir {:src-dirs ["src" "resources"] + :target-dir class-dir}) + (b/jar {:class-dir class-dir + :jar-file jar-file})) + +(defn deploy + "Don't forget to set CLOJARS_USERNAME and CLOJARS_PASSWORD env vars." + [_] + (dd/deploy {:installer :remote :artifact jar-file + :pom-file (b/pom-path {:lib lib :class-dir class-dir})})) + +(defn fib [a b] + (lazy-seq (cons a (fib b (+ a b))))) + +(defn retry-with-fib-backoff [retries exec-fn test-fn] + (loop [idle-times (take retries (fib 1 2))] + (let [result (exec-fn)] + (if (test-fn result) + (do (println "Returned: " result) + (if-let [sleep-ms (first idle-times)] + (do (println "Retrying with remaining back-off times (in s): " idle-times) + (Thread/sleep (* 1000 sleep-ms)) + (recur (rest idle-times))) + result)) + result)))) + +(defn try-release [] + (try (gh/overwrite-asset {:org org + :repo (name lib) + :tag version + :commit current-commit + :file jar-file + :content-type "application/java-archive" + :draft false}) + (catch ExceptionInfo e + (assoc (ex-data e) :failure? true)))) + +(defn release + [_] + (println "Trying to release artifact...") + (let [ret (retry-with-fib-backoff 10 try-release :failure?)] + (if (:failure? ret) + (do (println "GitHub release failed!") + (System/exit 1)) + (println (:url ret))))) + +(defn install + [_] + (clean nil) + (jar nil) + (b/install {:basis (b/create-basis {}) + :lib lib + :version version + :jar-file jar-file + :class-dir class-dir})) diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..3480e56 --- /dev/null +++ b/deps.edn @@ -0,0 +1,20 @@ +{:deps {org.clojure/clojure {:mvn/version "1.11.1" :scope "provided"} + io.replikativ/datahike {:mvn/version "0.6.1538"} + io.replikativ/superv.async {:mvn/version "0.2.11"} + io.replikativ/konserve-redis {:mvn/version "0.1.3"}} + :paths ["src"] + :aliases {:test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.60.977"}} + :extra-paths ["test"]} + :jar {:extra-deps {seancorfield/depstar {:mvn/version "1.1.116"}} + :main-opts ["-m" "hf.depstar.jar" "replikativ-datahike-redis.jar"]} + :format {:extra-deps {cljfmt/cljfmt {:mvn/version "0.7.0"}} + :main-opts ["-m" "cljfmt.main" "check"]} + :ffix {:extra-deps {cljfmt/cljfmt {:mvn/version "0.8.0"}} + :main-opts ["-m" "cljfmt.main" "fix"]} + :build {:deps {io.github.clojure/tools.build {:mvn/version "0.9.3"} + slipset/deps-deploy {:mvn/version "0.2.0"} + io.github.borkdude/gh-release-artifact {:git/sha "05f8d8659e6805d513c59447ff41dc8497878462"} + babashka/babashka.curl {:mvn/version "0.1.2"} + babashka/fs {:mvn/version "0.1.6"} + cheshire/cheshire {:mvn/version "5.10.2"}} + :ns-default build}}} diff --git a/src/datahike_redis/core.clj b/src/datahike_redis/core.clj new file mode 100644 index 0000000..c353675 --- /dev/null +++ b/src/datahike_redis/core.clj @@ -0,0 +1,29 @@ +(ns datahike-redis.core + (:require [datahike.store :refer [empty-store delete-store connect-store default-config config-spec release-store]] + [datahike.config :refer [map-from-env]] + [konserve-redis.core :as k] + [clojure.spec.alpha :as s])) + +(defmethod empty-store :redis [store-config] + (k/connect-store store-config)) + +(defmethod delete-store :redis [store-config] + (k/delete-store store-config)) + +(defmethod connect-store :redis [store-config] + (k/connect-store store-config)) + +(defmethod default-config :redis [config] + (merge + (map-from-env :datahike-store-config {}) + config)) + +(s/def :datahike.store.redis/backend #{:redis}) +(s/def :datahike.store.redis/uri string?) +(s/def ::redis (s/keys :req-un [:datahike.store.redis/backend] + :opt-un [:datahike.store.redis/uri])) + +(defmethod config-spec :redis [_] ::redis) + +(defmethod release-store :redis [_ store] + (k/release store {:sync? true})) diff --git a/template/pom.xml b/template/pom.xml new file mode 100644 index 0000000..73996ff --- /dev/null +++ b/template/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + io.replikativ + datahike-redis + jar + datahike-redis + Datahike Redis Backend + + + Eclipse + http://www.eclipse.org/legal/epl-v10.html + + + + scm:git:git@github.com:replikativ/datahike-redis.git + scm:git:git@github.com/replikativ/datahike-redis.git + https://github.com/replikativ/datahike-redis + + diff --git a/test/datahike_redis/core_test.cljc b/test/datahike_redis/core_test.cljc new file mode 100644 index 0000000..3344d62 --- /dev/null +++ b/test/datahike_redis/core_test.cljc @@ -0,0 +1,29 @@ +(ns datahike-redis.core-test + (:require + #?(:cljs [cljs.test :as t :refer-macros [is deftest]] + :clj [clojure.test :as t :refer [is deftest]]) + [datahike.api :as d] + [datahike-redis.core])) + +(deftest ^:integration test-redis + (let [config {:store {:backend :redis + :uri "redis://localhost:9475"} + :schema-flexibility :read + :keep-history? false} + _ (d/delete-database config)] + (is (not (d/database-exists? config))) + (let [_ (d/create-database config) + conn (d/connect config)] + + (d/transact conn [{:db/id 1, :name "Ivan", :age 15} + {:db/id 2, :name "Petr", :age 37} + {:db/id 3, :name "Ivan", :age 37} + {:db/id 4, :age 15}]) + (is (= (d/q '[:find ?e :where [?e :name]] @conn) + #{[3] [2] [1]})) + + (d/release conn) + (is (d/database-exists? config)) + (d/delete-database config) + (is (not (d/database-exists? config)))))) + diff --git a/tests.edn b/tests.edn new file mode 100644 index 0000000..6bec891 --- /dev/null +++ b/tests.edn @@ -0,0 +1 @@ +#kaocha/v1 {:reporter kaocha.report/documentation}