Compare commits
No commits in common. "master" and "v0.3.1" have entirely different histories.
13 changed files with 512 additions and 1096 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -9,5 +9,3 @@ pom.xml.asc
|
|||
/.nrepl-port
|
||||
.hgignore
|
||||
.hg/
|
||||
.lsp/
|
||||
.clj-kondo/
|
||||
|
|
|
|||
44
CHANGELOG.md
44
CHANGELOG.md
|
|
@ -1,44 +1,11 @@
|
|||
# Change Log
|
||||
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.8.0 - 2024-02-07
|
||||
### Added
|
||||
- Support for passing `:realise-fn` to avoid lazy response (thanks @jimpil)
|
||||
|
||||
### Changed
|
||||
- Remove reflective calls
|
||||
|
||||
## 0.7.0 - 2022-10-04
|
||||
### Added
|
||||
- Support reading dates as instanceres (thanks @henryw374)
|
||||
- Support data literal for mongo id (thanks @henryw374)
|
||||
- Support for implicit transactions (thanks @AdamClements)
|
||||
- Support for aggregation pipeline in find-one-and-update (requires 4.2+, thanks @jacobemcken)
|
||||
|
||||
## 0.6.0 - 2020-01-10
|
||||
### Added
|
||||
- Support for bulk-write
|
||||
|
||||
### Changed
|
||||
- Moved option creators and document conversion to the `mongo-driver-3.model` namespace (breaking change)
|
||||
|
||||
## 0.5.0 - 2019-11-22
|
||||
### Added
|
||||
- Support for transactions
|
||||
|
||||
## 0.4.0 - 2019-11-19
|
||||
### Added
|
||||
- list collections
|
||||
- start session
|
||||
- remove reflection warnings
|
||||
|
||||
## 0.3.1 - 2019-11-17
|
||||
## [0.3.1]
|
||||
### Added
|
||||
- More documentation
|
||||
|
||||
## 0.3.0 - 2019-11-15
|
||||
## [0.3.0] - 2019-11-15
|
||||
### Added
|
||||
- Added aggregate function
|
||||
- `skip` option to `find`
|
||||
|
|
@ -49,10 +16,15 @@ All notable changes to this project will be documented in this file. This change
|
|||
- Added ? suffix to boolean params
|
||||
- Renamed `find-one-as-map` to `find-one`
|
||||
|
||||
## 0.2.0 - 2019-11-14
|
||||
## [0.2.0] - 2019-11-14
|
||||
### Added
|
||||
- expose operators
|
||||
|
||||
## 0.1.0 - 2019-11-14
|
||||
### Added
|
||||
- Initial release
|
||||
|
||||
[Unreleased]: https://github.com/gnarroway/mongo-driver-3/compare/v0.3.1...HEAD
|
||||
[0.3.1]: https://github.com/gnarroway/mongo-driver-3/compare/v0.3.0...v0.3.1
|
||||
[0.3.0]: https://github.com/gnarroway/mongo-driver-3/compare/v0.2.0...v0.3.0
|
||||
[0.2.0]: https://github.com/gnarroway/hato/compare/v0.1.0...v0.2.0
|
||||
|
|
|
|||
107
README.md
107
README.md
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
[](https://clojars.org/mongo-driver-3)
|
||||
|
||||
[](https://cljdoc.org/d/mongo-driver-3/mongo-driver-3/CURRENT)
|
||||
[](https://cljdoc.org/d/manifold/manifold/CURRENT)
|
||||
|
||||
|
||||
A Mongo client for clojure, lightly wrapping 3.11/4.0+ versions of the [MongoDB Java Driver](https://mongodb.github.io/mongo-java-driver/)
|
||||
A Mongo client for clojure, lightly wrapping 3.11+ versions of the [MongoDB Java Driver](https://mongodb.github.io/mongo-java-driver/)
|
||||
|
||||
In general, it will feel familiar to users of mongo clients like [monger](https://github.com/michaelklishin/monger).
|
||||
Like our HTTP/2 client [hato](https://github.com/gnarroway/hato), the API is designed to be idiomatic and to make common
|
||||
|
|
@ -23,7 +23,7 @@ It was developed with the following goals:
|
|||
|
||||
## Status
|
||||
|
||||
mongo-driver-3 is used in production, and the existing public API will be maintained.
|
||||
mongo-driver-3 is under active development and the API may change.
|
||||
Please try it out and raise any issues you may find.
|
||||
|
||||
## Usage
|
||||
|
|
@ -32,23 +32,21 @@ For Leinengen, add this to your project.clj:
|
|||
|
||||
```clojure
|
||||
;; The underlying driver -- any newer version can also be used
|
||||
[org.mongodb/mongodb-driver-sync "4.11.1"]
|
||||
[org.mongodb/mongodb-driver-sync "3.11.2"]
|
||||
|
||||
;; This wrapper library
|
||||
[mongo-driver-3 "0.8.0"]
|
||||
[mongo-driver-3 "0.3.1"]
|
||||
```
|
||||
|
||||
## Getting started
|
||||
|
||||
```clojure
|
||||
(ns my.app
|
||||
(:require [mongo-driver-3.client :as mcl]))
|
||||
```
|
||||
|
||||
We usually start by creating a client and connecting to a database with a connection string.
|
||||
`connect-to-db` is a convenience function that allows you to do this directly.
|
||||
|
||||
```clojure
|
||||
(ns my.app
|
||||
(:require [mongo-driver-3.client :as mcl]))
|
||||
|
||||
(mcl/connect-to-db "mongodb://localhost:27017/my-db")
|
||||
; =>
|
||||
; {
|
||||
|
|
@ -57,19 +55,19 @@ We usually start by creating a client and connecting to a database with a connec
|
|||
; }
|
||||
```
|
||||
|
||||
You can also create a client and get a DB separately:
|
||||
You can also create a client and get a DB manually:
|
||||
|
||||
```clojure
|
||||
;; Calling create without an arg will try and connect to the default host/port.
|
||||
(def client (mcl/create "mongodb://localhost:27017"))
|
||||
|
||||
;; Create a db that you can pass around.
|
||||
(def db (mcl/get-db client "my-db"))
|
||||
(def db (mcl/get-db client "my-db:))
|
||||
```
|
||||
|
||||
### Collection functions
|
||||
|
||||
All the collection functions closely mirror the naming in the corresponding java driver
|
||||
All the collection functions closely mirror the name of the corresponding java driver
|
||||
[module](https://mongodb.github.io/mongo-java-driver/3.11/javadoc/com/mongodb/client/MongoCollection.html).
|
||||
|
||||
They always take a db as the first argument, collection name as the second,
|
||||
|
|
@ -94,25 +92,20 @@ As an example:
|
|||
; => 1
|
||||
|
||||
;; Find the documents, returning a seq
|
||||
(mc/find db "test" {} {:limit 1 :projection {:_id 0}})
|
||||
(mc/find db "test' {} {:limit 1 :projection {:_id 0}})
|
||||
; => ({:v "hello"})
|
||||
|
||||
;; Find the documents, returning the raw FindIterable response
|
||||
(mc/find db "test" {} {:raw? true})
|
||||
(mc/find db "test' {} {:raw? true})
|
||||
; => a MongoIterable
|
||||
|
||||
;; Find a single document or return nil
|
||||
(mc/find-one db "test" {:v "world"} {:keywordize? false})
|
||||
(mc/find-one db "test' {:v "world"} {:keywordize? false})
|
||||
; => {"v" "world"}
|
||||
|
||||
;; Avoid laziness in queries
|
||||
(mc/find db "test" {} {:realise-fn (partial into [])}
|
||||
; => [...]
|
||||
```
|
||||
|
||||
While most options are supported directly, sometimes you may need to some extra control.
|
||||
In such cases, you can pass in a configured java options object. Any other
|
||||
options will be applied on top of this object.
|
||||
While most options are supported directly, sometimes you may need to configure an operation directly.
|
||||
In such cases, you can pass in the java options object.
|
||||
|
||||
```clojure
|
||||
;; These are equivalent
|
||||
|
|
@ -124,74 +117,6 @@ options will be applied on top of this object.
|
|||
Again, read the [docs](https://cljdoc.org/d/mongo-driver-3/mongo-driver-3/CURRENT/api/mongo-driver-3.collection)
|
||||
for full API documentation.
|
||||
|
||||
### Using operators
|
||||
|
||||
Many mongo queries take operators like `$eq` and `$gt`. These are exposed in the `mongo-driver-3.operator` namespace.
|
||||
|
||||
```clojure
|
||||
(ns my.app
|
||||
(:require [mongo-driver-3.collection :as mc]
|
||||
[mongo-driver-3.operator :refer [$gt]))
|
||||
|
||||
(mc/find db "test" {:a {$gt 3}})
|
||||
|
||||
;; This is equivalent to, but with less chance of error than:
|
||||
(mc/find db "test" {:a {"$gt" 3}})
|
||||
```
|
||||
|
||||
### Bulk operations
|
||||
|
||||
The bulk API is similar to the [mongo shell](https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/),
|
||||
except each operation is defined as a 2-tuple rather than a map.
|
||||
|
||||
```clojure
|
||||
;; Execute a mix of operations in one go
|
||||
(bulk-write [[:insert-one {:document {:a 1}}]
|
||||
[:delete-one {:filter {:a 1}}]
|
||||
[:delete-many {:filter {:a 1}}]
|
||||
[:update-one {:filter {:a 1} :update {:$set {:a 2}}}]
|
||||
[:update-many {:filter {:a 1} :update {:$set {:a 2}}}]
|
||||
[:replace-one {:filter {:a 1} :replacement {:a 2}}]])
|
||||
; => a BulkWriteResult
|
||||
|
||||
;; Each operation can take the same options as their respective functions
|
||||
(bulk-write [[:update-one {:filter {:a 1} :update {:$set {:a 2}} :upsert? true}]
|
||||
[:update-many {:filter {:a 1} :update {:$set {:a 2}} :upsert? true}]
|
||||
[:replace-one {:filter {:a 1} :replacement {:a 2} :upsert? true}]])
|
||||
```
|
||||
|
||||
### Using transactions
|
||||
|
||||
You can create a session to perform multi-document transactions, where all operations either
|
||||
succeed or none are persisted.
|
||||
|
||||
It is important to
|
||||
use `with-open` so the session is closed after both successful and failed transactions.
|
||||
|
||||
```clojure
|
||||
;; Inserts 2 documents into a collection
|
||||
(with-open [s (mg/start-session client)]
|
||||
(mg/with-transaction s
|
||||
(fn []
|
||||
(mc/insert-one my-db "coll" {:name "hello"} {:session s})
|
||||
(mc/insert-one my-db "coll" {:name "world"} {:session s}))))
|
||||
|
||||
;; There is also a helper method to make this easier,
|
||||
;; where it is not necessary to manually open or pass a session:
|
||||
(mg/with-implicit-transaction
|
||||
{:client client}
|
||||
(fn []
|
||||
(mc/insert-one my-db "coll" {:name "hello"})
|
||||
(mc/insert-one my-db "coll" {:name "world"})))
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
1. Run mongo (e.g. via docker):
|
||||
- `docker run -it --rm -p 27017:27017 mongo`
|
||||
2. Run tests
|
||||
- `lein test`
|
||||
|
||||
## License
|
||||
|
||||
Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
(defproject mongo-driver-3 "0.9.0-SNAPSHOT"
|
||||
:description "A Clojure wrapper for the Java MongoDB driver 3.11/4.0+."
|
||||
(defproject mongo-driver-3 "0.3.1"
|
||||
:description "A Clojure wrapper for the Java MongoDB driver 3.11+."
|
||||
:url "https://github.com/gnarroway/mongo-driver-3"
|
||||
:license {:name "The MIT License"
|
||||
:url "http://opensource.org/licenses/mit-license.php"
|
||||
|
|
@ -10,5 +10,5 @@
|
|||
:sign-releases false}]]
|
||||
:plugins [[lein-cljfmt "0.6.4"]]
|
||||
|
||||
:profiles {:dev {:dependencies [[org.clojure/clojure "1.11.1"]
|
||||
[org.mongodb/mongodb-driver-sync "4.11.1"]]}})
|
||||
:profiles {:dev {:dependencies [[org.clojure/clojure "1.10.1"]
|
||||
[org.mongodb/mongodb-driver-sync "3.11.0"]]}})
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
{mongo/id mongo-driver-3.data-literals/mongo-id}
|
||||
|
|
@ -1,12 +1,7 @@
|
|||
(ns mongo-driver-3.client
|
||||
(:refer-clojure :exclude [find])
|
||||
(:require [mongo-driver-3.model :as m]
|
||||
[mongo-driver-3.iterable :as iterable])
|
||||
(:import (com.mongodb.client MongoClients MongoClient ClientSession MongoDatabase TransactionBody)
|
||||
(com.mongodb ConnectionString ClientSessionOptions TransactionOptions)
|
||||
(java.util.concurrent TimeUnit)))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
(:import (com.mongodb.client MongoClients MongoClient)
|
||||
(com.mongodb ConnectionString)))
|
||||
|
||||
;;; Core
|
||||
|
||||
|
|
@ -16,15 +11,16 @@
|
|||
`connection-string` is a mongo connection string, e.g. mongodb://localhost:27107
|
||||
|
||||
If a connecting string is not passed in, it will connect to the default localhost instance."
|
||||
(^MongoClient [] (MongoClients/create))
|
||||
(^MongoClient [^String connection-string] (MongoClients/create connection-string)))
|
||||
([] (MongoClients/create))
|
||||
([^String connection-string]
|
||||
(MongoClients/create connection-string)))
|
||||
|
||||
(defn get-db
|
||||
"Gets a database by name
|
||||
|
||||
`client` is a MongoClient, e.g. resulting from calling `connect`
|
||||
`name` is the name of the database to get."
|
||||
^MongoDatabase [^MongoClient client ^String name]
|
||||
[^MongoClient client ^String name]
|
||||
(.getDatabase client name))
|
||||
|
||||
(defn close
|
||||
|
|
@ -32,154 +28,6 @@
|
|||
[^MongoClient client]
|
||||
(.close client))
|
||||
|
||||
(defn list-collections
|
||||
"Lists collections in a database, returning as a seq of maps unless otherwise configured.
|
||||
|
||||
Arguments:
|
||||
|
||||
- `db` a MongoDatabase
|
||||
- `opts` (optional), a map of:
|
||||
- `:name-only?` returns just the string names
|
||||
- `:keywordize?` keywordize the keys of return results, default: true. Only applicable if `:name-only?` is false.
|
||||
- `:raw?` return the mongo iterable directly instead of processing into a clj data-structure, default: false
|
||||
- `:realise-fn` how to realise the MongoIterable, default: `clojure.core/sequence` (i.e. lazily)
|
||||
- `:session` a ClientSession"
|
||||
([^MongoDatabase db] (list-collections db {}))
|
||||
([^MongoDatabase db {:keys [raw? keywordize? ^ClientSession session realise-fn]
|
||||
:or {keywordize? true
|
||||
realise-fn sequence}}]
|
||||
(let [it (if session
|
||||
(.listCollections db session)
|
||||
(.listCollections db))]
|
||||
(if raw?
|
||||
it
|
||||
(realise-fn ;; accomodate users who don't want to use lazy-seqs
|
||||
(iterable/documents it keywordize?))))))
|
||||
|
||||
(defn list-collection-names
|
||||
"Lists collection names in a database, returning as a seq of strings unless otherwise configured.
|
||||
|
||||
Arguments:
|
||||
|
||||
- `db` a MongoDatabase
|
||||
- `opts` (optional), a map of:
|
||||
- `:raw?` return the mongo MongoIterable directly instead of processing into a seq, default: false
|
||||
- `:session` a ClientSession"
|
||||
([^MongoDatabase db] (list-collection-names db {}))
|
||||
([^MongoDatabase db opts]
|
||||
(let [it (if-let [^ClientSession session (:session opts)]
|
||||
(.listCollectionNames db session)
|
||||
(.listCollectionNames db))]
|
||||
(if (:raw? opts)
|
||||
it
|
||||
(seq it)))))
|
||||
|
||||
(defn ->TransactionOptions
|
||||
"Coerces options map into a TransactionOptions. See `start-session` for usage."
|
||||
[{:keys [max-commit-time-ms] :as opts}]
|
||||
(let [rp (m/->ReadPreference opts)
|
||||
rc (m/->ReadConcern opts)
|
||||
wc (m/->WriteConcern opts)]
|
||||
|
||||
(cond-> (TransactionOptions/builder)
|
||||
max-commit-time-ms (.maxCommitTime max-commit-time-ms (TimeUnit/MILLISECONDS))
|
||||
rp (.readPreference rp)
|
||||
rc (.readConcern rc)
|
||||
wc (.writeConcern wc)
|
||||
true (.build))))
|
||||
|
||||
(defn ->ClientSessionOptions
|
||||
"Coerces an options map into a ClientSessionOptions See `start-session` for usage.
|
||||
|
||||
See `start-session` for usage"
|
||||
[{:keys [client-session-options causally-consistent?] :as opts}]
|
||||
(let [trans-opts (->TransactionOptions opts)]
|
||||
(cond-> (if client-session-options (ClientSessionOptions/builder client-session-options) (ClientSessionOptions/builder))
|
||||
trans-opts (.defaultTransactionOptions trans-opts)
|
||||
(some? causally-consistent?) (.causallyConsistent causally-consistent?)
|
||||
true (.build))))
|
||||
|
||||
(defn start-session
|
||||
"Creates a client session.
|
||||
|
||||
Arguments:
|
||||
|
||||
- `client` a MongoClient
|
||||
- `opts` (optional), a map of:
|
||||
- `:max-commit-time-ms` Max execution time for commitTransaction operation, in milliseconds
|
||||
- `:causally-consistent?` whether operations using session should be causally consistent with each other
|
||||
- `:read-preference` Accepts a ReadPreference or a kw corresponding to one:
|
||||
[:primary, :primaryPreferred, :secondary, :secondaryPreferred, :nearest]
|
||||
Invalid values will throw an exception.
|
||||
- `:read-concern` Accepts a ReadConcern or kw corresponding to one:
|
||||
[:available, :default, :linearizable, :local, :majority, :snapshot]
|
||||
Invalid values will throw an exception.
|
||||
- `:write-concern` A WriteConcern or kw corresponding to one:
|
||||
[:acknowledged, :journaled, :majority, :unacknowledged, :w1, :w2, :w3],
|
||||
defaulting to :acknowledged, if some invalid option is provided.
|
||||
- `:write-concern/w` an int >= 0, controlling the number of replicas to acknowledge
|
||||
- `:write-concern/w-timeout-ms` How long to wait for secondaries to acknowledge before failing,
|
||||
in milliseconds (0 means indefinite).
|
||||
- `:write-concern/journal?` If true, block until write operations have been committed to the journal.
|
||||
- `:client-session-options` a ClientSessionOptions, for configuring directly. If specified, any
|
||||
other [preceding] query options will be applied to it."
|
||||
([^MongoClient client] (start-session client {}))
|
||||
([^MongoClient client opts]
|
||||
(.startSession client (->ClientSessionOptions opts))))
|
||||
|
||||
(defn with-transaction
|
||||
"Executes `body` in a transaction.
|
||||
|
||||
`body` should be a fn with one or more mongo operations in it.
|
||||
Ensure `session` is passed as an option to each operation.
|
||||
|
||||
e.g.
|
||||
|
||||
```clojure
|
||||
(with-open [s (start-session client)]
|
||||
(with-transaction s
|
||||
(fn []
|
||||
(insert-one my-db \"coll\" {:name \"hello\"} {:session s})
|
||||
(insert-one my-db \"coll\" {:name \"world\"} {:session s}))))
|
||||
```"
|
||||
([^ClientSession session body] (with-transaction session body {}))
|
||||
([^ClientSession session body opts]
|
||||
(.withTransaction session
|
||||
(reify TransactionBody
|
||||
(execute [_] (body)))
|
||||
(->TransactionOptions opts))))
|
||||
|
||||
|
||||
(def ^:dynamic *session* nil)
|
||||
|
||||
(defn with-implicit-transaction
|
||||
"Automatically sets the session / transaction for all mongo operations
|
||||
within the scope, using a dynamic binding.
|
||||
|
||||
The first argument is an options map with keys:
|
||||
|
||||
- `:client` a MongoClient (mandatory)
|
||||
- `:transaction-opts` (see `->TransactionOptions` for keys)
|
||||
- `:session-opts` (see `start-session` for details)
|
||||
|
||||
The second argument `body` is a fn with one or more mongo operations in it.
|
||||
e.g.
|
||||
|
||||
```
|
||||
(mg/with-implicit-transaction
|
||||
{:client client}
|
||||
(fn []
|
||||
(mc/insert-one my-db \"coll\" {:name \"hello\"})
|
||||
(mc/insert-one my-db \"coll\" {:name \"world\"})))
|
||||
```"
|
||||
[{:keys [^MongoClient client transaction-opts session-opts] :or {transaction-opts {} session-opts {}}} body]
|
||||
(with-open [^ClientSession session (start-session client session-opts)]
|
||||
(binding [*session* session]
|
||||
(with-transaction
|
||||
*session*
|
||||
body
|
||||
transaction-opts))))
|
||||
|
||||
;;; Utility
|
||||
|
||||
(defn connect-to-db
|
||||
|
|
|
|||
|
|
@ -1,19 +1,122 @@
|
|||
(ns mongo-driver-3.collection
|
||||
(:refer-clojure :exclude [find empty? drop])
|
||||
(:require [mongo-driver-3.model :refer :all]
|
||||
[mongo-driver-3.client :refer [*session*]]
|
||||
[mongo-driver-3.iterable :as iterable])
|
||||
(:import (com.mongodb MongoNamespace)
|
||||
(com.mongodb.client MongoDatabase MongoCollection ClientSession)
|
||||
(com.mongodb.client.model IndexModel)
|
||||
(java.util List)
|
||||
(org.bson Document)))
|
||||
(:import (clojure.lang Ratio Keyword Named IPersistentMap)
|
||||
(com.mongodb ReadConcern ReadPreference WriteConcern MongoNamespace)
|
||||
(com.mongodb.client MongoDatabase MongoCollection TransactionBody)
|
||||
(com.mongodb.client.model InsertOneOptions InsertManyOptions DeleteOptions FindOneAndUpdateOptions ReturnDocument FindOneAndReplaceOptions CountOptions CreateCollectionOptions RenameCollectionOptions IndexOptions IndexModel UpdateOptions ReplaceOptions)
|
||||
(java.util List Collection)
|
||||
(java.util.concurrent TimeUnit)
|
||||
(org.bson Document)
|
||||
(org.bson.types Decimal128)))
|
||||
|
||||
;;; Conversions
|
||||
|
||||
(defprotocol ConvertToDocument
|
||||
(^Document document [input] "Convert from clojure to Mongo Document"))
|
||||
|
||||
(extend-protocol ConvertToDocument
|
||||
nil
|
||||
(document [_]
|
||||
nil)
|
||||
|
||||
Ratio
|
||||
(document [^Ratio input]
|
||||
(double input))
|
||||
|
||||
Keyword
|
||||
(document [^Keyword input]
|
||||
(.getName input))
|
||||
|
||||
Named
|
||||
(document [^Named input]
|
||||
(.getName input))
|
||||
|
||||
IPersistentMap
|
||||
(document [^IPersistentMap input]
|
||||
(let [o (Document.)]
|
||||
(doseq [[k v] input]
|
||||
(.append o (document k) (document v)))
|
||||
o))
|
||||
|
||||
Collection
|
||||
(document [^Collection input]
|
||||
(map document input))
|
||||
|
||||
Object
|
||||
(document [input]
|
||||
input))
|
||||
|
||||
(defprotocol ConvertFromDocument
|
||||
(from-document [input keywordize?] "Converts Mongo Document to Clojure"))
|
||||
|
||||
(extend-protocol ConvertFromDocument
|
||||
nil
|
||||
(from-document [input _]
|
||||
input)
|
||||
|
||||
Object
|
||||
(from-document [input _] input)
|
||||
|
||||
Decimal128
|
||||
(from-document [^Decimal128 input _]
|
||||
(.bigDecimalValue input))
|
||||
|
||||
List
|
||||
(from-document [^List input keywordize?]
|
||||
(vec (map #(from-document % keywordize?) input)))
|
||||
|
||||
Document
|
||||
(from-document [^Document input keywordize?]
|
||||
(reduce (if keywordize?
|
||||
(fn [m ^String k]
|
||||
(assoc m (keyword k) (from-document (.get input k) true)))
|
||||
(fn [m ^String k]
|
||||
(assoc m k (from-document (.get input k) false))))
|
||||
{} (.keySet input))))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
;;; Collection
|
||||
|
||||
(defn ^MongoCollection collection
|
||||
|
||||
(def kw->ReadConcern
|
||||
{:available (ReadConcern/AVAILABLE)
|
||||
:default (ReadConcern/DEFAULT)
|
||||
:linearizable (ReadConcern/LINEARIZABLE)
|
||||
:local (ReadConcern/LOCAL)
|
||||
:majority (ReadConcern/MAJORITY)
|
||||
:snapshot (ReadConcern/SNAPSHOT)})
|
||||
|
||||
(defn ->ReadConcern
|
||||
"Coerce `rc` into a ReadConcern if not nil. See `collection` for usage."
|
||||
[rc]
|
||||
(when rc
|
||||
(if (instance? ReadConcern rc)
|
||||
rc
|
||||
(or (kw->ReadConcern rc) (throw (IllegalArgumentException.
|
||||
(str "No match for read concern of " (name rc))))))))
|
||||
|
||||
(defn ->ReadPreference
|
||||
"Coerce `rp` into a ReadPreference if not nil. See `collection` for usage."
|
||||
[rp]
|
||||
(when rp
|
||||
(if (instance? ReadPreference rp)
|
||||
rp
|
||||
(ReadPreference/valueOf (name rp)))))
|
||||
|
||||
(defn ->WriteConcern
|
||||
"Coerces write-concern related options to a WriteConcern. See `collection` for usage."
|
||||
[{:keys [write-concern write-concern/w write-concern/w-timeout-ms write-concern/journal?]}]
|
||||
(when (some some? [write-concern w w-timeout-ms journal?])
|
||||
(let [wc (when write-concern
|
||||
(if (instance? WriteConcern write-concern)
|
||||
write-concern
|
||||
(WriteConcern/valueOf (name write-concern))))]
|
||||
(-> (or wc (WriteConcern/ACKNOWLEDGED))
|
||||
(#(if w (.withW % w) %))
|
||||
(#(if w-timeout-ms (.withWTimeout % w-timeout-ms (TimeUnit/MILLISECONDS)) %))
|
||||
(#(if (some? journal?) (.withJournal % journal?) %))))))
|
||||
|
||||
(defn collection
|
||||
"Coerces `coll` to a MongoCollection with some options.
|
||||
|
||||
Arguments:
|
||||
|
|
@ -38,14 +141,12 @@
|
|||
([^MongoDatabase db coll]
|
||||
(collection db coll {}))
|
||||
([^MongoDatabase db coll opts]
|
||||
(let [^MongoCollection coll' (if (instance? MongoCollection coll) coll (.getCollection db coll))
|
||||
rp (->ReadPreference opts)
|
||||
rc (->ReadConcern opts)
|
||||
wc (->WriteConcern opts)]
|
||||
(cond-> ^MongoCollection coll'
|
||||
rp (.withReadPreference rp)
|
||||
rc (.withReadConcern rc)
|
||||
wc (.withWriteConcern wc)))))
|
||||
(let [coll' (if (instance? MongoCollection coll) coll (.getCollection db coll))
|
||||
{:keys [read-concern read-preference]} opts]
|
||||
(-> coll'
|
||||
(#(if-let [rp (->ReadPreference read-preference)] (.withReadPreference % rp) %))
|
||||
(#(if-let [rc (->ReadConcern read-concern)] (.withReadConcern % rc) %))
|
||||
(#(if-let [wc (->WriteConcern opts)] (.withWriteConcern % wc) %))))))
|
||||
|
||||
;;; CRUD functions
|
||||
|
||||
|
|
@ -63,55 +164,33 @@
|
|||
- `:batch-size` Documents to return per batch, e.g. 1
|
||||
- `:bypass-document-validation?` Boolean
|
||||
- `:keywordize?` keywordize the keys of return results, default: true
|
||||
- `:realise-fn` how to realise the MongoIterable, default: `clojure.core/sequence` (i.e. lazily)
|
||||
- `:raw?` return the mongo AggregateIterable directly instead of processing into a seq, default: false
|
||||
- `:session` a ClientSession"
|
||||
([^MongoDatabase db coll pipeline]
|
||||
(aggregate db coll pipeline {}))
|
||||
([^MongoDatabase db coll pipeline opts]
|
||||
(let [{:keys [^ClientSession session allow-disk-use? ^Integer batch-size bypass-document-validation? keywordize? raw? realise-fn]
|
||||
:or {keywordize? true
|
||||
realise-fn sequence}} opts
|
||||
^ClientSession session (or session *session*)
|
||||
it (cond-> (if session
|
||||
(.aggregate (collection db coll opts) session ^List (map document pipeline))
|
||||
(.aggregate (collection db coll opts) ^List (map document pipeline)))
|
||||
(some? allow-disk-use?) (.allowDiskUse allow-disk-use?)
|
||||
(some? bypass-document-validation?) (.bypassDocumentValidation bypass-document-validation?)
|
||||
batch-size (.batchSize batch-size))]
|
||||
(let [{:keys [session allow-disk-use? batch-size bypass-document-validation? keywordize? raw?] :or {keywordize? true raw? false}} opts
|
||||
it (-> (if session
|
||||
(.aggregate (collection db coll opts) session (document pipeline))
|
||||
(.aggregate (collection db coll opts) (document pipeline)))
|
||||
(#(if (some? allow-disk-use?) (.allowDiskUse % allow-disk-use?) %))
|
||||
(#(if batch-size (.batchSize % batch-size) %))
|
||||
(#(if (some? bypass-document-validation?) (.bypassDocumentValidation % bypass-document-validation?) %)))]
|
||||
|
||||
(if raw?
|
||||
it
|
||||
(realise-fn ;; accomodate users who don't want to use lazy-seqs
|
||||
(iterable/documents it keywordize?))))))
|
||||
(if-not raw?
|
||||
(map (fn [x] (from-document x keywordize?)) (seq it))
|
||||
it))))
|
||||
|
||||
(defn bulk-write
|
||||
"Executes a mix of inserts, updates, replaces, and deletes.
|
||||
(defn ->CountOptions
|
||||
"Coerce options map into CountOptions. See `count-documents` for usage."
|
||||
[{:keys [count-options hint limit max-time-ms skip]}]
|
||||
(let [opts (or count-options (CountOptions.))]
|
||||
(when hint (.hint opts (document hint)))
|
||||
(when limit (.limit opts limit))
|
||||
(when max-time-ms (.maxTime opts max-time-ms (TimeUnit/MILLISECONDS)))
|
||||
(when skip (.skip opts skip))
|
||||
|
||||
- `db` is a MongoDatabase
|
||||
- `coll` is a collection name
|
||||
- `operations` a list of 2-tuples in the form `[op config]`,
|
||||
- `op` is one of :insert-one :update-one :update-many :delete-one :delete-many :replace-one
|
||||
- `config` the configuration map for the operation
|
||||
- `insert` takes `:document`
|
||||
- `update` takes `:filter`, `:update`, and any options in the corresponding update function
|
||||
- `delete` takes `:filter`, and any options in the corresponding delete function
|
||||
- `replace` takes `:filter`, `:replacement`, and any options in the corresponding replace function
|
||||
- `opts` (optional), a map of:
|
||||
- `:bypass-document-validation?` Boolean
|
||||
- `:ordered?` Boolean whether serve should insert documents in order provided (default true)
|
||||
- `:bulk-write-options` A BulkWriteOptions for configuring directly. If specified,
|
||||
any other [preceding] query options will be applied to it.
|
||||
- `:session` A ClientSession
|
||||
|
||||
Additionally takes options specified in `collection`"
|
||||
([^MongoDatabase db coll operations]
|
||||
(bulk-write db coll operations {}))
|
||||
([^MongoDatabase db coll operations opts]
|
||||
(let [opts' (->BulkWriteOptions opts)]
|
||||
(if-let [session (or (:session opts) *session*)]
|
||||
(.bulkWrite (collection db coll opts') ^ClientSession session ^List (map write-model operations))
|
||||
(.bulkWrite (collection db coll opts') (map write-model operations))))))
|
||||
opts))
|
||||
|
||||
(defn count-documents
|
||||
"Count documents in a collection, optionally matching a filter query `q`.
|
||||
|
|
@ -137,10 +216,16 @@
|
|||
(count-documents db coll q {}))
|
||||
([^MongoDatabase db coll q opts]
|
||||
(let [opts' (->CountOptions opts)]
|
||||
(if-let [session (or (:session opts) *session*)]
|
||||
(if-let [session (:session opts)]
|
||||
(.countDocuments (collection db coll opts) session (document q) opts')
|
||||
(.countDocuments (collection db coll opts) (document q) opts')))))
|
||||
|
||||
(defn ->DeleteOptions
|
||||
"Coerce options map into DeleteOptions. See `delete-one` and `delete-many` for usage."
|
||||
[{:keys [delete-options]}]
|
||||
(let [opts (or delete-options (DeleteOptions.))]
|
||||
opts))
|
||||
|
||||
(defn delete-one
|
||||
"Deletes a single document from a collection and returns a DeleteResult.
|
||||
|
||||
|
|
@ -157,7 +242,7 @@
|
|||
([^MongoDatabase db coll q]
|
||||
(delete-one db coll q {}))
|
||||
([^MongoDatabase db coll q opts]
|
||||
(if-let [session (or (:session opts) *session*)]
|
||||
(if-let [session (:session opts)]
|
||||
(.deleteOne (collection db coll opts) session (document q) (->DeleteOptions opts))
|
||||
(.deleteOne (collection db coll opts) (document q) (->DeleteOptions opts)))))
|
||||
|
||||
|
|
@ -177,7 +262,7 @@
|
|||
([^MongoDatabase db coll q]
|
||||
(delete-many db coll q {}))
|
||||
([^MongoDatabase db coll q opts]
|
||||
(if-let [session (or (:session opts) *session*)]
|
||||
(if-let [session (:session opts)]
|
||||
(.deleteMany (collection db coll opts) session (document q) (->DeleteOptions opts))
|
||||
(.deleteMany (collection db coll opts) (document q) (->DeleteOptions opts)))))
|
||||
|
||||
|
|
@ -195,30 +280,25 @@
|
|||
- `:sort` document representing sort order, e.g. {:timestamp -1}
|
||||
- `:projection` document representing fields to return, e.g. {:_id 0}
|
||||
- `:keywordize?` keywordize the keys of return results, default: true
|
||||
- `:realise-fn` how to realise the MongoIterable, default: `clojure.core/sequence` (i.e. lazily)
|
||||
- `:raw?` return the mongo FindIterable directly instead of processing into a seq, default: false
|
||||
- `:session` a ClientSession
|
||||
|
||||
Additionally takes options specified in `collection`."
|
||||
([^MongoDatabase db coll q]
|
||||
(find db coll q {}))
|
||||
([^MongoDatabase db coll q {:keys [limit skip sort projection ^ClientSession session keywordize? raw? realise-fn]
|
||||
:or {keywordize? true
|
||||
realise-fn sequence}
|
||||
:as opts}]
|
||||
(let [^ClientSession session (or session *session*)
|
||||
it (cond-> (if session
|
||||
([^MongoDatabase db coll q opts]
|
||||
(let [{:keys [limit skip sort projection session keywordize? raw?] :or {keywordize? true raw? false}} opts]
|
||||
(let [it (-> (if session
|
||||
(.find (collection db coll opts) session (document q))
|
||||
(.find (collection db coll opts) (document q)))
|
||||
limit (.limit limit)
|
||||
skip (.skip skip)
|
||||
sort (.sort (document sort))
|
||||
projection (.projection (document projection)))]
|
||||
(#(if limit (.limit % limit) %))
|
||||
(#(if skip (.skip % skip) %))
|
||||
(#(if sort (.sort % (document sort)) %))
|
||||
(#(if projection (.projection % (document projection)) %)))]
|
||||
|
||||
(if raw?
|
||||
it
|
||||
(realise-fn ;; accomodate users who don't want to use lazy-seqs
|
||||
(iterable/documents it keywordize?))))))
|
||||
(if-not raw?
|
||||
(map (fn [x] (from-document x keywordize?)) (seq it))
|
||||
it)))))
|
||||
|
||||
(defn find-one
|
||||
"Finds a single document and returns it as a clojure map, or nil if not found.
|
||||
|
|
@ -229,6 +309,17 @@
|
|||
([^MongoDatabase db coll q opts]
|
||||
(first (find db coll q (assoc opts :limit 1 :raw? false)))))
|
||||
|
||||
(defn ->FindOneAndUpdateOptions
|
||||
"Coerce options map into FindOneAndUpdateOptions. See `find-one-and-update` for usage."
|
||||
[{:keys [find-one-and-update-options upsert? return-new? sort projection]}]
|
||||
(let [opts (or find-one-and-update-options (FindOneAndUpdateOptions.))]
|
||||
(when (some? upsert?) (.upsert opts upsert?))
|
||||
(when return-new? (.returnDocument opts (ReturnDocument/AFTER)))
|
||||
(when sort (.sort opts (document sort)))
|
||||
(when projection (.projection opts (document projection)))
|
||||
|
||||
opts))
|
||||
|
||||
(defn find-one-and-update
|
||||
"Atomically find a document (at most one) and modify it.
|
||||
|
||||
|
|
@ -237,10 +328,7 @@
|
|||
- `db` is a MongoDatabase
|
||||
- `coll` is a collection name
|
||||
- `q` is a map representing a query.
|
||||
- `update` is either a map representing a document update or a vector
|
||||
representing an 'aggregation pipeline'. A document update must include only
|
||||
update operators, while an 'aggregation pipeline' can contain multiple
|
||||
stages of `$set`, `$unset` and `$replaceWith`.
|
||||
- `update` is a map representing an update. The update to apply must include only update operators.
|
||||
- `opts` (optional), a map of:
|
||||
- `:upsert?` whether to insert a new document if nothing is found, default: false
|
||||
- `:return-new?` whether to return the document after update (insead of its state before the update), default: false
|
||||
|
|
@ -255,19 +343,24 @@
|
|||
([^MongoDatabase db coll q update]
|
||||
(find-one-and-update db coll q update {}))
|
||||
([^MongoDatabase db coll q update opts]
|
||||
(let [{:keys [keywordize? ^ClientSession session] :or {keywordize? true}} opts
|
||||
^ClientSession session (or session *session*)
|
||||
(let [{:keys [keywordize? session] :or {keywordize? true}} opts
|
||||
opts' (->FindOneAndUpdateOptions opts)]
|
||||
(-> (if (instance? List update)
|
||||
(let [pipeline ^List (map document update)]
|
||||
(if session
|
||||
(.findOneAndUpdate (collection db coll opts) session (document q) pipeline opts')
|
||||
(.findOneAndUpdate (collection db coll opts) (document q) pipeline opts')))
|
||||
(if session
|
||||
(-> (if session
|
||||
(.findOneAndUpdate (collection db coll opts) session (document q) (document update) opts')
|
||||
(.findOneAndUpdate (collection db coll opts) (document q) (document update) opts')))
|
||||
(.findOneAndUpdate (collection db coll opts) (document q) (document update) opts'))
|
||||
(from-document keywordize?)))))
|
||||
|
||||
(defn ->FindOneAndReplaceOptions
|
||||
"Coerce options map into FindOneAndReplaceOptions. See `find-one-and-replace` for usage."
|
||||
[{:keys [find-one-and-replace-options upsert? return-new? sort projection]}]
|
||||
(let [opts (or find-one-and-replace-options (FindOneAndReplaceOptions.))]
|
||||
(when (some? upsert?) (.upsert opts upsert?))
|
||||
(when return-new? (.returnDocument opts (ReturnDocument/AFTER)))
|
||||
(when sort (.sort opts (document sort)))
|
||||
(when projection (.projection opts (document projection)))
|
||||
|
||||
opts))
|
||||
|
||||
(defn find-one-and-replace
|
||||
"Atomically find a document (at most one) and replace it.
|
||||
|
||||
|
|
@ -292,13 +385,20 @@
|
|||
(find-one-and-replace db coll q doc {}))
|
||||
([^MongoDatabase db coll q doc opts]
|
||||
(let [{:keys [keywordize? session] :or {keywordize? true}} opts
|
||||
session (or session *session*)
|
||||
opts' (->FindOneAndReplaceOptions opts)]
|
||||
(-> (if session
|
||||
(.findOneAndReplace (collection db coll opts) session (document q) (document doc) opts')
|
||||
(.findOneAndReplace (collection db coll opts) (document q) (document doc) opts'))
|
||||
(from-document keywordize?)))))
|
||||
|
||||
(defn ->InsertOneOptions
|
||||
"Coerce options map into InsertOneOptions. See `insert-one` for usage."
|
||||
[{:keys [insert-one-options bypass-document-validation?]}]
|
||||
(let [opts (or insert-one-options (InsertOneOptions.))]
|
||||
(when (some? bypass-document-validation?) (.bypassDocumentValidation opts bypass-document-validation?))
|
||||
|
||||
opts))
|
||||
|
||||
(defn insert-one
|
||||
"Inserts a single document into a collection, and returns nil.
|
||||
If the document does not have an _id field, it will be auto-generated by the underlying driver.
|
||||
|
|
@ -319,10 +419,19 @@
|
|||
(insert-one db coll doc {}))
|
||||
([^MongoDatabase db coll doc opts]
|
||||
(let [opts' (->InsertOneOptions opts)]
|
||||
(if-let [session (or (:session opts) *session*)]
|
||||
(if-let [session (:session opts)]
|
||||
(.insertOne (collection db coll opts) session (document doc) opts')
|
||||
(.insertOne (collection db coll opts) (document doc) opts')))))
|
||||
|
||||
(defn ->InsertManyOptions
|
||||
"Coerce options map into InsertManyOptions. See `insert-many` for usage."
|
||||
[{:keys [insert-many-options bypass-document-validation? ordered?]}]
|
||||
(let [opts (or insert-many-options (InsertManyOptions.))]
|
||||
(when (some? bypass-document-validation?) (.bypassDocumentValidation opts bypass-document-validation?))
|
||||
(when (some? ordered?) (.ordered opts ordered?))
|
||||
|
||||
opts))
|
||||
|
||||
(defn insert-many
|
||||
"Inserts multiple documents into a collection.
|
||||
If a document does not have an _id field, it will be auto-generated by the underlying driver.
|
||||
|
|
@ -344,9 +453,18 @@
|
|||
(insert-many db coll docs {}))
|
||||
([^MongoDatabase db coll docs opts]
|
||||
(let [opts' (->InsertManyOptions opts)]
|
||||
(if-let [^ClientSession session (or (:session opts) *session*)]
|
||||
(.insertMany (collection db coll opts) session ^List (map document docs) opts')
|
||||
(.insertMany (collection db coll opts) ^List (map document docs) opts')))))
|
||||
(if-let [session (:session opts)]
|
||||
(.insertMany (collection db coll opts) session (map document docs) opts')
|
||||
(.insertMany (collection db coll opts) (map document docs) opts')))))
|
||||
|
||||
(defn ->ReplaceOptions
|
||||
"Coerce options map into ReplaceOptions. See `replace-one` and `replace-many` for usage."
|
||||
[{:keys [replace-options upsert? bypass-document-validation?]}]
|
||||
(let [opts (or replace-options (ReplaceOptions.))]
|
||||
(when (some? upsert?) (.upsert opts upsert?))
|
||||
(when (some? bypass-document-validation?) (.bypassDocumentValidation opts bypass-document-validation?))
|
||||
|
||||
opts))
|
||||
|
||||
(defn replace-one
|
||||
"Replace a single document in a collection and returns an UpdateResult.
|
||||
|
|
@ -368,10 +486,19 @@
|
|||
([^MongoDatabase db coll q doc]
|
||||
(find-one-and-replace db coll q doc {}))
|
||||
([^MongoDatabase db coll q doc opts]
|
||||
(if-let [^ClientSession session (or (:session opts) *session*)]
|
||||
(if-let [session (:session opts)]
|
||||
(.replaceOne (collection db coll opts) session (document q) (document doc) (->ReplaceOptions opts))
|
||||
(.replaceOne (collection db coll opts) (document q) (document doc) (->ReplaceOptions opts)))))
|
||||
|
||||
(defn ->UpdateOptions
|
||||
"Coerce options map into UpdateOptions. See `update-one` and `update-many` for usage."
|
||||
[{:keys [update-options upsert? bypass-document-validation?]}]
|
||||
(let [opts (or update-options (UpdateOptions.))]
|
||||
(when (some? upsert?) (.upsert opts upsert?))
|
||||
(when (some? bypass-document-validation?) (.bypassDocumentValidation opts bypass-document-validation?))
|
||||
|
||||
opts))
|
||||
|
||||
(defn update-one
|
||||
"Updates a single document in a collection and returns an UpdateResult.
|
||||
|
||||
|
|
@ -392,7 +519,7 @@
|
|||
([^MongoDatabase db coll q update]
|
||||
(update-one db coll q update {}))
|
||||
([^MongoDatabase db coll q update opts]
|
||||
(if-let [^ClientSession session (or (:session opts) *session*)]
|
||||
(if-let [session (:session opts)]
|
||||
(.updateOne (collection db coll opts) session (document q) (document update) (->UpdateOptions opts))
|
||||
(.updateOne (collection db coll opts) (document q) (document update) (->UpdateOptions opts)))))
|
||||
|
||||
|
|
@ -414,14 +541,24 @@
|
|||
|
||||
Additionally takes options specified in `collection`"
|
||||
([^MongoDatabase db coll q update]
|
||||
(update-many db coll q update {}))
|
||||
(update-many db coll q {}))
|
||||
([^MongoDatabase db coll q update opts]
|
||||
(if-let [^ClientSession session (or (:session opts) *session*)]
|
||||
(if-let [session (:session opts)]
|
||||
(.updateMany (collection db coll opts) session (document q) (document update) (->UpdateOptions opts))
|
||||
(.updateMany (collection db coll opts) (document q) (document update) (->UpdateOptions opts)))))
|
||||
|
||||
;;; Admin functions
|
||||
|
||||
(defn ->CreateCollectionOptions
|
||||
"Coerce options map into CreateCollectionOptions. See `create` usage."
|
||||
[{:keys [create-collection-options capped? max-documents max-size-bytes]}]
|
||||
(let [opts (or create-collection-options (CreateCollectionOptions.))]
|
||||
(when (some? capped?) (.capped opts capped?))
|
||||
(when max-documents (.maxDocuments opts max-documents))
|
||||
(when max-size-bytes (.sizeInBytes opts max-size-bytes))
|
||||
|
||||
opts))
|
||||
|
||||
(defn create
|
||||
"Creates a collection
|
||||
|
||||
|
|
@ -435,12 +572,20 @@
|
|||
- `:max-size-bytes` max collection size in bytes for a capped collection
|
||||
- `:create-collection-options` A CreateCollectionOptions for configuring directly. If specified,
|
||||
any other [preceding] query options will be applied to it"
|
||||
([^MongoDatabase db ^String coll]
|
||||
([^MongoDatabase db coll]
|
||||
(create db coll {}))
|
||||
([^MongoDatabase db ^String coll opts]
|
||||
([^MongoDatabase db coll opts]
|
||||
(let [opts' (->CreateCollectionOptions opts)]
|
||||
(.createCollection db coll opts'))))
|
||||
|
||||
(defn ->RenameCollectionOptions
|
||||
"Coerce options map into RenameCollectionOptions. See `rename` usage."
|
||||
[{:keys [rename-collection-options drop-target?]}]
|
||||
(let [opts (or rename-collection-options (RenameCollectionOptions.))]
|
||||
(when (some? drop-target?) (.dropTarget opts drop-target?))
|
||||
|
||||
opts))
|
||||
|
||||
(defn rename
|
||||
"Renames `coll` to `new-coll` in the same DB.
|
||||
|
||||
|
|
@ -467,6 +612,18 @@
|
|||
[^MongoDatabase db coll]
|
||||
(.drop (collection db coll)))
|
||||
|
||||
(defn ->IndexOptions
|
||||
"Coerces an options map into an IndexOptions.
|
||||
|
||||
See `create-index` for usage"
|
||||
[{:keys [index-options name sparse? unique?]}]
|
||||
(let [opts (or index-options (IndexOptions.))]
|
||||
(when name (.name opts name))
|
||||
(when (some? sparse?) (.sparse opts sparse?))
|
||||
(when (some? unique?) (.unique opts unique?))
|
||||
|
||||
opts))
|
||||
|
||||
(defn create-index
|
||||
"Creates an index
|
||||
|
||||
|
|
@ -502,7 +659,7 @@
|
|||
(create-indexes db coll indexes {}))
|
||||
([^MongoDatabase db coll indexes opts]
|
||||
(->> indexes
|
||||
(mapv (fn [x] (IndexModel. (document (:keys x)) (->IndexOptions x))))
|
||||
(map (fn [x] (IndexModel. (document (:keys x)) (->IndexOptions x))))
|
||||
(.createIndexes (collection db coll opts)))))
|
||||
|
||||
(defn list-indexes
|
||||
|
|
@ -510,7 +667,23 @@
|
|||
([^MongoDatabase db coll]
|
||||
(list-indexes db coll {}))
|
||||
([^MongoDatabase db coll opts]
|
||||
(let [it (.listIndexes (collection db coll opts))
|
||||
realise-fn (:realise-fn opts sequence)]
|
||||
(realise-fn
|
||||
(iterable/documents it true)))))
|
||||
(->> (.listIndexes (collection db coll opts))
|
||||
(map #(from-document % true)))))
|
||||
|
||||
;;; Utility functions
|
||||
|
||||
(defn- with-transaction
|
||||
"Executes `body` in a transaction.
|
||||
|
||||
`body` should be a fn with one or more mongo operations in it.
|
||||
Ensure `session` is passed as an option to each operation.
|
||||
|
||||
e.g.
|
||||
(def s (.startSession conn))
|
||||
(with-transaction s
|
||||
(fn []
|
||||
(insert-one my-db \"coll\" {:name \"hello\"} {:session s})
|
||||
(insert-one my-db \"coll\" {:name \"world\"} {:session s})))"
|
||||
[session body]
|
||||
(.withTransaction session (reify TransactionBody
|
||||
(execute [_] body))))
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
(ns mongo-driver-3.data-literals
|
||||
(:import (org.bson.types ObjectId)
|
||||
(java.io Writer)
|
||||
(java.util Date)
|
||||
(java.nio ByteBuffer)))
|
||||
|
||||
|
||||
(defmethod print-method ObjectId [^ObjectId c ^Writer w] (.write w (str "#mongo/id " \" (.toHexString c) \")))
|
||||
(defmethod print-dup ObjectId [^ObjectId c ^Writer w] (.write w (str "#mongo/id " \" (.toHexString c) \")))
|
||||
|
||||
(defprotocol AsObjectId
|
||||
(oid-from [this]))
|
||||
|
||||
(extend-protocol AsObjectId
|
||||
(Class/forName "[B")
|
||||
(oid-from [this] (ObjectId. ^bytes this))
|
||||
nil
|
||||
(oid-from [_] (ObjectId.))
|
||||
String
|
||||
(oid-from [this] (ObjectId. this))
|
||||
Date
|
||||
(oid-from [this] (ObjectId. this))
|
||||
ByteBuffer
|
||||
(oid-from [this] (ObjectId. this))
|
||||
)
|
||||
|
||||
|
||||
|
||||
(defn mongo-id ;; https://mongodb.github.io/mongo-java-driver/4.8/apidocs/bson/org/bson/types/ObjectId.html
|
||||
(^ObjectId [] (ObjectId.))
|
||||
(^ObjectId [o] (oid-from o))
|
||||
(^ObjectId [o1 o2]
|
||||
(if (and (int? o1)
|
||||
(int? o2))
|
||||
(ObjectId. (int o1) (int o2))
|
||||
(ObjectId. ^Date o1 (int o2)))))
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
(ns mongo-driver-3.iterable
|
||||
(:require [mongo-driver-3.model :as m]))
|
||||
|
||||
(defn documents
|
||||
"Given a MongoIterable <it>, returns an eduction which will
|
||||
eventually yield all the documents (per `m/from-document`)."
|
||||
[it keywordize?]
|
||||
(eduction (map #(m/from-document % keywordize?)) it))
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
(ns mongo-driver-3.model
|
||||
(:import (com.mongodb.client.model CountOptions DeleteOptions ReturnDocument FindOneAndUpdateOptions InsertOneOptions ReplaceOptions UpdateOptions CreateCollectionOptions RenameCollectionOptions InsertManyOptions FindOneAndReplaceOptions IndexOptions BulkWriteOptions DeleteManyModel DeleteOneModel InsertOneModel ReplaceOneModel UpdateManyModel UpdateOneModel)
|
||||
(org.bson Document)
|
||||
(java.util.concurrent TimeUnit)
|
||||
(com.mongodb WriteConcern ReadPreference ReadConcern)
|
||||
(clojure.lang Ratio Named IPersistentMap)
|
||||
(java.util Collection List Date)
|
||||
(org.bson.types Decimal128)))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
;;; Conversions
|
||||
|
||||
(defprotocol ConvertToDocument
|
||||
(^Document document [input] "Convert from clojure to Mongo Document"))
|
||||
|
||||
(defn read-dates-as-instants! []
|
||||
(extend-protocol ConvertToDocument
|
||||
Date
|
||||
(document [input _]
|
||||
(.toInstant ^Date input))))
|
||||
|
||||
(extend-protocol ConvertToDocument
|
||||
nil
|
||||
(document [_]
|
||||
nil)
|
||||
|
||||
Ratio
|
||||
(document [^Ratio input]
|
||||
(double input))
|
||||
|
||||
Named
|
||||
(document [^Named input]
|
||||
(.getName input))
|
||||
|
||||
IPersistentMap
|
||||
(document [^IPersistentMap input]
|
||||
(reduce-kv
|
||||
(fn [^Document doc k v]
|
||||
(doto doc
|
||||
(.append (document k) (document v))))
|
||||
(Document.)
|
||||
input))
|
||||
|
||||
Collection
|
||||
(document [^Collection input]
|
||||
(mapv document input))
|
||||
|
||||
Object
|
||||
(document [input]
|
||||
input))
|
||||
|
||||
(defprotocol ConvertFromDocument
|
||||
(from-document [input keywordize?] "Converts Mongo Document to Clojure"))
|
||||
|
||||
(extend-protocol ConvertFromDocument
|
||||
nil
|
||||
(from-document [input _]
|
||||
input)
|
||||
|
||||
Object
|
||||
(from-document [input _] input)
|
||||
|
||||
Decimal128
|
||||
(from-document [^Decimal128 input _]
|
||||
(.bigDecimalValue input))
|
||||
|
||||
List
|
||||
(from-document [^List input keywordize?]
|
||||
(mapv #(from-document % keywordize?) input))
|
||||
|
||||
Document
|
||||
(from-document [^Document input keywordize?]
|
||||
(persistent!
|
||||
(reduce (if keywordize?
|
||||
(fn [m ^String k]
|
||||
(assoc! m (keyword k) (from-document (.get input k) true)))
|
||||
(fn [m ^String k]
|
||||
(assoc! m k (from-document (.get input k) false))))
|
||||
(transient {})
|
||||
(.keySet input)))))
|
||||
|
||||
;;; Config
|
||||
|
||||
(def kw->ReadConcern
|
||||
{:available (ReadConcern/AVAILABLE)
|
||||
:default (ReadConcern/DEFAULT)
|
||||
:linearizable (ReadConcern/LINEARIZABLE)
|
||||
:local (ReadConcern/LOCAL)
|
||||
:majority (ReadConcern/MAJORITY)
|
||||
:snapshot (ReadConcern/SNAPSHOT)})
|
||||
|
||||
(defn ->ReadConcern
|
||||
"Coerce `rc` into a ReadConcern if not nil. See `collection` for usage."
|
||||
^ReadConcern [{:keys [read-concern]}]
|
||||
(when read-concern
|
||||
(if (instance? ReadConcern read-concern)
|
||||
read-concern
|
||||
(or (kw->ReadConcern read-concern)
|
||||
(throw (IllegalArgumentException.
|
||||
(str "No match for read concern of " (name read-concern))))))))
|
||||
|
||||
(defn ->ReadPreference
|
||||
"Coerce `rp` into a ReadPreference if not nil. See `collection` for usage."
|
||||
^ReadPreference [{:keys [read-preference]}]
|
||||
(when read-preference
|
||||
(if (instance? ReadPreference read-preference)
|
||||
read-preference
|
||||
(ReadPreference/valueOf (name read-preference)))))
|
||||
|
||||
(defn ->WriteConcern
|
||||
"Coerces write-concern related options to a WriteConcern. See `collection` for usage."
|
||||
^WriteConcern [{:keys [write-concern ^Integer write-concern/w ^Long write-concern/w-timeout-ms ^Boolean write-concern/journal?]}]
|
||||
(when (some some? [write-concern w w-timeout-ms journal?])
|
||||
(let [^WriteConcern wc (when write-concern
|
||||
(if (instance? WriteConcern write-concern)
|
||||
write-concern
|
||||
(WriteConcern/valueOf (name write-concern))))]
|
||||
(cond-> (or wc (WriteConcern/ACKNOWLEDGED))
|
||||
w (.withW w)
|
||||
w-timeout-ms (.withWTimeout w-timeout-ms (TimeUnit/MILLISECONDS))
|
||||
(some? journal?) (.withJournal journal?)))))
|
||||
|
||||
(defn ->BulkWriteOptions
|
||||
"Coerce options map into BulkWriteOptions. See `bulk-write` for usage."
|
||||
^BulkWriteOptions [{:keys [bulk-write-options bypass-document-validation? ordered?]}]
|
||||
(let [^BulkWriteOptions opts (or bulk-write-options (BulkWriteOptions.))]
|
||||
(cond-> opts
|
||||
(some? bypass-document-validation?) (.bypassDocumentValidation bypass-document-validation?)
|
||||
(some? ordered?) (.ordered ordered?))))
|
||||
|
||||
(defn ->CountOptions
|
||||
"Coerce options map into CountOptions. See `count-documents` for usage."
|
||||
^CountOptions [{:keys [count-options hint limit max-time-ms skip]}]
|
||||
(let [^CountOptions opts (or count-options (CountOptions.))]
|
||||
(cond-> opts
|
||||
hint (.hint (document hint))
|
||||
limit (.limit limit)
|
||||
max-time-ms (.maxTime max-time-ms (TimeUnit/MILLISECONDS))
|
||||
skip (.skip skip))))
|
||||
|
||||
(defn ->DeleteOptions
|
||||
"Coerce options map into DeleteOptions. See `delete-one` and `delete-many` for usage."
|
||||
^DeleteOptions [{:keys [delete-options]}]
|
||||
(let [^DeleteOptions opts (or delete-options (DeleteOptions.))]
|
||||
opts))
|
||||
|
||||
(defn ->FindOneAndReplaceOptions
|
||||
"Coerce options map into FindOneAndReplaceOptions. See `find-one-and-replace` for usage."
|
||||
^FindOneAndReplaceOptions [{:keys [find-one-and-replace-options upsert? return-new? sort projection]}]
|
||||
(let [^FindOneAndReplaceOptions opts (or find-one-and-replace-options (FindOneAndReplaceOptions.))]
|
||||
(cond-> opts
|
||||
(some? upsert?) (.upsert upsert?)
|
||||
return-new? (.returnDocument (ReturnDocument/AFTER))
|
||||
sort (.sort (document sort))
|
||||
projection (.projection (document projection)))))
|
||||
|
||||
(defn ->FindOneAndUpdateOptions
|
||||
"Coerce options map into FindOneAndUpdateOptions. See `find-one-and-update` for usage."
|
||||
^FindOneAndUpdateOptions [{:keys [find-one-and-update-options upsert? return-new? sort projection]}]
|
||||
(let [^FindOneAndUpdateOptions opts (or find-one-and-update-options (FindOneAndUpdateOptions.))]
|
||||
(cond-> opts
|
||||
(some? upsert?) (.upsert upsert?)
|
||||
return-new? (.returnDocument (ReturnDocument/AFTER))
|
||||
sort (.sort (document sort))
|
||||
projection (.projection (document projection)))))
|
||||
|
||||
(defn ^IndexOptions ->IndexOptions
|
||||
"Coerces an options map into an IndexOptions.
|
||||
|
||||
See `create-index` for usage"
|
||||
[{:keys [index-options name sparse? unique?]}]
|
||||
(let [^IndexOptions opts (or index-options (IndexOptions.))]
|
||||
(cond-> opts
|
||||
name (.name name)
|
||||
(some? sparse?) (.sparse sparse?)
|
||||
(some? unique?) (.unique unique?))))
|
||||
|
||||
(defn ->InsertManyOptions
|
||||
"Coerce options map into InsertManyOptions. See `insert-many` for usage."
|
||||
^InsertManyOptions [{:keys [insert-many-options bypass-document-validation? ordered?]}]
|
||||
(let [^InsertManyOptions opts (or insert-many-options (InsertManyOptions.))]
|
||||
(cond-> opts
|
||||
(some? bypass-document-validation?) (.bypassDocumentValidation bypass-document-validation?)
|
||||
(some? ordered?) (.ordered ordered?))))
|
||||
|
||||
(defn ->InsertOneOptions
|
||||
"Coerce options map into InsertOneOptions. See `insert-one` for usage."
|
||||
^InsertOneOptions [{:keys [insert-one-options bypass-document-validation?]}]
|
||||
(let [^InsertOneOptions opts (or insert-one-options (InsertOneOptions.))]
|
||||
(cond-> opts
|
||||
(some? bypass-document-validation?) (.bypassDocumentValidation bypass-document-validation?))))
|
||||
|
||||
(defn ->ReplaceOptions
|
||||
"Coerce options map into ReplaceOptions. See `replace-one` and `replace-many` for usage."
|
||||
^ReplaceOptions [{:keys [replace-options upsert? bypass-document-validation?]}]
|
||||
(let [^ReplaceOptions opts (or replace-options (ReplaceOptions.))]
|
||||
(cond-> opts
|
||||
(some? upsert?) (.upsert upsert?)
|
||||
(some? bypass-document-validation?) (.bypassDocumentValidation bypass-document-validation?))))
|
||||
|
||||
(defn ->UpdateOptions
|
||||
"Coerce options map into UpdateOptions. See `update-one` and `update-many` for usage."
|
||||
^UpdateOptions [{:keys [update-options upsert? bypass-document-validation?]}]
|
||||
(let [^UpdateOptions opts (or update-options (UpdateOptions.))]
|
||||
(cond-> opts
|
||||
(some? upsert?) (.upsert upsert?)
|
||||
(some? bypass-document-validation?) (.bypassDocumentValidation bypass-document-validation?))))
|
||||
|
||||
(defn ->CreateCollectionOptions
|
||||
"Coerce options map into CreateCollectionOptions. See `create` usage."
|
||||
^CreateCollectionOptions [{:keys [create-collection-options capped? max-documents max-size-bytes]}]
|
||||
(let [^CreateCollectionOptions opts (or create-collection-options (CreateCollectionOptions.))]
|
||||
(cond-> opts
|
||||
(some? capped?) (.capped capped?)
|
||||
max-documents (.maxDocuments max-documents)
|
||||
max-size-bytes (.sizeInBytes max-size-bytes))))
|
||||
|
||||
(defn ^RenameCollectionOptions ->RenameCollectionOptions
|
||||
"Coerce options map into RenameCollectionOptions. See `rename` usage."
|
||||
[{:keys [rename-collection-options drop-target?]}]
|
||||
(let [^RenameCollectionOptions opts (or rename-collection-options (RenameCollectionOptions.))]
|
||||
(cond-> opts
|
||||
(some? drop-target?) (.dropTarget drop-target?))))
|
||||
|
||||
(defmulti write-model
|
||||
(fn [[type _]] type))
|
||||
|
||||
(defmethod write-model :delete-many
|
||||
[[_ opts]]
|
||||
(DeleteManyModel. (document (:filter opts)) (->DeleteOptions opts)))
|
||||
|
||||
(defmethod write-model :delete-one
|
||||
[[_ opts]]
|
||||
(DeleteOneModel. (document (:filter opts)) (->DeleteOptions opts)))
|
||||
|
||||
(defmethod write-model :insert-one
|
||||
[[_ opts]]
|
||||
(InsertOneModel. (document (:document opts))))
|
||||
|
||||
(defmethod write-model :replace-one
|
||||
[[_ opts]]
|
||||
(ReplaceOneModel. (document (:filter opts)) (document (:replacement opts)) (->ReplaceOptions opts)))
|
||||
|
||||
(defmethod write-model :update-many
|
||||
[[_ opts]]
|
||||
(UpdateManyModel. (document (:filter opts)) (document (:update opts)) (->UpdateOptions opts)))
|
||||
|
||||
(defmethod write-model :update-one
|
||||
[[_ opts]]
|
||||
(UpdateOneModel. (document (:filter opts)) (document (:update opts)) (->UpdateOptions opts)))
|
||||
|
|
@ -1,135 +1,21 @@
|
|||
(ns mongo-driver-3.client-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[mongo-driver-3.client :as mg]
|
||||
[mongo-driver-3.collection :as mc]
|
||||
[mongo-driver-3.model :as m])
|
||||
(:import (com.mongodb.client MongoClient MongoDatabase MongoIterable ListCollectionsIterable ClientSession)
|
||||
(java.util UUID)
|
||||
(com.mongodb ClientSessionOptions ReadConcern ReadPreference)
|
||||
(java.util.concurrent TimeUnit)))
|
||||
|
||||
;;; Unit
|
||||
|
||||
(deftest test->ClientSessionOptions
|
||||
(is (instance? ClientSessionOptions (mg/->ClientSessionOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isCausallyConsistent (mg/->ClientSessionOptions {:causally-consistent? arg})))
|
||||
true true
|
||||
false false
|
||||
nil nil)
|
||||
(is (= 7 (.getMaxCommitTime (.getDefaultTransactionOptions
|
||||
(mg/->ClientSessionOptions {:max-commit-time-ms 7})) (TimeUnit/MILLISECONDS))))
|
||||
(is (= (ReadConcern/AVAILABLE) (.getReadConcern (.getDefaultTransactionOptions
|
||||
(mg/->ClientSessionOptions {:read-concern :available})))))
|
||||
(is (= (ReadPreference/primary) (.getReadPreference (.getDefaultTransactionOptions (mg/->ClientSessionOptions {:read-preference :primary})))))
|
||||
(is (nil? (.getWriteConcern (.getDefaultTransactionOptions (mg/->ClientSessionOptions {})))))
|
||||
(is (= 1 (.getW (.getWriteConcern (.getDefaultTransactionOptions (mg/->ClientSessionOptions {:write-concern/w 1}))))))
|
||||
(let [opts (.build (.causallyConsistent (ClientSessionOptions/builder) true))]
|
||||
(is (= opts (mg/->ClientSessionOptions {:client-session-options opts})) "configure directly")))
|
||||
[mongo-driver-3.client :as mg])
|
||||
(:import (com.mongodb.client MongoClient MongoDatabase)))
|
||||
|
||||
;;; Integration
|
||||
|
||||
; docker run -it --rm -p 27017:27017 mongo:4.2
|
||||
(def mongo-host "mongodb://localhost:27017")
|
||||
|
||||
(deftest ^:integration test-create
|
||||
(def mongo-host (or (System/getenv "MONGO_HOST") "mongodb://localhost:27017"))
|
||||
|
||||
(deftest test-create
|
||||
(is (instance? MongoClient (mg/create)))
|
||||
(is (instance? MongoClient (mg/create mongo-host))))
|
||||
|
||||
(deftest ^:integration test-connect-to-db
|
||||
(deftest test-connect-to-db
|
||||
(is (thrown? IllegalArgumentException (mg/connect-to-db mongo-host)))
|
||||
(let [res (mg/connect-to-db (str mongo-host "/my-db"))]
|
||||
(is (instance? MongoClient (:client res)))
|
||||
(is (instance? MongoDatabase (:db res)))
|
||||
(is (= "my-db" (.getName (:db res))))))
|
||||
|
||||
(def client (atom nil))
|
||||
|
||||
(defn- setup-connections [f]
|
||||
(reset! client (mg/create))
|
||||
(f)
|
||||
(mg/close @client))
|
||||
|
||||
(use-fixtures :once setup-connections)
|
||||
|
||||
(defn new-db
|
||||
[client]
|
||||
(mg/get-db client (.toString (UUID/randomUUID))))
|
||||
|
||||
(deftest ^:integration test-list-collections
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "test")]
|
||||
(is (= ["test"] (map :name (mg/list-collections db))))
|
||||
(is (= ["test"] (map #(get % "name") (mg/list-collections db {:keywordize? false}))))
|
||||
(is (instance? ListCollectionsIterable (mg/list-collections db {:raw? true})))))
|
||||
|
||||
(deftest ^:integration test-list-collection-names
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "test")]
|
||||
(is (= ["test"] (mg/list-collection-names db)))
|
||||
(is (instance? MongoIterable (mg/list-collection-names db {:raw? true})))))
|
||||
|
||||
(comment
|
||||
;; Currently in a comment because it is troublesome to set up a replset in the CI
|
||||
|
||||
;; docker run -it --rm -p 27017:27017 mongo:4.2 --replSet rs1
|
||||
;; Ensure we have a replica set so we can run session tests
|
||||
(let [client (mg/create mongo-host)
|
||||
admin-db (mg/get-db client "admin")]
|
||||
(try (.runCommand admin-db (mc/document {:replSetInitiate {}}))
|
||||
(catch Exception _ "already initialized")))
|
||||
|
||||
(deftest ^:integration test-start-session
|
||||
(is (instance? ClientSession (mg/start-session @client))))
|
||||
|
||||
(deftest ^:integration test-with-transaction
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "test")]
|
||||
(with-open [session (mg/start-session @client)]
|
||||
(is (= 2 (mg/with-transaction session
|
||||
(fn []
|
||||
(mc/insert-one db "test" {:a 1} {:session session})
|
||||
(mc/insert-one db "test" {:a 2} {:session session})
|
||||
(mc/count-documents db "test" {} {:session session}))))))))
|
||||
|
||||
(deftest ^:integration test-with-implicit-transaction
|
||||
(testing "passing"
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "test")]
|
||||
(is (= 2 (mg/with-implicit-transaction
|
||||
{:client @client}
|
||||
(fn []
|
||||
(mc/insert-one db "test" {:a 1})
|
||||
(mc/insert-one db "test" {:a 2})
|
||||
(mc/count-documents db "test" {})))))))
|
||||
|
||||
(testing "failing"
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "test")]
|
||||
(is (= 0 (try (mg/with-implicit-transaction
|
||||
{:client @client}
|
||||
(fn []
|
||||
(mc/insert-one db "test" {:a 1})
|
||||
(mc/insert-one db "test" {nil 2})))
|
||||
(catch Exception _ (mc/count-documents db "test" {}))))))))
|
||||
|
||||
(deftest ^:integration test-with-transaction
|
||||
(testing "passing"
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "test")]
|
||||
(with-open [session (mg/start-session @client)]
|
||||
(is (= 2 (mg/with-transaction session
|
||||
(fn []
|
||||
(mc/insert-one db "test" {:a 1} {:session session})
|
||||
(mc/insert-one db "test" {:a 2} {:session session})
|
||||
(mc/count-documents db "test" {} {:session session}))))))))
|
||||
|
||||
(testing "failing"
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "test")]
|
||||
(with-open [session (mg/start-session @client)]
|
||||
(is (= 0 (try (mg/with-transaction session
|
||||
(fn []
|
||||
(mc/insert-one db "test" {:a 1} {:session session})
|
||||
(mc/insert-one db "test" {nil 2} {:session session})))
|
||||
(catch Exception _ (mc/count-documents db "test" {}))))))))))
|
||||
|
|
@ -2,10 +2,186 @@
|
|||
(:require [clojure.test :refer :all]
|
||||
[mongo-driver-3.client :as mg]
|
||||
[mongo-driver-3.collection :as mc])
|
||||
(:import (java.time ZoneId LocalDate LocalTime LocalDateTime)
|
||||
(:import (com.mongodb ReadConcern ReadPreference WriteConcern)
|
||||
(java.util.concurrent TimeUnit)
|
||||
(com.mongodb.client.model InsertOneOptions InsertManyOptions DeleteOptions FindOneAndUpdateOptions ReturnDocument FindOneAndReplaceOptions CountOptions UpdateOptions ReplaceOptions IndexOptions CreateCollectionOptions RenameCollectionOptions)
|
||||
(java.time ZoneId LocalDate LocalTime LocalDateTime)
|
||||
(java.util Date UUID)
|
||||
(com.mongodb.client FindIterable)))
|
||||
|
||||
;;; Unit
|
||||
|
||||
(deftest test->ReadConcern
|
||||
(is (nil? (mc/->ReadConcern nil)))
|
||||
(is (thrown? IllegalArgumentException (mc/->ReadConcern "invalid")))
|
||||
(is (instance? ReadConcern (mc/->ReadConcern :available))))
|
||||
|
||||
(deftest test->ReadPreference
|
||||
(is (nil? (mc/->ReadPreference nil)))
|
||||
(is (thrown? IllegalArgumentException (mc/->ReadPreference "invalid")))
|
||||
(is (instance? ReadPreference (mc/->ReadPreference :primary))))
|
||||
|
||||
(deftest test->WriteConcern
|
||||
(is (= (WriteConcern/W1) (mc/->WriteConcern {:write-concern :w1})) "accepts kw")
|
||||
(is (= (WriteConcern/W1) (mc/->WriteConcern {:write-concern (WriteConcern/W1)})) "accepts WriteConcern")
|
||||
(is (= (WriteConcern/ACKNOWLEDGED) (mc/->WriteConcern {:write-concern "invalid"})) "defaults to acknowledged")
|
||||
(is (= 1 (.getW (mc/->WriteConcern {:write-concern/w 1}))) "set w")
|
||||
(is (= 2 (.getW (mc/->WriteConcern {:write-concern (WriteConcern/W2)}))))
|
||||
(is (= 1 (.getW (mc/->WriteConcern {:write-concern (WriteConcern/W2) :write-concern/w 1}))) "prefer granular option")
|
||||
(is (true? (.getJournal (mc/->WriteConcern {:write-concern/journal? true}))) "can set journal")
|
||||
(is (= 77 (.getWTimeout (mc/->WriteConcern {:write-concern/w-timeout-ms 77}) (TimeUnit/MILLISECONDS))) "can set timeout"))
|
||||
|
||||
(deftest test->InsertOneOptions
|
||||
(is (instance? InsertOneOptions (mc/->InsertOneOptions {})))
|
||||
(is (true? (.getBypassDocumentValidation (mc/->InsertOneOptions {:bypass-document-validation? true}))))
|
||||
(is (true? (.getBypassDocumentValidation (mc/->InsertOneOptions
|
||||
{:insert-one-options (.bypassDocumentValidation (InsertOneOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (mc/->InsertOneOptions
|
||||
{:insert-one-options (.bypassDocumentValidation (InsertOneOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test->ReplaceOptions
|
||||
(is (instance? ReplaceOptions (mc/->ReplaceOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (mc/->ReplaceOptions {:upsert? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil) (is (true? (.getBypassDocumentValidation (mc/->ReplaceOptions {:bypass-document-validation? true}))))
|
||||
(is (true? (.getBypassDocumentValidation (mc/->ReplaceOptions
|
||||
{:replace-options (.bypassDocumentValidation (ReplaceOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (mc/->ReplaceOptions
|
||||
{:replace-options (.bypassDocumentValidation (ReplaceOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test->UpdateOptions
|
||||
(is (instance? UpdateOptions (mc/->UpdateOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (mc/->UpdateOptions {:upsert? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(is (true? (.getBypassDocumentValidation (mc/->UpdateOptions {:bypass-document-validation? true}))))
|
||||
(is (true? (.getBypassDocumentValidation (mc/->UpdateOptions
|
||||
{:update-options (.bypassDocumentValidation (UpdateOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (mc/->UpdateOptions
|
||||
{:update-options (.bypassDocumentValidation (UpdateOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test->InsertManyOptions
|
||||
(is (instance? InsertManyOptions (mc/->InsertManyOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.getBypassDocumentValidation (mc/->InsertManyOptions {:bypass-document-validation? arg})))
|
||||
true true
|
||||
false false
|
||||
nil nil)
|
||||
(are [expected arg]
|
||||
(= expected (.isOrdered (mc/->InsertManyOptions {:ordered? arg})))
|
||||
true true
|
||||
false false
|
||||
true nil)
|
||||
(is (true? (.getBypassDocumentValidation (mc/->InsertManyOptions
|
||||
{:insert-many-options (.bypassDocumentValidation (InsertManyOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (mc/->InsertManyOptions
|
||||
{:insert-one-options (.bypassDocumentValidation (InsertManyOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test->DeleteOptions
|
||||
(is (instance? DeleteOptions (mc/->DeleteOptions {})))
|
||||
(let [opts (DeleteOptions.)]
|
||||
(is (= opts (mc/->DeleteOptions {:delete-options opts})) "configure directly")))
|
||||
|
||||
(deftest test->RenameCollectionOptions
|
||||
(is (instance? RenameCollectionOptions (mc/->RenameCollectionOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isDropTarget (mc/->RenameCollectionOptions {:drop-target? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(let [opts (RenameCollectionOptions.)]
|
||||
(is (= opts (mc/->RenameCollectionOptions {:rename-collection-options opts})) "configure directly")))
|
||||
|
||||
(deftest test->FindOneAndUpdateOptions
|
||||
(is (instance? FindOneAndUpdateOptions (mc/->FindOneAndUpdateOptions {})))
|
||||
(let [opts (FindOneAndUpdateOptions.)]
|
||||
(is (= opts (mc/->FindOneAndUpdateOptions {:find-one-and-update-options opts})) "configure directly"))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (mc/->FindOneAndUpdateOptions {:upsert? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(are [expected arg]
|
||||
(= expected (.getReturnDocument (mc/->FindOneAndUpdateOptions {:return-new? arg})))
|
||||
(ReturnDocument/AFTER) true
|
||||
(ReturnDocument/BEFORE) false
|
||||
(ReturnDocument/BEFORE) nil)
|
||||
|
||||
(is (= {"_id" 1} (.getSort (mc/->FindOneAndUpdateOptions {:sort {:_id 1}}))))
|
||||
(is (= {"_id" 1} (.getProjection (mc/->FindOneAndUpdateOptions {:projection {:_id 1}})))))
|
||||
|
||||
(deftest test->FindOneAndReplaceOptions
|
||||
(is (instance? FindOneAndReplaceOptions (mc/->FindOneAndReplaceOptions {})))
|
||||
(let [opts (FindOneAndReplaceOptions.)]
|
||||
(is (= opts (mc/->FindOneAndReplaceOptions {:find-one-and-replace-options opts})) "configure directly"))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (mc/->FindOneAndReplaceOptions {:upsert? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(are [expected arg]
|
||||
(= expected (.getReturnDocument (mc/->FindOneAndReplaceOptions {:return-new? arg})))
|
||||
(ReturnDocument/AFTER) true
|
||||
(ReturnDocument/BEFORE) false
|
||||
(ReturnDocument/BEFORE) nil)
|
||||
|
||||
(is (= {"_id" 1} (.getSort (mc/->FindOneAndReplaceOptions {:sort {:_id 1}}))))
|
||||
(is (= {"_id" 1} (.getProjection (mc/->FindOneAndReplaceOptions {:projection {:_id 1}})))))
|
||||
|
||||
(deftest test->CountOptions
|
||||
(is (instance? CountOptions (mc/->CountOptions {})))
|
||||
(let [opts (CountOptions.)]
|
||||
(is (= opts (mc/->CountOptions {:count-options opts})) "configure directly"))
|
||||
(is (= {"a" 1} (.getHint (mc/->CountOptions {:hint {:a 1}}))))
|
||||
(is (= 7 (.getLimit (mc/->CountOptions {:limit 7}))))
|
||||
(is (= 2 (.getSkip (mc/->CountOptions {:skip 2}))))
|
||||
(is (= 42 (.getMaxTime (mc/->CountOptions {:max-time-ms 42}) (TimeUnit/MILLISECONDS)))))
|
||||
|
||||
(deftest test->IndexOptions
|
||||
(is (instance? IndexOptions (mc/->IndexOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isSparse (mc/->IndexOptions {:sparse? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(are [expected arg]
|
||||
(= expected (.isUnique (mc/->IndexOptions {:unique? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(let [opts (IndexOptions.)]
|
||||
(is (= opts (mc/->IndexOptions {:index-options opts})) "configure directly")))
|
||||
|
||||
(deftest test->CreateCollectionOptions
|
||||
(are [expected arg]
|
||||
(= expected (.isCapped (mc/->CreateCollectionOptions {:capped? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(is (= 7 (.getMaxDocuments (mc/->CreateCollectionOptions {:max-documents 7}))))
|
||||
(is (= 42 (.getSizeInBytes (mc/->CreateCollectionOptions {:max-size-bytes 42}))))
|
||||
(let [opts (-> (CreateCollectionOptions.) (.maxDocuments 5))]
|
||||
(is (= opts (mc/->CreateCollectionOptions {:create-collection-options opts})) "configure directly")
|
||||
(is (= 5 (.getMaxDocuments (mc/->CreateCollectionOptions {:create-collection-options opts}))))
|
||||
(is (= 7 (.getMaxDocuments (mc/->CreateCollectionOptions {:create-collection-options opts :max-documents 7})))
|
||||
"can override")))
|
||||
|
||||
;;; Integration
|
||||
|
||||
; docker run -it --rm -p 27017:27017 mongo:4.2
|
||||
|
|
@ -144,11 +320,7 @@
|
|||
(is (instance? FindIterable (mc/find db "test" {} {:raw? true}))))
|
||||
|
||||
(testing "keywordize"
|
||||
(is (= [{"id" 1}] (mc/find db "test" {} {:keywordize? false :projection {:_id 0 :id 1} :limit 1}))))
|
||||
|
||||
(testing "realise-fn"
|
||||
(is (seq? (mc/find db "test" {})))
|
||||
(is (vector? (mc/find db "test" {} {:realise-fn (partial into [])}))))))
|
||||
(is (= [{"id" 1}] (mc/find db "test" {} {:keywordize? false :projection {:_id 0 :id 1} :limit 1}))))))
|
||||
|
||||
(deftest ^:integration test-find-one
|
||||
(let [db (new-db @client)
|
||||
|
|
@ -220,13 +392,7 @@
|
|||
(let [db (new-db @client)]
|
||||
|
||||
(is (nil? (dissoc (mc/find-one-and-update db "test" {:id 1} {:$set {:r 1}} {:return-new? true}) :_id)))
|
||||
(is (= {:id 1 :r 1} (dissoc (mc/find-one-and-update db "test" {:id 1} {:$set {:r 1}} {:return-new? true :upsert? true}) :_id)))))
|
||||
|
||||
(testing "aggregation pipeline"
|
||||
(let [db (new-db @client)
|
||||
_ (mc/insert-many db "test" [{:id 1 :a [1 2] :b []} {:id 2 :a [7 8] :b []}])]
|
||||
|
||||
(is (= {:id 1 :a [] :b [1 2]} (dissoc (mc/find-one-and-update db "test" {:id 1} [{:$set {:b :$a}} {:$set {:a []}}] {:return-new? true}) :_id))))))
|
||||
(is (= {:id 1 :r 1} (dissoc (mc/find-one-and-update db "test" {:id 1} {:$set {:r 1}} {:return-new? true :upsert? true}) :_id))))))
|
||||
|
||||
(deftest ^:integration test-find-one-and-replace
|
||||
(testing "return new"
|
||||
|
|
@ -242,28 +408,6 @@
|
|||
(is (nil? (dissoc (mc/find-one-and-replace db "test" {:id 1} {:id 1 :v 2} {:return-new? true}) :_id)))
|
||||
(is (= {:id 1 :v 2} (dissoc (mc/find-one-and-replace db "test" {:id 1} {:id 1 :v 2} {:return-new? true :upsert? true}) :_id))))))
|
||||
|
||||
(deftest ^:integration test-bulk-write
|
||||
(testing "existing docs"
|
||||
(let [db (new-db @client)
|
||||
_ (mc/insert-many db "test" [{:id 1} {:id 2} {:id 3} {:id 4}])
|
||||
_ (mc/bulk-write db "test" [[:replace-one {:filter {:id 2} :replacement {:id 2.1}}]
|
||||
[:update-many {:filter {:id 3} :update {:$set {:a "b"}}}]
|
||||
[:update-one {:filter {:id 4} :update {:$set {:a "b"}}}]])]
|
||||
|
||||
(is (= [{:id 1} {:id 2.1} {:id 3 :a "b"} {:id 4 :a "b"}]
|
||||
(mc/find db "test" {} {:projection {:_id 0}})))))
|
||||
|
||||
(testing "upsert"
|
||||
(let [db (new-db @client)
|
||||
res (mc/bulk-write db "test" [[:insert-one {:document {:id 1}}]
|
||||
[:replace-one {:filter {:id 2} :replacement {:id 2.1} :upsert? true}]
|
||||
[:update-many {:filter {:id 3} :update {:$set {:a "b"}} :upsert? true}]
|
||||
[:update-one {:filter {:id 4} :update {:$set {:a "b"}} :upsert? true}]])]
|
||||
|
||||
(is (= 4 (mc/count-documents db "test")))
|
||||
(is (= 1 (.getInsertedCount res)))
|
||||
(is (= 3 (count (.getUpserts res)))))))
|
||||
|
||||
(deftest ^:integration test-replace-one
|
||||
(testing "existing doc"
|
||||
(let [db (new-db @client)
|
||||
|
|
@ -353,7 +497,12 @@
|
|||
(testing "not existing"
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "my-coll")]
|
||||
(is (true? (coll-exists? db "my-coll"))))))
|
||||
(is (true? (coll-exists? db "my-coll")))))
|
||||
|
||||
(testing "existing"
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "my-coll")]
|
||||
(is (thrown? Exception (mc/create db "my-coll"))))))
|
||||
|
||||
(deftest ^:integration test-rename
|
||||
(testing "not existing"
|
||||
|
|
@ -386,11 +535,7 @@
|
|||
(deftest ^:integration test-list-indexes
|
||||
(let [db (new-db @client)
|
||||
_ (mc/create db "test")]
|
||||
(is (= 1 (count (mc/list-indexes db "test"))) "has default index")
|
||||
|
||||
(testing "realise-fn"
|
||||
(is (seq? (mc/list-indexes db "test")))
|
||||
(is (vector? (mc/list-indexes db "test" {:realise-fn (partial into [])}))))))
|
||||
(is (= 1 (count (mc/list-indexes db "test"))) "has default index")))
|
||||
|
||||
(deftest ^:integration test-create-index
|
||||
(let [db (new-db @client)
|
||||
|
|
|
|||
|
|
@ -1,235 +0,0 @@
|
|||
(ns mongo-driver-3.model-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[mongo-driver-3.model :as m])
|
||||
(:import (com.mongodb ReadConcern ReadPreference WriteConcern)
|
||||
(java.util.concurrent TimeUnit)
|
||||
(com.mongodb.client.model InsertOneOptions InsertManyOptions DeleteOptions FindOneAndUpdateOptions ReturnDocument FindOneAndReplaceOptions CountOptions UpdateOptions ReplaceOptions IndexOptions CreateCollectionOptions RenameCollectionOptions BulkWriteOptions DeleteManyModel DeleteOneModel InsertOneModel ReplaceOneModel UpdateManyModel UpdateOneModel)))
|
||||
|
||||
;;; Unit
|
||||
|
||||
(deftest test->ReadConcern
|
||||
(is (nil? (m/->ReadConcern {})))
|
||||
(is (thrown? IllegalArgumentException (m/->ReadConcern {:read-concern "invalid"})))
|
||||
(is (instance? ReadConcern (m/->ReadConcern {:read-concern :available}))))
|
||||
|
||||
(deftest test->ReadPreference
|
||||
(is (nil? (m/->ReadPreference {})))
|
||||
(is (thrown? IllegalArgumentException (m/->ReadPreference {:read-preference "invalid"})))
|
||||
(is (instance? ReadPreference (m/->ReadPreference {:read-preference :primary}))))
|
||||
|
||||
(deftest test->WriteConcern
|
||||
(is (= (WriteConcern/W1) (m/->WriteConcern {:write-concern :w1})) "accepts kw")
|
||||
(is (= (WriteConcern/W1) (m/->WriteConcern {:write-concern (WriteConcern/W1)})) "accepts WriteConcern")
|
||||
(is (= (WriteConcern/ACKNOWLEDGED) (m/->WriteConcern {:write-concern "invalid"})) "defaults to acknowledged")
|
||||
(is (= 1 (.getW (m/->WriteConcern {:write-concern/w 1}))) "set w")
|
||||
(is (= 2 (.getW (m/->WriteConcern {:write-concern (WriteConcern/W2)}))))
|
||||
(is (= 1 (.getW (m/->WriteConcern {:write-concern (WriteConcern/W2) :write-concern/w 1}))) "prefer granular option")
|
||||
(is (true? (.getJournal (m/->WriteConcern {:write-concern/journal? true}))) "can set journal")
|
||||
(is (= 77 (.getWTimeout (m/->WriteConcern {:write-concern/w-timeout-ms 77}) (TimeUnit/MILLISECONDS))) "can set timeout"))
|
||||
|
||||
(deftest test->InsertOneOptions
|
||||
(is (instance? InsertOneOptions (m/->InsertOneOptions {})))
|
||||
(is (true? (.getBypassDocumentValidation (m/->InsertOneOptions {:bypass-document-validation? true}))))
|
||||
(is (true? (.getBypassDocumentValidation (m/->InsertOneOptions
|
||||
{:insert-one-options (.bypassDocumentValidation (InsertOneOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (m/->InsertOneOptions
|
||||
{:insert-one-options (.bypassDocumentValidation (InsertOneOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test->ReplaceOptions
|
||||
(is (instance? ReplaceOptions (m/->ReplaceOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (m/->ReplaceOptions {:upsert? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(is (true? (.getBypassDocumentValidation (m/->ReplaceOptions {:bypass-document-validation? true}))))
|
||||
(is (true? (.getBypassDocumentValidation (m/->ReplaceOptions
|
||||
{:replace-options (.bypassDocumentValidation (ReplaceOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (m/->ReplaceOptions
|
||||
{:replace-options (.bypassDocumentValidation (ReplaceOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test->UpdateOptions
|
||||
(is (instance? UpdateOptions (m/->UpdateOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (m/->UpdateOptions {:upsert? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(is (true? (.getBypassDocumentValidation (m/->UpdateOptions {:bypass-document-validation? true}))))
|
||||
(is (true? (.getBypassDocumentValidation (m/->UpdateOptions
|
||||
{:update-options (.bypassDocumentValidation (UpdateOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (m/->UpdateOptions
|
||||
{:update-options (.bypassDocumentValidation (UpdateOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test->InsertManyOptions
|
||||
(is (instance? InsertManyOptions (m/->InsertManyOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.getBypassDocumentValidation (m/->InsertManyOptions {:bypass-document-validation? arg})))
|
||||
true true
|
||||
false false
|
||||
nil nil)
|
||||
(are [expected arg]
|
||||
(= expected (.isOrdered (m/->InsertManyOptions {:ordered? arg})))
|
||||
true true
|
||||
false false
|
||||
true nil)
|
||||
(is (true? (.getBypassDocumentValidation (m/->InsertManyOptions
|
||||
{:insert-many-options (.bypassDocumentValidation (InsertManyOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (m/->InsertManyOptions
|
||||
{:insert-many-options (.bypassDocumentValidation (InsertManyOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test->DeleteOptions
|
||||
(is (instance? DeleteOptions (m/->DeleteOptions {})))
|
||||
(let [opts (DeleteOptions.)]
|
||||
(is (= opts (m/->DeleteOptions {:delete-options opts})) "configure directly")))
|
||||
|
||||
(deftest test->RenameCollectionOptions
|
||||
(is (instance? RenameCollectionOptions (m/->RenameCollectionOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isDropTarget (m/->RenameCollectionOptions {:drop-target? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(let [opts (RenameCollectionOptions.)]
|
||||
(is (= opts (m/->RenameCollectionOptions {:rename-collection-options opts})) "configure directly")))
|
||||
|
||||
(deftest test->FindOneAndUpdateOptions
|
||||
(is (instance? FindOneAndUpdateOptions (m/->FindOneAndUpdateOptions {})))
|
||||
(let [opts (FindOneAndUpdateOptions.)]
|
||||
(is (= opts (m/->FindOneAndUpdateOptions {:find-one-and-update-options opts})) "configure directly"))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (m/->FindOneAndUpdateOptions {:upsert? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(are [expected arg]
|
||||
(= expected (.getReturnDocument (m/->FindOneAndUpdateOptions {:return-new? arg})))
|
||||
(ReturnDocument/AFTER) true
|
||||
(ReturnDocument/BEFORE) false
|
||||
(ReturnDocument/BEFORE) nil)
|
||||
|
||||
(is (= {"_id" 1} (.getSort (m/->FindOneAndUpdateOptions {:sort {:_id 1}}))))
|
||||
(is (= {"_id" 1} (.getProjection (m/->FindOneAndUpdateOptions {:projection {:_id 1}})))))
|
||||
|
||||
(deftest test->FindOneAndReplaceOptions
|
||||
(is (instance? FindOneAndReplaceOptions (m/->FindOneAndReplaceOptions {})))
|
||||
(let [opts (FindOneAndReplaceOptions.)]
|
||||
(is (= opts (m/->FindOneAndReplaceOptions {:find-one-and-replace-options opts})) "configure directly"))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (m/->FindOneAndReplaceOptions {:upsert? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(are [expected arg]
|
||||
(= expected (.getReturnDocument (m/->FindOneAndReplaceOptions {:return-new? arg})))
|
||||
(ReturnDocument/AFTER) true
|
||||
(ReturnDocument/BEFORE) false
|
||||
(ReturnDocument/BEFORE) nil)
|
||||
|
||||
(is (= {"_id" 1} (.getSort (m/->FindOneAndReplaceOptions {:sort {:_id 1}}))))
|
||||
(is (= {"_id" 1} (.getProjection (m/->FindOneAndReplaceOptions {:projection {:_id 1}})))))
|
||||
|
||||
(deftest test->CountOptions
|
||||
(is (instance? CountOptions (m/->CountOptions {})))
|
||||
(let [opts (CountOptions.)]
|
||||
(is (= opts (m/->CountOptions {:count-options opts})) "configure directly"))
|
||||
(is (= {"a" 1} (.getHint (m/->CountOptions {:hint {:a 1}}))))
|
||||
(is (= 7 (.getLimit (m/->CountOptions {:limit 7}))))
|
||||
(is (= 2 (.getSkip (m/->CountOptions {:skip 2}))))
|
||||
(is (= 42 (.getMaxTime (m/->CountOptions {:max-time-ms 42}) (TimeUnit/MILLISECONDS)))))
|
||||
|
||||
(deftest test->IndexOptions
|
||||
(is (instance? IndexOptions (m/->IndexOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.isSparse (m/->IndexOptions {:sparse? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(are [expected arg]
|
||||
(= expected (.isUnique (m/->IndexOptions {:unique? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(let [opts (IndexOptions.)]
|
||||
(is (= opts (m/->IndexOptions {:index-options opts})) "configure directly")))
|
||||
|
||||
(deftest test->CreateCollectionOptions
|
||||
(are [expected arg]
|
||||
(= expected (.isCapped (m/->CreateCollectionOptions {:capped? arg})))
|
||||
true true
|
||||
false false
|
||||
false nil)
|
||||
(is (= 7 (.getMaxDocuments (m/->CreateCollectionOptions {:max-documents 7}))))
|
||||
(is (= 42 (.getSizeInBytes (m/->CreateCollectionOptions {:max-size-bytes 42}))))
|
||||
(let [opts (-> (CreateCollectionOptions.) (.maxDocuments 5))]
|
||||
(is (= opts (m/->CreateCollectionOptions {:create-collection-options opts})) "configure directly")
|
||||
(is (= 5 (.getMaxDocuments (m/->CreateCollectionOptions {:create-collection-options opts}))))
|
||||
(is (= 7 (.getMaxDocuments (m/->CreateCollectionOptions {:create-collection-options opts :max-documents 7})))
|
||||
"can override")))
|
||||
|
||||
(deftest test->BulkWriteOptions
|
||||
(is (instance? BulkWriteOptions (m/->BulkWriteOptions {})))
|
||||
(are [expected arg]
|
||||
(= expected (.getBypassDocumentValidation (m/->BulkWriteOptions {:bypass-document-validation? arg})))
|
||||
true true
|
||||
false false
|
||||
nil nil)
|
||||
(are [expected arg]
|
||||
(= expected (.isOrdered (m/->BulkWriteOptions {:ordered? arg})))
|
||||
true true
|
||||
false false
|
||||
true nil)
|
||||
(is (true? (.getBypassDocumentValidation (m/->BulkWriteOptions
|
||||
{:bulk-write-options (.bypassDocumentValidation (BulkWriteOptions.) true)})))
|
||||
"configure directly")
|
||||
(is (false? (.getBypassDocumentValidation (m/->BulkWriteOptions
|
||||
{:bulk-write-options (.bypassDocumentValidation (BulkWriteOptions.) true)
|
||||
:bypass-document-validation? false})))
|
||||
"can override"))
|
||||
|
||||
(deftest test-write-model
|
||||
(testing "delete many"
|
||||
(is (instance? DeleteManyModel (m/write-model [:delete-many {:filter {:a "b"}}]))))
|
||||
|
||||
(testing "delete one"
|
||||
(is (instance? DeleteOneModel (m/write-model [:delete-one {:filter {:a "b"}}]))))
|
||||
|
||||
(testing "insert one"
|
||||
(is (instance? InsertOneModel (m/write-model [:insert-one {:document {:a "b"}}]))))
|
||||
|
||||
(testing "replace one"
|
||||
(is (instance? ReplaceOneModel (m/write-model [:replace-one {:filter {:a "b"} :replacement {:a "c"}}])))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (.getReplaceOptions (m/write-model [:replace-one {:filter {:a "b"} :replacement {:a "c"} :upsert? arg}]))))
|
||||
true true
|
||||
false false
|
||||
false nil))
|
||||
|
||||
(testing "update many"
|
||||
(is (instance? UpdateManyModel (m/write-model [:update-many {:filter {:a "b"} :update {"$set" {:a "c"}}}])))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (.getOptions ^UpdateManyModel (m/write-model [:update-many {:filter {:a "b"} :update {"$set" {:a "c"}} :upsert? arg}]))))
|
||||
true true
|
||||
false false
|
||||
false nil))
|
||||
|
||||
(testing "update one"
|
||||
(is (instance? UpdateOneModel (m/write-model [:update-one {:filter {:a "b"} :update {"$set" {:a "c"}}}])))
|
||||
(are [expected arg]
|
||||
(= expected (.isUpsert (.getOptions (m/write-model [:update-one {:filter {:a "b"} :update {"$set" {:a "c"}} :upsert? arg}]))))
|
||||
true true
|
||||
false false
|
||||
false nil)))
|
||||
|
||||
Loading…
Reference in a new issue