Compare commits

..

36 commits

Author SHA1 Message Date
George Narroway
0930cbbcf0 bump version to 0.9.0-SNAPSHOT 2024-02-07 11:11:01 +08:00
George Narroway
c39dd281c4 version 0.8.0 release 2024-02-07 11:09:44 +08:00
George Narroway
0acd78c963 bump dev dependencies 2024-02-07 11:08:43 +08:00
George Narroway
7e62bf8898 add test and readme for realise-fn 2024-02-07 11:07:55 +08:00
gnarroway
9c2c899876
Merge pull request #10 from jimpil/master
various optimisations/fixes
2024-02-07 10:44:13 +08:00
George Narroway
28e054aa3f creating existing collection no longer throws 2024-02-07 10:41:52 +08:00
George Narroway
57f2115e7b bump version 2024-02-07 10:41:38 +08:00
jimpil
1de8cabb5b type-hint all arities of mongo-id 2024-01-17 23:09:05 +00:00
jimpil
4ffb3f8a9a simplify data-literals 2024-01-17 23:05:35 +00:00
jimpil
fad49204e1 remove redundant require 2024-01-17 22:14:40 +00:00
jimpil
c09581a587 improve mongo-id 2024-01-17 22:03:06 +00:00
jimpil
3ac5a5cf69 remove lsp/clj-kondo folders 2024-01-17 21:40:45 +00:00
jimpil
6e8dc539ea update gitignore file 2024-01-17 21:36:14 +00:00
jimpil
11dfeabf17 various fixes - 2 2024-01-17 21:31:51 +00:00
jimpil
b32f13574a various optimisations/fixes 2024-01-17 21:23:40 +00:00
George Narroway
3934a6cc50 0.7.0 release again 2022-10-04 19:25:28 +08:00
George Narroway
c46e114f44 dependency update 2022-10-04 19:24:03 +08:00
George Narroway
46ca9d63df version 0.7.0 release 2022-10-04 19:12:29 +08:00
George Narroway
8a94879c94 version 0.7.0 release 2022-10-04 19:10:24 +08:00
George Narroway
e84af3e0ce tests 2022-10-04 19:06:49 +08:00
George Narroway
cddcae94e6 bump version; update change log; some tests for aggregations and docs 2022-10-04 19:04:31 +08:00
gnarroway
5435a5afa8
Merge pull request #8 from jacobemcken/feature/support_update_pipelines
Add support for update pipelines
2022-10-04 18:59:23 +08:00
Jacob Emcken
989a6d87a3
Add support for update pipelines
Resolves: #7
2022-10-02 18:02:59 +02:00
gnarroway
34ef1f44b6
Merge pull request #4 from henryw374/master
data literal for mongo id
2021-10-25 21:11:01 +08:00
gnarroway
07959e5970
Merge pull request #5 from henryw374/dates-as-instant
read dates as instants
2021-10-25 21:05:07 +08:00
gnarroway
97ea4ea2b5
Merge pull request #6 from eva-healthtech/implicit-transaction
Add a helper method for implicit transactions
2021-05-22 09:37:32 +08:00
David Williams
aa5a7118fd Add a helper method for implicit transactions 2021-03-24 05:29:24 +00:00
George Narroway
bcc547ebf7 bump version 2021-02-12 13:26:58 +08:00
George Narroway
68db895210 docs 2021-02-12 13:24:16 +08:00
Henry Widd
406e5d3576 read dates as instants 2020-07-17 14:05:32 +01:00
Henry Widd
4440798a24 data literal for mongo id 2020-07-17 13:56:47 +01:00
George Narroway
30e5d7c9ea 0.6.0 release 2020-01-10 00:02:09 +08:00
George Narroway
c6004a1156 0.6.0 release 2020-01-10 00:00:42 +08:00
George Narroway
9f9e625abc readme 2020-01-09 23:56:48 +08:00
George Narroway
e7c18692a1 support bulk-write 2020-01-09 23:40:08 +08:00
George Narroway
cb684136e5 move model helpers to their own ns 2020-01-09 22:54:54 +08:00
13 changed files with 827 additions and 466 deletions

2
.gitignore vendored
View file

@ -9,3 +9,5 @@ pom.xml.asc
/.nrepl-port /.nrepl-port
.hgignore .hgignore
.hg/ .hg/
.lsp/
.clj-kondo/

View file

@ -1,6 +1,29 @@
# Change Log # 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/). 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 ## 0.5.0 - 2019-11-22
### Added ### Added
- Support for transactions - Support for transactions

View file

@ -5,7 +5,7 @@
[![cljdoc badge](https://cljdoc.org/badge/mongo-driver-3/mongo-driver-3)](https://cljdoc.org/d/mongo-driver-3/mongo-driver-3/CURRENT) [![cljdoc badge](https://cljdoc.org/badge/mongo-driver-3/mongo-driver-3)](https://cljdoc.org/d/mongo-driver-3/mongo-driver-3/CURRENT)
A Mongo client for clojure, lightly wrapping 3.11+ versions of the [MongoDB Java Driver](https://mongodb.github.io/mongo-java-driver/) A Mongo client for clojure, lightly wrapping 3.11/4.0+ 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). 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 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 ## Status
mongo-driver-3 is used in production, but is also under development and the API may change slightly. mongo-driver-3 is used in production, and the existing public API will be maintained.
Please try it out and raise any issues you may find. Please try it out and raise any issues you may find.
## Usage ## Usage
@ -32,10 +32,10 @@ For Leinengen, add this to your project.clj:
```clojure ```clojure
;; The underlying driver -- any newer version can also be used ;; The underlying driver -- any newer version can also be used
[org.mongodb/mongodb-driver-sync "3.11.2"] [org.mongodb/mongodb-driver-sync "4.11.1"]
;; This wrapper library ;; This wrapper library
[mongo-driver-3 "0.5.0"] [mongo-driver-3 "0.8.0"]
``` ```
## Getting started ## Getting started
@ -104,6 +104,10 @@ As an example:
;; Find a single document or return nil ;; 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"} ; => {"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. While most options are supported directly, sometimes you may need to some extra control.
@ -135,6 +139,27 @@ Many mongo queries take operators like `$eq` and `$gt`. These are exposed in the
(mc/find db "test" {:a {"$gt" 3}}) (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 ### Using transactions
You can create a session to perform multi-document transactions, where all operations either You can create a session to perform multi-document transactions, where all operations either
@ -150,8 +175,23 @@ use `with-open` so the session is closed after both successful and failed transa
(fn [] (fn []
(mc/insert-one my-db "coll" {:name "hello"} {:session s}) (mc/insert-one my-db "coll" {:name "hello"} {:session s})
(mc/insert-one my-db "coll" {:name "world"} {: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 ## License
Released under the MIT License: http://www.opensource.org/licenses/mit-license.php Released under the MIT License: http://www.opensource.org/licenses/mit-license.php

View file

@ -1,5 +1,5 @@
(defproject mongo-driver-3 "0.5.0" (defproject mongo-driver-3 "0.9.0-SNAPSHOT"
:description "A Clojure wrapper for the Java MongoDB driver 3.11+." :description "A Clojure wrapper for the Java MongoDB driver 3.11/4.0+."
:url "https://github.com/gnarroway/mongo-driver-3" :url "https://github.com/gnarroway/mongo-driver-3"
:license {:name "The MIT License" :license {:name "The MIT License"
:url "http://opensource.org/licenses/mit-license.php" :url "http://opensource.org/licenses/mit-license.php"
@ -10,5 +10,5 @@
:sign-releases false}]] :sign-releases false}]]
:plugins [[lein-cljfmt "0.6.4"]] :plugins [[lein-cljfmt "0.6.4"]]
:profiles {:dev {:dependencies [[org.clojure/clojure "1.10.1"] :profiles {:dev {:dependencies [[org.clojure/clojure "1.11.1"]
[org.mongodb/mongodb-driver-sync "3.11.2"]]}}) [org.mongodb/mongodb-driver-sync "4.11.1"]]}})

1
src/data_readers.clj Normal file
View file

@ -0,0 +1 @@
{mongo/id mongo-driver-3.data-literals/mongo-id}

View file

@ -1,6 +1,7 @@
(ns mongo-driver-3.client (ns mongo-driver-3.client
(:refer-clojure :exclude [find]) (:refer-clojure :exclude [find])
(:require [mongo-driver-3.collection :as mc]) (:require [mongo-driver-3.model :as m]
[mongo-driver-3.iterable :as iterable])
(:import (com.mongodb.client MongoClients MongoClient ClientSession MongoDatabase TransactionBody) (:import (com.mongodb.client MongoClients MongoClient ClientSession MongoDatabase TransactionBody)
(com.mongodb ConnectionString ClientSessionOptions TransactionOptions) (com.mongodb ConnectionString ClientSessionOptions TransactionOptions)
(java.util.concurrent TimeUnit))) (java.util.concurrent TimeUnit)))
@ -15,16 +16,15 @@
`connection-string` is a mongo connection string, e.g. mongodb://localhost:27107 `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." If a connecting string is not passed in, it will connect to the default localhost instance."
([] (MongoClients/create)) (^MongoClient [] (MongoClients/create))
([^String connection-string] (^MongoClient [^String connection-string] (MongoClients/create connection-string)))
(MongoClients/create connection-string)))
(defn get-db (defn get-db
"Gets a database by name "Gets a database by name
`client` is a MongoClient, e.g. resulting from calling `connect` `client` is a MongoClient, e.g. resulting from calling `connect`
`name` is the name of the database to get." `name` is the name of the database to get."
[^MongoClient client ^String name] ^MongoDatabase [^MongoClient client ^String name]
(.getDatabase client name)) (.getDatabase client name))
(defn close (defn close
@ -41,16 +41,20 @@
- `opts` (optional), a map of: - `opts` (optional), a map of:
- `:name-only?` returns just the string names - `:name-only?` returns just the string names
- `:keywordize?` keywordize the keys of return results, default: true. Only applicable if `:name-only?` is false. - `: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 seq, default: 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" - `:session` a ClientSession"
([^MongoDatabase db] (list-collections db {})) ([^MongoDatabase db] (list-collections db {}))
([^MongoDatabase db {:keys [raw? keywordize? ^ClientSession session] :or {keywordize? true}}] ([^MongoDatabase db {:keys [raw? keywordize? ^ClientSession session realise-fn]
:or {keywordize? true
realise-fn sequence}}]
(let [it (if session (let [it (if session
(.listCollections db session) (.listCollections db session)
(.listCollections db))] (.listCollections db))]
(if-not raw? (if raw?
(map #(mc/from-document % keywordize?) (seq it)) it
it)))) (realise-fn ;; accomodate users who don't want to use lazy-seqs
(iterable/documents it keywordize?))))))
(defn list-collection-names (defn list-collection-names
"Lists collection names in a database, returning as a seq of strings unless otherwise configured. "Lists collection names in a database, returning as a seq of strings unless otherwise configured.
@ -66,16 +70,16 @@
(let [it (if-let [^ClientSession session (:session opts)] (let [it (if-let [^ClientSession session (:session opts)]
(.listCollectionNames db session) (.listCollectionNames db session)
(.listCollectionNames db))] (.listCollectionNames db))]
(if-not (:raw? opts) (if (:raw? opts)
(seq it) it
it)))) (seq it)))))
(defn ->TransactionOptions (defn ->TransactionOptions
"Coerces options map into a TransactionOptions. See `start-session` for usage." "Coerces options map into a TransactionOptions. See `start-session` for usage."
[{:keys [max-commit-time-ms] :as opts}] [{:keys [max-commit-time-ms] :as opts}]
(let [rp (mc/->ReadPreference opts) (let [rp (m/->ReadPreference opts)
rc (mc/->ReadConcern opts) rc (m/->ReadConcern opts)
wc (mc/->WriteConcern opts)] wc (m/->WriteConcern opts)]
(cond-> (TransactionOptions/builder) (cond-> (TransactionOptions/builder)
max-commit-time-ms (.maxCommitTime max-commit-time-ms (TimeUnit/MILLISECONDS)) max-commit-time-ms (.maxCommitTime max-commit-time-ms (TimeUnit/MILLISECONDS))
@ -130,11 +134,14 @@
Ensure `session` is passed as an option to each operation. Ensure `session` is passed as an option to each operation.
e.g. e.g.
```clojure
(with-open [s (start-session client)] (with-open [s (start-session client)]
(with-transaction s (with-transaction s
(fn [] (fn []
(insert-one my-db \"coll\" {:name \"hello\"} {:session s}) (insert-one my-db \"coll\" {:name \"hello\"} {:session s})
(insert-one my-db \"coll\" {:name \"world\"} {:session s}))))" (insert-one my-db \"coll\" {:name \"world\"} {:session s}))))
```"
([^ClientSession session body] (with-transaction session body {})) ([^ClientSession session body] (with-transaction session body {}))
([^ClientSession session body opts] ([^ClientSession session body opts]
(.withTransaction session (.withTransaction session
@ -142,6 +149,37 @@
(execute [_] (body))) (execute [_] (body)))
(->TransactionOptions opts)))) (->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 ;;; Utility
(defn connect-to-db (defn connect-to-db

View file

@ -1,123 +1,18 @@
(ns mongo-driver-3.collection (ns mongo-driver-3.collection
(:refer-clojure :exclude [find empty? drop]) (:refer-clojure :exclude [find empty? drop])
(:import (clojure.lang Ratio Keyword Named IPersistentMap) (:require [mongo-driver-3.model :refer :all]
(com.mongodb ReadConcern ReadPreference WriteConcern MongoNamespace) [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 MongoDatabase MongoCollection ClientSession)
(com.mongodb.client.model InsertOneOptions InsertManyOptions DeleteOptions FindOneAndUpdateOptions ReturnDocument FindOneAndReplaceOptions CountOptions CreateCollectionOptions RenameCollectionOptions IndexOptions IndexModel UpdateOptions ReplaceOptions) (com.mongodb.client.model IndexModel)
(java.util List Collection) (java.util List)
(java.util.concurrent TimeUnit) (org.bson Document)))
(org.bson Document)
(org.bson.types Decimal128)))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
;;; 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))))
;;; Collection ;;; 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."
[{: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."
[{:keys [read-preference]}]
(when read-preference
(if (instance? ReadPreference read-preference)
read-preference
(ReadPreference/valueOf (name read-preference)))))
(defn ^WriteConcern ->WriteConcern
"Coerces write-concern related options to a WriteConcern. See `collection` for usage."
[{: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 ^MongoCollection collection (defn ^MongoCollection collection
"Coerces `coll` to a MongoCollection with some options. "Coerces `coll` to a MongoCollection with some options.
@ -168,32 +63,55 @@
- `:batch-size` Documents to return per batch, e.g. 1 - `:batch-size` Documents to return per batch, e.g. 1
- `:bypass-document-validation?` Boolean - `:bypass-document-validation?` Boolean
- `:keywordize?` keywordize the keys of return results, default: true - `: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 - `:raw?` return the mongo AggregateIterable directly instead of processing into a seq, default: false
- `:session` a ClientSession" - `:session` a ClientSession"
([^MongoDatabase db coll pipeline] ([^MongoDatabase db coll pipeline]
(aggregate db coll pipeline {})) (aggregate db coll pipeline {}))
([^MongoDatabase db coll pipeline opts] ([^MongoDatabase db coll pipeline opts]
(let [{:keys [^ClientSession session allow-disk-use? ^Integer batch-size bypass-document-validation? keywordize? raw?] :or {keywordize? true raw? false}} opts (let [{:keys [^ClientSession session allow-disk-use? ^Integer batch-size bypass-document-validation? keywordize? raw? realise-fn]
it (cond-> (if session :or {keywordize? true
(.aggregate (collection db coll opts) session ^List (map document pipeline)) realise-fn sequence}} opts
(.aggregate (collection db coll opts) ^List (map document pipeline))) ^ClientSession session (or session *session*)
(some? allow-disk-use?) (.allowDiskUse allow-disk-use?) it (cond-> (if session
(some? bypass-document-validation?) (.bypassDocumentValidation bypass-document-validation?) (.aggregate (collection db coll opts) session ^List (map document pipeline))
batch-size (.batchSize batch-size))] (.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))]
(if-not raw? (if raw?
(map (fn [x] (from-document x keywordize?)) (seq it)) it
it)))) (realise-fn ;; accomodate users who don't want to use lazy-seqs
(iterable/documents it keywordize?))))))
(defn ^CountOptions ->CountOptions (defn bulk-write
"Coerce options map into CountOptions. See `count-documents` for usage." "Executes a mix of inserts, updates, replaces, and deletes.
[{:keys [count-options hint limit max-time-ms skip]}]
(let [^CountOptions opts (or count-options (CountOptions.))] - `db` is a MongoDatabase
(cond-> opts - `coll` is a collection name
hint (.hint (document hint)) - `operations` a list of 2-tuples in the form `[op config]`,
limit (.limit limit) - `op` is one of :insert-one :update-one :update-many :delete-one :delete-many :replace-one
max-time-ms (.maxTime max-time-ms (TimeUnit/MILLISECONDS)) - `config` the configuration map for the operation
skip (.skip skip)))) - `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))))))
(defn count-documents (defn count-documents
"Count documents in a collection, optionally matching a filter query `q`. "Count documents in a collection, optionally matching a filter query `q`.
@ -219,16 +137,10 @@
(count-documents db coll q {})) (count-documents db coll q {}))
([^MongoDatabase db coll q opts] ([^MongoDatabase db coll q opts]
(let [opts' (->CountOptions opts)] (let [opts' (->CountOptions opts)]
(if-let [session (:session opts)] (if-let [session (or (:session opts) *session*)]
(.countDocuments (collection db coll opts) session (document q) opts') (.countDocuments (collection db coll opts) session (document q) opts')
(.countDocuments (collection db coll opts) (document q) opts'))))) (.countDocuments (collection db coll opts) (document q) opts')))))
(defn ^DeleteOptions ->DeleteOptions
"Coerce options map into DeleteOptions. See `delete-one` and `delete-many` for usage."
[{:keys [delete-options]}]
(let [^DeleteOptions opts (or delete-options (DeleteOptions.))]
opts))
(defn delete-one (defn delete-one
"Deletes a single document from a collection and returns a DeleteResult. "Deletes a single document from a collection and returns a DeleteResult.
@ -245,7 +157,7 @@
([^MongoDatabase db coll q] ([^MongoDatabase db coll q]
(delete-one db coll q {})) (delete-one db coll q {}))
([^MongoDatabase db coll q opts] ([^MongoDatabase db coll q opts]
(if-let [session (:session opts)] (if-let [session (or (:session opts) *session*)]
(.deleteOne (collection db coll opts) session (document q) (->DeleteOptions opts)) (.deleteOne (collection db coll opts) session (document q) (->DeleteOptions opts))
(.deleteOne (collection db coll opts) (document q) (->DeleteOptions opts))))) (.deleteOne (collection db coll opts) (document q) (->DeleteOptions opts)))))
@ -265,7 +177,7 @@
([^MongoDatabase db coll q] ([^MongoDatabase db coll q]
(delete-many db coll q {})) (delete-many db coll q {}))
([^MongoDatabase db coll q opts] ([^MongoDatabase db coll q opts]
(if-let [session (:session opts)] (if-let [session (or (:session opts) *session*)]
(.deleteMany (collection db coll opts) session (document q) (->DeleteOptions opts)) (.deleteMany (collection db coll opts) session (document q) (->DeleteOptions opts))
(.deleteMany (collection db coll opts) (document q) (->DeleteOptions opts))))) (.deleteMany (collection db coll opts) (document q) (->DeleteOptions opts)))))
@ -283,25 +195,30 @@
- `:sort` document representing sort order, e.g. {:timestamp -1} - `:sort` document representing sort order, e.g. {:timestamp -1}
- `:projection` document representing fields to return, e.g. {:_id 0} - `:projection` document representing fields to return, e.g. {:_id 0}
- `:keywordize?` keywordize the keys of return results, default: true - `: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 - `:raw?` return the mongo FindIterable directly instead of processing into a seq, default: false
- `:session` a ClientSession - `:session` a ClientSession
Additionally takes options specified in `collection`." Additionally takes options specified in `collection`."
([^MongoDatabase db coll q] ([^MongoDatabase db coll q]
(find db coll q {})) (find db coll q {}))
([^MongoDatabase db coll q opts] ([^MongoDatabase db coll q {:keys [limit skip sort projection ^ClientSession session keywordize? raw? realise-fn]
(let [{:keys [limit skip sort projection ^ClientSession session keywordize? raw?] :or {keywordize? true raw? false}} opts] :or {keywordize? true
(let [it (cond-> (if session realise-fn sequence}
(.find (collection db coll opts) session (document q)) :as opts}]
(.find (collection db coll opts) (document q))) (let [^ClientSession session (or session *session*)
limit (.limit limit) it (cond-> (if session
skip (.skip skip) (.find (collection db coll opts) session (document q))
sort (.sort (document sort)) (.find (collection db coll opts) (document q)))
projection (.projection (document projection)))] limit (.limit limit)
skip (.skip skip)
sort (.sort (document sort))
projection (.projection (document projection)))]
(if-not raw? (if raw?
(map (fn [x] (from-document x keywordize?)) (seq it)) it
it))))) (realise-fn ;; accomodate users who don't want to use lazy-seqs
(iterable/documents it keywordize?))))))
(defn find-one (defn find-one
"Finds a single document and returns it as a clojure map, or nil if not found. "Finds a single document and returns it as a clojure map, or nil if not found.
@ -312,16 +229,6 @@
([^MongoDatabase db coll q opts] ([^MongoDatabase db coll q opts]
(first (find db coll q (assoc opts :limit 1 :raw? false))))) (first (find db coll q (assoc opts :limit 1 :raw? false)))))
(defn ^FindOneAndUpdateOptions ->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 [^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 find-one-and-update (defn find-one-and-update
"Atomically find a document (at most one) and modify it. "Atomically find a document (at most one) and modify it.
@ -330,7 +237,10 @@
- `db` is a MongoDatabase - `db` is a MongoDatabase
- `coll` is a collection name - `coll` is a collection name
- `q` is a map representing a query. - `q` is a map representing a query.
- `update` is a map representing an update. The update to apply must include only update operators. - `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`.
- `opts` (optional), a map of: - `opts` (optional), a map of:
- `:upsert?` whether to insert a new document if nothing is found, default: false - `: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 - `:return-new?` whether to return the document after update (insead of its state before the update), default: false
@ -346,22 +256,18 @@
(find-one-and-update db coll q update {})) (find-one-and-update db coll q update {}))
([^MongoDatabase db coll q update opts] ([^MongoDatabase db coll q update opts]
(let [{:keys [keywordize? ^ClientSession session] :or {keywordize? true}} opts (let [{:keys [keywordize? ^ClientSession session] :or {keywordize? true}} opts
opts' (->FindOneAndUpdateOptions opts)] ^ClientSession session (or session *session*)
(-> (if session opts' (->FindOneAndUpdateOptions opts)]
(.findOneAndUpdate (collection db coll opts) session (document q) (document update) opts') (-> (if (instance? List update)
(.findOneAndUpdate (collection db coll opts) (document q) (document update) opts')) (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
(.findOneAndUpdate (collection db coll opts) session (document q) (document update) opts')
(.findOneAndUpdate (collection db coll opts) (document q) (document update) opts')))
(from-document keywordize?))))) (from-document keywordize?)))))
(defn ^FindOneAndReplaceOptions ->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 [^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 find-one-and-replace (defn find-one-and-replace
"Atomically find a document (at most one) and replace it. "Atomically find a document (at most one) and replace it.
@ -386,20 +292,13 @@
(find-one-and-replace db coll q doc {})) (find-one-and-replace db coll q doc {}))
([^MongoDatabase db coll q doc opts] ([^MongoDatabase db coll q doc opts]
(let [{:keys [keywordize? session] :or {keywordize? true}} opts (let [{:keys [keywordize? session] :or {keywordize? true}} opts
opts' (->FindOneAndReplaceOptions opts)] session (or session *session*)
opts' (->FindOneAndReplaceOptions opts)]
(-> (if session (-> (if session
(.findOneAndReplace (collection db coll opts) session (document q) (document doc) opts') (.findOneAndReplace (collection db coll opts) session (document q) (document doc) opts')
(.findOneAndReplace (collection db coll opts) (document q) (document doc) opts')) (.findOneAndReplace (collection db coll opts) (document q) (document doc) opts'))
(from-document keywordize?))))) (from-document keywordize?)))))
(defn ^InsertOneOptions ->InsertOneOptions
"Coerce options map into InsertOneOptions. See `insert-one` for usage."
[{:keys [insert-one-options bypass-document-validation?]}]
(let [^InsertOneOptions opts (or insert-one-options (InsertOneOptions.))]
(when (some? bypass-document-validation?) (.bypassDocumentValidation opts bypass-document-validation?))
opts))
(defn insert-one (defn insert-one
"Inserts a single document into a collection, and returns nil. "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. If the document does not have an _id field, it will be auto-generated by the underlying driver.
@ -420,18 +319,10 @@
(insert-one db coll doc {})) (insert-one db coll doc {}))
([^MongoDatabase db coll doc opts] ([^MongoDatabase db coll doc opts]
(let [opts' (->InsertOneOptions opts)] (let [opts' (->InsertOneOptions opts)]
(if-let [session (:session opts)] (if-let [session (or (:session opts) *session*)]
(.insertOne (collection db coll opts) session (document doc) opts') (.insertOne (collection db coll opts) session (document doc) opts')
(.insertOne (collection db coll opts) (document doc) opts'))))) (.insertOne (collection db coll opts) (document doc) opts')))))
(defn ^InsertManyOptions ->InsertManyOptions
"Coerce options map into InsertManyOptions. See `insert-many` for usage."
[{: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 insert-many (defn insert-many
"Inserts multiple documents into a collection. "Inserts multiple documents into a collection.
If a document does not have an _id field, it will be auto-generated by the underlying driver. If a document does not have an _id field, it will be auto-generated by the underlying driver.
@ -453,18 +344,10 @@
(insert-many db coll docs {})) (insert-many db coll docs {}))
([^MongoDatabase db coll docs opts] ([^MongoDatabase db coll docs opts]
(let [opts' (->InsertManyOptions opts)] (let [opts' (->InsertManyOptions opts)]
(if-let [^ClientSession session (:session 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) session ^List (map document docs) opts')
(.insertMany (collection db coll opts) ^List (map document docs) opts'))))) (.insertMany (collection db coll opts) ^List (map document docs) opts')))))
(defn ^ReplaceOptions ->ReplaceOptions
"Coerce options map into ReplaceOptions. See `replace-one` and `replace-many` for usage."
[{: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 replace-one (defn replace-one
"Replace a single document in a collection and returns an UpdateResult. "Replace a single document in a collection and returns an UpdateResult.
@ -485,18 +368,10 @@
([^MongoDatabase db coll q doc] ([^MongoDatabase db coll q doc]
(find-one-and-replace db coll q doc {})) (find-one-and-replace db coll q doc {}))
([^MongoDatabase db coll q doc opts] ([^MongoDatabase db coll q doc opts]
(if-let [^ClientSession session (:session opts)] (if-let [^ClientSession session (or (:session opts) *session*)]
(.replaceOne (collection db coll opts) session (document q) (document doc) (->ReplaceOptions opts)) (.replaceOne (collection db coll opts) session (document q) (document doc) (->ReplaceOptions opts))
(.replaceOne (collection db coll opts) (document q) (document doc) (->ReplaceOptions opts))))) (.replaceOne (collection db coll opts) (document q) (document doc) (->ReplaceOptions opts)))))
(defn ^UpdateOptions ->UpdateOptions
"Coerce options map into UpdateOptions. See `update-one` and `update-many` for usage."
[{: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 update-one (defn update-one
"Updates a single document in a collection and returns an UpdateResult. "Updates a single document in a collection and returns an UpdateResult.
@ -517,7 +392,7 @@
([^MongoDatabase db coll q update] ([^MongoDatabase db coll q update]
(update-one db coll q update {})) (update-one db coll q update {}))
([^MongoDatabase db coll q update opts] ([^MongoDatabase db coll q update opts]
(if-let [^ClientSession session (:session opts)] (if-let [^ClientSession session (or (:session opts) *session*)]
(.updateOne (collection db coll opts) session (document q) (document update) (->UpdateOptions opts)) (.updateOne (collection db coll opts) session (document q) (document update) (->UpdateOptions opts))
(.updateOne (collection db coll opts) (document q) (document update) (->UpdateOptions opts))))) (.updateOne (collection db coll opts) (document q) (document update) (->UpdateOptions opts)))))
@ -541,21 +416,12 @@
([^MongoDatabase db coll q update] ([^MongoDatabase db coll q update]
(update-many db coll q update {})) (update-many db coll q update {}))
([^MongoDatabase db coll q update opts] ([^MongoDatabase db coll q update opts]
(if-let [^ClientSession session (:session opts)] (if-let [^ClientSession session (or (:session opts) *session*)]
(.updateMany (collection db coll opts) session (document q) (document update) (->UpdateOptions opts)) (.updateMany (collection db coll opts) session (document q) (document update) (->UpdateOptions opts))
(.updateMany (collection db coll opts) (document q) (document update) (->UpdateOptions opts))))) (.updateMany (collection db coll opts) (document q) (document update) (->UpdateOptions opts)))))
;;; Admin functions ;;; Admin functions
(defn ^CreateCollectionOptions ->CreateCollectionOptions
"Coerce options map into CreateCollectionOptions. See `create` usage."
[{: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 create (defn create
"Creates a collection "Creates a collection
@ -575,13 +441,6 @@
(let [opts' (->CreateCollectionOptions opts)] (let [opts' (->CreateCollectionOptions opts)]
(.createCollection db coll opts')))) (.createCollection db coll opts'))))
(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?))))
(defn rename (defn rename
"Renames `coll` to `new-coll` in the same DB. "Renames `coll` to `new-coll` in the same DB.
@ -608,17 +467,6 @@
[^MongoDatabase db coll] [^MongoDatabase db coll]
(.drop (collection db coll))) (.drop (collection db coll)))
(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 create-index (defn create-index
"Creates an index "Creates an index
@ -654,7 +502,7 @@
(create-indexes db coll indexes {})) (create-indexes db coll indexes {}))
([^MongoDatabase db coll indexes opts] ([^MongoDatabase db coll indexes opts]
(->> indexes (->> indexes
(map (fn [x] (IndexModel. (document (:keys x)) (->IndexOptions x)))) (mapv (fn [x] (IndexModel. (document (:keys x)) (->IndexOptions x))))
(.createIndexes (collection db coll opts))))) (.createIndexes (collection db coll opts)))))
(defn list-indexes (defn list-indexes
@ -662,5 +510,7 @@
([^MongoDatabase db coll] ([^MongoDatabase db coll]
(list-indexes db coll {})) (list-indexes db coll {}))
([^MongoDatabase db coll opts] ([^MongoDatabase db coll opts]
(->> (.listIndexes (collection db coll opts)) (let [it (.listIndexes (collection db coll opts))
(map #(from-document % true))))) realise-fn (:realise-fn opts sequence)]
(realise-fn
(iterable/documents it true)))))

View file

@ -0,0 +1,36 @@
(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)))))

View file

@ -0,0 +1,8 @@
(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))

View file

@ -0,0 +1,251 @@
(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)))

View file

@ -1,8 +1,9 @@
(ns mongo-driver-3.client-test (ns mongo-driver-3.client-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[mongo-driver-3.client :as mg] [mongo-driver-3.client :as mg]
[mongo-driver-3.collection :as mc]) [mongo-driver-3.collection :as mc]
(:import (com.mongodb.client MongoClient MongoDatabase MongoIterable ListCollectionsIterable) [mongo-driver-3.model :as m])
(:import (com.mongodb.client MongoClient MongoDatabase MongoIterable ListCollectionsIterable ClientSession)
(java.util UUID) (java.util UUID)
(com.mongodb ClientSessionOptions ReadConcern ReadPreference) (com.mongodb ClientSessionOptions ReadConcern ReadPreference)
(java.util.concurrent TimeUnit))) (java.util.concurrent TimeUnit)))
@ -83,23 +84,44 @@
(deftest ^:integration test-with-transaction (deftest ^:integration test-with-transaction
(let [db (new-db @client) (let [db (new-db @client)
_ (mc/create db "test")] _ (mc/create db "test")]
(with-open [session (mg/start-session @client)] (with-open [session (mg/start-session @client)]
(is (= 2 (mg/with-transaction session (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 [] (fn []
(mc/insert-one db "test" {:a 1} {:session session}) (mc/insert-one db "test" {:a 1})
(mc/insert-one db "test" {:a 2} {:session session}) (mc/insert-one db "test" {:a 2})
(mc/count-documents db "test" {} {:session session})))))))) (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 (deftest ^:integration test-with-transaction
(testing "passing" (testing "passing"
(let [db (new-db @client) (let [db (new-db @client)
_ (mc/create db "test")] _ (mc/create db "test")]
(with-open [session (mg/start-session @client)] (with-open [session (mg/start-session @client)]
(is (= 2 (mg/with-transaction session (is (= 2 (mg/with-transaction session
(fn [] (fn []
(mc/insert-one db "test" {:a 1} {:session session}) (mc/insert-one db "test" {:a 1} {:session session})
(mc/insert-one db "test" {:a 2} {:session session}) (mc/insert-one db "test" {:a 2} {:session session})
(mc/count-documents db "test" {} {:session session})))))))) (mc/count-documents db "test" {} {:session session}))))))))
(testing "failing" (testing "failing"

View file

@ -2,186 +2,10 @@
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[mongo-driver-3.client :as mg] [mongo-driver-3.client :as mg]
[mongo-driver-3.collection :as mc]) [mongo-driver-3.collection :as mc])
(:import (com.mongodb ReadConcern ReadPreference WriteConcern) (:import (java.time ZoneId LocalDate LocalTime LocalDateTime)
(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) (java.util Date UUID)
(com.mongodb.client FindIterable))) (com.mongodb.client FindIterable)))
;;; Unit
(deftest test->ReadConcern
(is (nil? (mc/->ReadConcern {})))
(is (thrown? IllegalArgumentException (mc/->ReadConcern {:read-concern "invalid"})))
(is (instance? ReadConcern (mc/->ReadConcern {:read-concern :available}))))
(deftest test->ReadPreference
(is (nil? (mc/->ReadPreference {})))
(is (thrown? IllegalArgumentException (mc/->ReadPreference {:read-preference "invalid"})))
(is (instance? ReadPreference (mc/->ReadPreference {:read-preference :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 ;;; Integration
; docker run -it --rm -p 27017:27017 mongo:4.2 ; docker run -it --rm -p 27017:27017 mongo:4.2
@ -320,7 +144,11 @@
(is (instance? FindIterable (mc/find db "test" {} {:raw? true})))) (is (instance? FindIterable (mc/find db "test" {} {:raw? true}))))
(testing "keywordize" (testing "keywordize"
(is (= [{"id" 1}] (mc/find db "test" {} {:keywordize? false :projection {:_id 0 :id 1} :limit 1})))))) (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 [])}))))))
(deftest ^:integration test-find-one (deftest ^:integration test-find-one
(let [db (new-db @client) (let [db (new-db @client)
@ -392,7 +220,13 @@
(let [db (new-db @client)] (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 (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)))))) (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))))))
(deftest ^:integration test-find-one-and-replace (deftest ^:integration test-find-one-and-replace
(testing "return new" (testing "return new"
@ -408,6 +242,28 @@
(is (nil? (dissoc (mc/find-one-and-replace db "test" {:id 1} {:id 1 :v 2} {:return-new? true}) :_id))) (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)))))) (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 (deftest ^:integration test-replace-one
(testing "existing doc" (testing "existing doc"
(let [db (new-db @client) (let [db (new-db @client)
@ -497,12 +353,7 @@
(testing "not existing" (testing "not existing"
(let [db (new-db @client) (let [db (new-db @client)
_ (mc/create db "my-coll")] _ (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 (deftest ^:integration test-rename
(testing "not existing" (testing "not existing"
@ -535,7 +386,11 @@
(deftest ^:integration test-list-indexes (deftest ^:integration test-list-indexes
(let [db (new-db @client) (let [db (new-db @client)
_ (mc/create db "test")] _ (mc/create db "test")]
(is (= 1 (count (mc/list-indexes db "test"))) "has default index"))) (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 [])}))))))
(deftest ^:integration test-create-index (deftest ^:integration test-create-index
(let [db (new-db @client) (let [db (new-db @client)

View file

@ -0,0 +1,235 @@
(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)))