├── .circleci
└── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── bin
├── deploy-jar.sh
└── jar.sh
├── deps.edn
├── pom.xml
├── src
└── compute
│ └── datomic_client_memdb
│ ├── async.clj
│ └── core.clj
└── test
└── compute
└── datomic_client_memdb
├── async_test.clj
└── core_test.clj
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | defaults: &defaults
4 | working_directory: ~/repo
5 | docker:
6 | - image: circleci/clojure:tools-deps-1.9.0.381
7 |
8 | jobs:
9 | test:
10 | <<: *defaults
11 |
12 | steps:
13 | - checkout
14 |
15 | - restore_cache:
16 | keys:
17 | - project-{{ checksum "deps.edn" }}
18 | - project
19 |
20 | - run: clojure -A:test:run-tests
21 |
22 | - save_cache:
23 | paths:
24 | - ~/.m2
25 | - ~/.gitlibs
26 | - ./.cpcache
27 | key: project-{{ checksum "deps.edn" }}
28 |
29 | workflows:
30 | version: 2
31 | ci-workflow:
32 | jobs:
33 | - test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml.asc
5 | *.jar
6 | *.class
7 | /.lein-*
8 | /.nrepl-port
9 | .nrepl-history
10 | .hgignore
11 | .hg/
12 | *.impl
13 | .idea
14 | .envrc
15 | project.clj
16 | *.iml
17 | .nrepl-history
18 | .nrepl-port
19 | out
20 | .tags
21 | .cpcache
22 | extra
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License - v 1.0
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF
5 | THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial code and
12 | documentation distributed under this Agreement, and
13 |
14 | b) in the case of each subsequent Contributor:
15 |
16 | i) changes to the Program, and
17 |
18 | ii) additions to the Program;
19 |
20 | where such changes and/or additions to the Program originate from and
21 | are distributed by that particular Contributor. A Contribution
22 | 'originates' from a Contributor if it was added to the Program by such
23 | Contributor itself or anyone acting on such Contributor's
24 | behalf. Contributions do not include additions to the Program which:
25 | (i) are separate modules of software distributed in conjunction with
26 | the Program under their own license agreement, and (ii) are not
27 | derivative works of the Program.
28 |
29 | "Contributor" means any person or entity that distributes the Program.
30 |
31 | "Licensed Patents" mean patent claims licensable by a Contributor
32 | which are necessarily infringed by the use or sale of its Contribution
33 | alone or when combined with the Program.
34 |
35 | "Program" means the Contributions distributed in accordance with this
36 | Agreement.
37 |
38 | "Recipient" means anyone who receives the Program under this
39 | Agreement, including all Contributors.
40 |
41 | 2. GRANT OF RIGHTS
42 |
43 | a) Subject to the terms of this Agreement, each Contributor hereby
44 | grants Recipient a non-exclusive, worldwide, royalty-free copyright
45 | license to reproduce, prepare derivative works of, publicly display,
46 | publicly perform, distribute and sublicense the Contribution of such
47 | Contributor, if any, and such derivative works, in source code and
48 | object code form.
49 |
50 | b) Subject to the terms of this Agreement, each Contributor hereby
51 | grants Recipient a non-exclusive, worldwide, royalty-free patent
52 | license under Licensed Patents to make, use, sell, offer to sell,
53 | import and otherwise transfer the Contribution of such Contributor, if
54 | any, in source code and object code form. This patent license shall
55 | apply to the combination of the Contribution and the Program if, at
56 | the time the Contribution is added by the Contributor, such addition
57 | of the Contribution causes such combination to be covered by the
58 | Licensed Patents. The patent license shall not apply to any other
59 | combinations which include the Contribution. No hardware per se is
60 | licensed hereunder.
61 |
62 | c) Recipient understands that although each Contributor grants the
63 | licenses to its Contributions set forth herein, no assurances are
64 | provided by any Contributor that the Program does not infringe the
65 | patent or other intellectual property rights of any other entity. Each
66 | Contributor disclaims any liability to Recipient for claims brought by
67 | any other entity based on infringement of intellectual property rights
68 | or otherwise. As a condition to exercising the rights and licenses
69 | granted hereunder, each Recipient hereby assumes sole responsibility
70 | to secure any other intellectual property rights needed, if any. For
71 | example, if a third party patent license is required to allow
72 | Recipient to distribute the Program, it is Recipient's responsibility
73 | to acquire that license before distributing the Program.
74 |
75 | d) Each Contributor represents that to its knowledge it has sufficient
76 | copyright rights in its Contribution, if any, to grant the copyright
77 | license set forth in this Agreement.
78 |
79 | 3. REQUIREMENTS
80 |
81 | A Contributor may choose to distribute the Program in object code form
82 | under its own license agreement, provided that:
83 |
84 | a) it complies with the terms and conditions of this Agreement; and
85 |
86 | b) its license agreement:
87 |
88 | i) effectively disclaims on behalf of all Contributors all warranties
89 | and conditions, express and implied, including warranties or
90 | conditions of title and non-infringement, and implied warranties or
91 | conditions of merchantability and fitness for a particular purpose;
92 |
93 | ii) effectively excludes on behalf of all Contributors all liability
94 | for damages, including direct, indirect, special, incidental and
95 | consequential damages, such as lost profits;
96 |
97 | iii) states that any provisions which differ from this Agreement are
98 | offered by that Contributor alone and not by any other party; and
99 |
100 | iv) states that source code for the Program is available from such
101 | Contributor, and informs licensees how to obtain it in a reasonable
102 | manner on or through a medium customarily used for software exchange.
103 |
104 | When the Program is made available in source code form:
105 |
106 | a) it must be made available under this Agreement; and
107 |
108 | b) a copy of this Agreement must be included with each copy of the Program.
109 |
110 | Contributors may not remove or alter any copyright notices contained
111 | within the Program.
112 |
113 | Each Contributor must identify itself as the originator of its
114 | Contribution, if any, in a manner that reasonably allows subsequent
115 | Recipients to identify the originator of the Contribution.
116 |
117 | 4. COMMERCIAL DISTRIBUTION
118 |
119 | Commercial distributors of software may accept certain
120 | responsibilities with respect to end users, business partners and the
121 | like. While this license is intended to facilitate the commercial use
122 | of the Program, the Contributor who includes the Program in a
123 | commercial product offering should do so in a manner which does not
124 | create potential liability for other Contributors. Therefore, if a
125 | Contributor includes the Program in a commercial product offering,
126 | such Contributor ("Commercial Contributor") hereby agrees to defend
127 | and indemnify every other Contributor ("Indemnified Contributor")
128 | against any losses, damages and costs (collectively "Losses") arising
129 | from claims, lawsuits and other legal actions brought by a third party
130 | against the Indemnified Contributor to the extent caused by the acts
131 | or omissions of such Commercial Contributor in connection with its
132 | distribution of the Program in a commercial product offering. The
133 | obligations in this section do not apply to any claims or Losses
134 | relating to any actual or alleged intellectual property
135 | infringement. In order to qualify, an Indemnified Contributor must: a)
136 | promptly notify the Commercial Contributor in writing of such claim,
137 | and b) allow the Commercial Contributor tocontrol, and cooperate with
138 | the Commercial Contributor in, the defense and any related settlement
139 | negotiations. The Indemnified Contributor may participate in any such
140 | claim at its own expense.
141 |
142 | For example, a Contributor might include the Program in a commercial
143 | product offering, Product X. That Contributor is then a Commercial
144 | Contributor. If that Commercial Contributor then makes performance
145 | claims, or offers warranties related to Product X, those performance
146 | claims and warranties are such Commercial Contributor's responsibility
147 | alone. Under this section, the Commercial Contributor would have to
148 | defend claims against the other Contributors related to those
149 | performance claims and warranties, and if a court requires any other
150 | Contributor to pay any damages as a result, the Commercial Contributor
151 | must pay those damages.
152 |
153 | 5. NO WARRANTY
154 |
155 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
156 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
157 | KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
158 | WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
159 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
160 | responsible for determining the appropriateness of using and
161 | distributing the Program and assumes all risks associated with its
162 | exercise of rights under this Agreement , including but not limited to
163 | the risks and costs of program errors, compliance with applicable
164 | laws, damage to or loss of data, programs or equipment, and
165 | unavailability or interruption of operations.
166 |
167 | 6. DISCLAIMER OF LIABILITY
168 |
169 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
170 | ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
171 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
172 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
173 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
174 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
175 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
176 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
177 |
178 | 7. GENERAL
179 |
180 | If any provision of this Agreement is invalid or unenforceable under
181 | applicable law, it shall not affect the validity or enforceability of
182 | the remainder of the terms of this Agreement, and without further
183 | action by the parties hereto, such provision shall be reformed to the
184 | minimum extent necessary to make such provision valid and enforceable.
185 |
186 | If Recipient institutes patent litigation against any entity
187 | (including a cross-claim or counterclaim in a lawsuit) alleging that
188 | the Program itself (excluding combinations of the Program with other
189 | software or hardware) infringes such Recipient's patent(s), then such
190 | Recipient's rights granted under Section 2(b) shall terminate as of
191 | the date such litigation is filed.
192 |
193 | All Recipient's rights under this Agreement shall terminate if it
194 | fails to comply with any of the material terms or conditions of this
195 | Agreement and does not cure such failure in a reasonable period of
196 | time after becoming aware of such noncompliance. If all Recipient's
197 | rights under this Agreement terminate, Recipient agrees to cease use
198 | and distribution of the Program as soon as reasonably
199 | practicable. However, Recipient's obligations under this Agreement and
200 | any licenses granted by Recipient relating to the Program shall
201 | continue and survive.
202 |
203 | Everyone is permitted to copy and distribute copies of this Agreement,
204 | but in order to avoid inconsistency the Agreement is copyrighted and
205 | may only be modified in the following manner. The Agreement Steward
206 | reserves the right to publish new versions (including revisions) of
207 | this Agreement from time to time. No one other than the Agreement
208 | Steward has the right to modify this Agreement. The Eclipse Foundation
209 | is the initial Agreement Steward. The Eclipse Foundation may assign
210 | the responsibility to serve as the Agreement Steward to a suitable
211 | separate entity. Each new version of the Agreement will be given a
212 | distinguishing version number. The Program (including Contributions)
213 | may always be distributed subject to the version of the Agreement
214 | under which it was received. In addition, after a new version of the
215 | Agreement is published, Contributor may elect to distribute the
216 | Program (including its Contributions) under the new version. Except as
217 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives
218 | no rights or licenses to the intellectual property of any Contributor
219 | under this Agreement, whether expressly, by implication, estoppel or
220 | otherwise. All rights in the Program not expressly granted under this
221 | Agreement are reserved.
222 |
223 | This Agreement is governed by the laws of the State of Washington and
224 | the intellectual property laws of the United States of America. No
225 | party to this Agreement will bring a legal action under this Agreement
226 | more than one year after the cause of action arose. Each party waives
227 | its rights to a jury trial in any resulting litigation.
228 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Library is Deprecated
2 |
3 | The Datomic team released [dev-local](https://docs.datomic.com/cloud/dev-local.html) on July 10, 2020. This library has always been a temporary workaround for offline testing and easy CI support. Dev-local supersedes this library. I recommend switching over to dev-local.
4 |
5 | # datomic-client-memdb
6 |
7 | [](https://circleci.com/gh/ComputeSoftware/datomic-client-memdb)
8 |
9 | Datomic Client protocols for Datomic Peer databases.
10 |
11 | ## Installation
12 |
13 | ```clojure
14 | datomic-client-memdb {:mvn/version "1.1.1"}
15 | ```
16 |
17 | ## Usage
18 |
19 | Create a Client like you normally would with the Client API:
20 |
21 | ```clojure
22 | (require '[compute.datomic-client-memdb.core :as memdb])
23 | (def c (memdb/client {}))
24 | ```
25 |
26 | `memdb/client` returns `compute.datomic-client-memdb.core/Client` type which implements
27 | `datomic.client.api/Client`. Calling `memdb/client` multiple times with the same
28 | arg-map will result in the same instance of the client (i.e. the function is memoized).
29 |
30 | This is technically the only function that this library exposes. All other operations
31 | are done using the Datomic Client API. See [here](https://docs.datomic.com/client-api/datomic.client.api.html)
32 | for a list of functions that you can call.
33 |
34 | ### Suggested usage pattern
35 |
36 | Our company has a database util library, and I imagine most companies have something
37 | similar, that contains a number of functions associated with database operations.
38 | Any time you use Datomic, that util library is probably on the classpath. I
39 | suggest adding a `client` function that returns a Datomic Client dependent on
40 | some values in the Datomic Client arg-map (the args passed to the `client` function).
41 | For example,
42 |
43 | ```clojure
44 | (defn client
45 | [datomic-config]
46 | (if (datomic-local? datomic-config)
47 | (do
48 | (require 'compute.datomic-client-memdb.core)
49 | (if-let [v (resolve 'compute.datomic-client-memdb.core/client)]
50 | (@v datomic-config)
51 | (throw (ex-info "compute.datomic-client-memdb.core is not on the classpath." {}))))
52 | (datomic.client.api/client datomic-config)))
53 | ```
54 |
55 | The `datomic-local?` function returns true if `datomic-config` (the client arg-map)
56 | has some particular value set. Perhaps `:local? true` or `:endpoint "localhost"`.
57 |
58 | ### Caveats
59 |
60 | Datomic Cloud supports transaction functions through Datomic Ions. This library
61 | does not have support for Ions.
62 |
63 | This API wraps the Datomic Peer (On-Prem). There are features in Datomic Cloud's
64 | query that are not present in Datomic On-Prem, and vice versa.
65 |
66 | This library does not try to re-implement features from Datomic Cloud,
67 | but makes some effort to throw exceptions when the user tries to use
68 | features exclusive to on-prem, notably find spec for collections and scalars.
69 |
70 | Because this is not exhaustive, it is recommended to also run integration tests
71 | against an actual instance of Datomic Cloud.
72 |
73 | ### Cleanup
74 |
75 | If you would like to cleanup the in-memory DBs, you can use the `close` function:
76 |
77 | ```clojure
78 | (memdb/close c)
79 | ```
80 |
81 | This will delete all the DBs associated with the client and purge them from the
82 | client's DB store. Our Client also implements the `Closeable` interface so you can
83 | use the Client with `with-open`, as seen [here](https://github.com/ComputeSoftware/datomic-client-memdb/blob/aa52ef9c125aef9c48777e0e3b024eb821f387a7/test/compute/datomic_client_memdb/core_test.clj#L10-L14).
84 |
85 | Because this library is only intended to be used during the development and
86 | testing stages, you can probably just ignore the `close` function and only address
87 | cleanup if it becomes a problem.
88 |
89 | ## Implementation
90 |
91 | This library is implemented by creating custom types (via `deftype`) that extend
92 | the Datomic Client protocols located in the `datomic.client.api` namespace. I
93 | tried implementing this using `extend-type` on the Datomic peer classes (i.e. `datomic.db.Db`),
94 | but you cannot add an implementation of `clojure.lang.ILookup` via `deftype` because
95 | `ILookup` is an interface, not a protocol. We need to provide an implementation of
96 | `ILookup` because that is how the Datomic Client API exposes `t` and `next-t`.
97 |
98 | ## License
99 |
100 | Copyright © 2020 Compute Software
101 |
102 | Distributed under the Eclipse Public License either version 1.0 or (at
103 | your option) any later version.
104 |
--------------------------------------------------------------------------------
/bin/deploy-jar.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | mvn deploy:deploy-file -Dfile=datomic-client-memdb.jar -DrepositoryId=clojars -Durl=https://clojars.org/repo -DpomFile=pom.xml
4 | rm datomic-client-memdb.jar
--------------------------------------------------------------------------------
/bin/jar.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -euo pipefail
4 |
5 | clojure -Spom
6 | clojure -A:jar
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:deps {org.clojure/core.async {:mvn/version "1.1.587"}
2 | com.datomic/client {:mvn/version "0.8.89"}
3 | com.datomic/datomic-free {:mvn/version "0.9.5697"}}
4 | :paths ["src"]
5 | :aliases {:test {:extra-paths ["test"]}
6 | :run-tests {:extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git"
7 | :sha "f7ef16dc3b8332b0d77bc0274578ad5270fbfedd"}}
8 | :main-opts ["-m" "cognitect.test-runner"]}
9 | :jar {:extra-deps {seancorfield/depstar {:mvn/version "0.5.2"}}
10 | :main-opts ["-m" "hf.depstar.jar" "datomic-client-memdb.jar"]}}}
11 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | datomic-client-memdb
5 | datomic-client-memdb
6 | 1.1.1
7 | datomic-client-memdb
8 |
9 |
10 | org.clojure
11 | clojure
12 | 1.10.1
13 |
14 |
15 | com.datomic
16 | datomic-free
17 | 0.9.5697
18 |
19 |
20 | com.datomic
21 | client
22 | 0.8.89
23 |
24 |
25 | org.clojure
26 | core.async
27 | 1.1.587
28 |
29 |
30 |
31 | src
32 |
33 |
34 |
35 | clojars
36 | https://repo.clojars.org/
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/compute/datomic_client_memdb/async.clj:
--------------------------------------------------------------------------------
1 | (ns compute.datomic-client-memdb.async
2 | (:require
3 | [clojure.core.async :as async]
4 | [datomic.client.api :as d]
5 | [datomic.client.impl.shared.protocols :as async-proto]
6 | [compute.datomic-client-memdb.core :as memdb])
7 | (:import (java.io Closeable)
8 | (clojure.lang ExceptionInfo)))
9 |
10 | (defn- wrap-async
11 | [f]
12 | (async/thread
13 | (try
14 | (f)
15 | (catch Throwable ex
16 | (if (and (instance? ExceptionInfo ex)
17 | (:cognitect.anomalies/category (ex-data ex)))
18 | (ex-data ex)
19 | {:cognitect.anomalies/category :cognitect.anomalies/fault
20 | :cognitect.anomalies/message (.getMessage ex)
21 | :ex ex})))))
22 |
23 | (deftype LocalDb [conn db]
24 | async-proto/Db
25 | (as-of [_ time-point]
26 | (d/as-of db time-point))
27 | (datoms [_ arg-map]
28 | (wrap-async #(d/datoms db arg-map)))
29 | (db-stats [_]
30 | (doto (async/promise-chan)
31 | (async/put! {:cognitect.anomalies/category :cognitect.anomalies/unsupported
32 | :cognitect.anomalies/message "db-stats is not implemented for datomic-client-memdb"})))
33 | (history [_]
34 | (d/history db))
35 | (index-range [_ arg-map]
36 | (wrap-async #(d/index-range db arg-map)))
37 | (pull [_ arg-map]
38 | (wrap-async #(d/pull db arg-map)))
39 | (since [_ t]
40 | (d/since db t))
41 | (with [_ arg-map]
42 | (wrap-async #(d/with db arg-map)))
43 |
44 | async-proto/ParentConnection
45 | (-conn [_] conn)
46 |
47 | clojure.lang.ILookup
48 | (valAt [_ k]
49 | (.valAt db k))
50 | (valAt [_ k not-found]
51 | (.valAt db k not-found)))
52 |
53 | (deftype LocalConnection [conn]
54 | async-proto/Connection
55 | (db [this]
56 | (wrap-async #(LocalDb. this (d/db conn))))
57 | (log [_]
58 | (doto (async/promise-chan)
59 | (async/put! {:cognitect.anomalies/category :cognitect.anomalies/unsupported
60 | :cognitect.anomalies/message "log is not implemented for datomic-client-memdb"})))
61 | (q [_ arg-map]
62 | (wrap-async
63 | #(d/q (update arg-map :args (fn [args]
64 | (map (fn [x]
65 | (if (async-proto/-conn x)
66 | (.-db x)
67 | x))
68 | args))))))
69 | (tx-range [_ arg-map]
70 | (wrap-async #(d/tx-range conn arg-map)))
71 | (transact [_ arg-map]
72 | (wrap-async #(d/transact conn arg-map)))
73 | (recent-db [this]
74 | (async-proto/db this))
75 | (sync [_ t]
76 | (doto (async/promise-chan)
77 | (async/put! {:cognitect.anomalies/category :cognitect.anomalies/unsupported
78 | :cognitect.anomalies/message "recent-db is not implemented for datomic-client-memdb"})))
79 | (with-db [_]
80 | (wrap-async #(d/with-db conn)))
81 |
82 | clojure.lang.ILookup
83 | (valAt [_ k]
84 | (.valAt conn k))
85 | (valAt [_ k not-found]
86 | (.valAt conn k not-found)))
87 |
88 | (defrecord Client [client]
89 | async-proto/Client
90 | (list-databases [_ argm]
91 | (wrap-async #(d/list-databases client argm)))
92 | (connect [_ arg-map]
93 | (wrap-async #(LocalConnection. (d/connect client arg-map))))
94 | (create-database [_ arg-map]
95 | (wrap-async #(d/create-database client arg-map)))
96 | (delete-database [_ arg-map]
97 | (wrap-async #(d/delete-database client arg-map)))
98 | Closeable
99 | (close [_]
100 | (.close client)))
101 |
102 | (defn client
103 | "Returns a Client record that implements the Datomic Client API Protocol. Optionally
104 | takes :db-name-as-uri-fn which is a function that is passed a db-name and is
105 | expected to return a Datomic Peer database URI. Note that this function is passed
106 | a '*' when list-databases is called."
107 | [arg-map]
108 | (map->Client {:client (memdb/client arg-map)}))
--------------------------------------------------------------------------------
/src/compute/datomic_client_memdb/core.clj:
--------------------------------------------------------------------------------
1 | (ns compute.datomic-client-memdb.core
2 | (:require
3 | [datomic.client.api :as client]
4 | [datomic.client.api.protocols :as client-proto]
5 | [datomic.client.api.impl :as client-impl]
6 | [datomic.query.support :as q-support]
7 | [datomic.api :as peer])
8 | (:import (java.io Closeable)))
9 |
10 | (defn- update-vals [m ks f]
11 | (reduce #(update-in % [%2] f) m ks))
12 |
13 | (defn- throw-unsupported
14 | [data]
15 | (throw (ex-info "Unsupported operation."
16 | (merge {:cognitect.anomalies/category :cognitect.anomalies/unsupported}
17 | data))))
18 |
19 | (defn memdb-uri
20 | "Returns a Datomic mem database URI for `db-name`."
21 | [db-name]
22 | (str "datomic:mem://" db-name))
23 |
24 | (defn free-uri
25 | "Returns the default Datomic free database URI for `db-name`."
26 | [db-name]
27 | (str "datomic:free://localhost:4334/" db-name))
28 |
29 | (defn collection-query? [query]
30 | (let [[{fnd :find}] (q-support/parse-as query)]
31 | (or (and (= 2 (count fnd))
32 | (= '. (second fnd)))
33 | (and (vector? (first fnd))
34 | (= 2 (count (first fnd)))
35 | (= '... (second (first fnd)))))))
36 |
37 | (deftype LocalDb [db db-name]
38 | client-proto/Db
39 | (as-of [_ time-point]
40 | (LocalDb. (peer/as-of db time-point) db-name))
41 | (datoms [_ arg-map]
42 | (apply peer/datoms db (:index arg-map) (:components arg-map)))
43 | (db-stats [_]
44 | (throw-unsupported {}))
45 | (history [_]
46 | (LocalDb. (peer/history db) db-name))
47 | (index-range [_ arg-map]
48 | (peer/index-range db (:attrid arg-map) (:start arg-map) (:end arg-map)))
49 | (pull [this arg-map]
50 | (client/pull this (:selector arg-map) (:eid arg-map)))
51 | (pull [_ selector eid]
52 | ;; Datomic free will return nil when pull'ing for a single attribute that does
53 | ;; not exist on an entity. Datomic Cloud returns {} in this case. Since there
54 | ;; isn't any cases known where Datomic Cloud will return nil for a pull, we
55 | ;; simply return an empty map if nil is returned from the Peer API.
56 | (if eid
57 | (or (peer/pull db selector eid) {})
58 | (throw (ex-info "Expected value for :eid"
59 | {:cognitect.anomalies/category :cognitect.anomalies/incorrect
60 | :cognitect.anomalies/message "Expected value for :eid"
61 | :datomic.client.impl.shared.validator/got {:selector selector :eid eid}
62 | :datomic.client.impl.shared.validator/op :pull
63 | :datomic.client.impl.shared.validator/requirements '{:eid value :selector value}}))))
64 | (since [_ t]
65 | (LocalDb. (peer/since db t) db-name))
66 | (with [_ arg-map]
67 | (-> (peer/with db (:tx-data arg-map))
68 | (update :db-before #(LocalDb. % db-name))
69 | (update :db-after #(LocalDb. % db-name))))
70 |
71 | client-impl/Queryable
72 | (q [_ arg-map]
73 | (let [{:keys [query args]} arg-map]
74 | (when (collection-query? query)
75 | (throw (ex-info "Only find-rel elements are allowed in client find-spec, see http://docs.datomic.com/query.html#grammar"
76 | {:cognitect.anomalies/category :cognitect.anomalies/incorrect,
77 | :cognitect.anomalies/message "Only find-rel elements are allowed in client find-spec, see http://docs.datomic.com/query.html#grammar"
78 | :dbs (filterv #(satisfies? client-proto/Db %) args)})))
79 | (apply peer/q query (map (fn [x]
80 | (if (instance? LocalDb x)
81 | (.-db x)
82 | x))
83 | args))))
84 |
85 | clojure.lang.ILookup
86 | (valAt [this k]
87 | (.valAt this k nil))
88 | (valAt [this k not-found]
89 | (case k
90 | :t (peer/basis-t db)
91 | :next-t (inc (:t this))
92 | :db-name db-name
93 | not-found)))
94 |
95 |
96 | (deftype LocalConnection [conn db-name]
97 | client-proto/Connection
98 | (db [_]
99 | (LocalDb. (peer/db conn) db-name))
100 |
101 | (transact [_ arg-map]
102 | (-> @(peer/transact conn (:tx-data arg-map))
103 | (update-vals #{:db-before :db-after} #(LocalDb. % db-name))))
104 |
105 | (tx-range [_ arg-map]
106 | (peer/tx-range (peer/log conn) (:start arg-map) (:end arg-map)))
107 |
108 | (with-db [this]
109 | (client/db this))
110 |
111 | clojure.lang.ILookup
112 | (valAt [this k]
113 | (.valAt this k nil))
114 | (valAt [this k not-found]
115 | (get (client/db this) k not-found)))
116 |
117 |
118 | (defrecord Client [db-name-as-uri-fn]
119 | client-proto/Client
120 | (administer-system [_ arg-map] (throw-unsupported {}))
121 | (list-databases [_ _]
122 | (or (peer/get-database-names (db-name-as-uri-fn "*")) (list)))
123 |
124 | (connect [client arg-map]
125 | (let [db-name (:db-name arg-map)]
126 | (if (contains? (set (client/list-databases client {})) db-name)
127 | (LocalConnection. (peer/connect (db-name-as-uri-fn db-name)) db-name)
128 | (let [msg (format "Unable to find keyfile %s. Make sure that your endpoint and db-name are correct." db-name)]
129 | (throw (ex-info msg {:cognitect.anomalies/category :cognitect.anomalies/not-found
130 | :cognitect.anomalies/message msg}))))))
131 |
132 | (create-database [_ arg-map]
133 | (let [db-name (:db-name arg-map)]
134 | (peer/create-database (db-name-as-uri-fn db-name)))
135 | true)
136 |
137 | (delete-database [_ arg-map]
138 | (peer/delete-database (db-name-as-uri-fn (:db-name arg-map)))
139 | true)
140 |
141 | Closeable
142 | (close [client]
143 | (doseq [db (client/list-databases client {})]
144 | (client/delete-database client {:db-name db}))
145 | nil))
146 |
147 | (defn close
148 | "Cleans up the Datomic Peer Client by deleting all databases."
149 | [client]
150 | (.close client))
151 |
152 | (defn client
153 | "Returns a Client record that implements the Datomic Client API Protocol. Optionally
154 | takes :db-name-as-uri-fn which is a function that is passed a db-name and is
155 | expected to return a Datomic Peer database URI. Note that this function is passed
156 | a '*' when list-databases is called."
157 | [arg-map]
158 | (map->Client {:db-name-as-uri-fn (or (:db-name-as-uri-fn arg-map) memdb-uri)}))
159 |
--------------------------------------------------------------------------------
/test/compute/datomic_client_memdb/async_test.clj:
--------------------------------------------------------------------------------
1 | (ns compute.datomic-client-memdb.async-test
2 | (:require
3 | [clojure.test :refer :all]
4 | [clojure.core.async :as async]
5 | [datomic.client.api.async :as d.async]
6 | [datomic.client.api.protocols :as client-proto]
7 | [datomic.client.impl.shared.protocols :as async-proto]
8 | [compute.datomic-client-memdb.core :as memdb]
9 | [compute.datomic-client-memdb.async :as memdb.async])
10 | (:import (clojure.lang ExceptionInfo)))
11 |
12 | (def ^:dynamic *client* nil)
13 |
14 | (defn client-fixture
15 | [f]
16 | (with-open [c (memdb.async/client {})]
17 | (binding [*client* c]
18 | (f))))
19 |
20 | (defn anom->ex
21 | [anom]
22 | (let [cat (:cognitect.anomalies/category anom)
23 | default-msg (str "A " cat " anomaly occurred.")
24 | ex-msg (or (:cognitect.anomalies/message anom) default-msg)]
25 | (if-let [ex (:ex anom)]
26 | (ex-info ex-msg anom ex)
27 | (ex-info ex-msg anom))))
28 |
29 | (defn ex v))
36 | v)
37 | ::timeout))))
38 |
39 | (use-fixtures :each client-fixture)
40 |
41 | (deftest client-test
42 | (is (= true ( conn (d.async/tx-range {}) ( conn (d/tx-range {}) (first) (keys) (set)))))
50 | (testing "with-db is a db"
51 | (is (satisfies? client-proto/Db (d/with-db conn))))
52 | (testing "conn info works"
53 | (is (every? some? (map #(get conn %) [:t :next-t :db-name]))))))
54 |
55 | (deftest db-test
56 | (d/create-database *client* {:db-name "test"})
57 | (let [conn (d/connect *client* {:db-name "test"})
58 | _ (d/transact conn {:tx-data test-schema})
59 | tx-report (d/transact conn {:tx-data [{:db/id "bob"
60 | :user/name "bob"}]})
61 | bob-id (get-in tx-report [:tempids "bob"])
62 | db (d/db conn)]
63 | (testing "query works"
64 | (is (= #{[bob-id]}
65 | (d/q '[:find ?e
66 | :where
67 | [?e :user/name "bob"]] db))))
68 | (testing "collection queries throw"
69 | (is (thrown? ExceptionInfo (d/q '[:find [(pull ?e [*]) ...]
70 | :where [?e :user/name]]
71 | db)))
72 | (is (thrown? ExceptionInfo (d/q '[:find ?e .
73 | :where [?e :user/name "bob"]]
74 | db))))
75 | (testing "pull works"
76 | (is (= {:user/name "bob"}
77 | (d/pull db '[:user/name] bob-id)))
78 | (is (= {}
79 | (d/pull db '[:user/i-dont-exist] bob-id))
80 | "nonexistent attribute results in empty map, not nil")
81 | (is (thrown? ExceptionInfo (d/pull db [:user/name] nil))
82 | "exception thrown when pulling nil eid"))
83 | (testing "db info works"
84 | (is (every? some? (map #(get db %) [:t :next-t :db-name])))
85 | (is (:t (last (d/tx-range conn {})))
86 | (:t conn)))
87 | (testing "db with works"
88 | (let [tx-report (d/with db {:tx-data [{:user/name "bob5"}]})]
89 | (is (:db-before tx-report))
90 | (is (:db-after tx-report))
91 | (is (:tempids tx-report))
92 | (is (:tx-data tx-report))))
93 | (testing "index-range works"
94 | (let [[_ _ value] (first (d/index-range db {:attrid [:db/ident :user/name]}))]
95 | (is (= value "bob"))))))
96 |
97 |
--------------------------------------------------------------------------------