Compare commits

...

56 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
George Narroway
4942c3426c 0.5.0 release 2019-11-22 17:58:29 +08:00
George Narroway
f737c6ffb6 support transactions 2019-11-22 17:55:03 +08:00
George Narroway
c723f2a36f operator docs 2019-11-22 13:50:54 +08:00
George Narroway
f3aadc7670 operator docs 2019-11-22 13:49:46 +08:00
George Narroway
5512590e96 0.4.0 release 2019-11-19 16:04:21 +08:00
George Narroway
db93922521 remove reflection warnings 2019-11-19 16:01:21 +08:00
George Narroway
b7ba5599bf skip session test until we can set up a replset nicely 2019-11-19 13:42:33 +08:00
George Narroway
55fec074f2 start mongo as replset 2019-11-19 13:30:48 +08:00
George Narroway
bd60183e1c list collections, start session 2019-11-19 13:28:46 +08:00
George Narroway
8c206f626d more typos 2019-11-17 15:36:10 +08:00
George Narroway
df95e717a8 typos 2019-11-17 14:21:52 +08:00
George Narroway
1b1acd13c1 typos 2019-11-17 14:17:43 +08:00
George Narroway
431a18e8ba typos 2019-11-17 14:06:57 +08:00
George Narroway
ea59047585 v0.3.1 release 2019-11-17 14:04:54 +08:00
George Narroway
2a128fc77d Only build on push to avoid duplicate build 2019-11-17 14:02:07 +08:00
George Narroway
ac1f598f4f More docs 2019-11-17 14:00:30 +08:00
George Narroway
e6d935c47e 0.3.0 2019-11-15 08:53:13 +08:00
George Narroway
62b879583f add aggregate; update docs 2019-11-14 20:09:01 +08:00
George Narroway
e931d78bf0 0.2.0 release 2019-11-14 14:19:54 +08:00
George Narroway
95277e4bfe Add some operators 2019-11-14 14:17:00 +08:00
15 changed files with 1710 additions and 745 deletions

View file

@ -1,6 +1,9 @@
name: Clojure CI
on: [push]
on:
push:
branches:
- master
jobs:
build:

2
.gitignore vendored
View file

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

View file

@ -1,8 +1,58 @@
# 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
### Added
- More documentation
## 0.3.0 - 2019-11-15
### Added
- Added aggregate function
- `skip` option to `find`
### Changed
- Better docs for cljdoc
- Merged `find-maps` into `find`
- Added ? suffix to boolean params
- Renamed `find-one-as-map` to `find-one`
## 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.1.0...HEAD

170
README.md
View file

@ -2,7 +2,10 @@
[![Clojars Project](https://img.shields.io/clojars/v/mongo-driver-3.svg)](https://clojars.org/mongo-driver-3)
A Mongo client for clojure, lightly wrapping 3.11+ versions of the [MongoDB Java Driver](https://mongodb.github.io/mongo-java-driver/)
[![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/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).
Like our HTTP/2 client [hato](https://github.com/gnarroway/hato), the API is designed to be idiomatic and to make common
@ -10,16 +13,17 @@ tasks convenient, whilst still allowing the underlying client to be configured v
It was developed with the following goals:
- Simple
- Up to date with the latest driver versions
- Minimal layer that doesn't block any functionality
- Minimal layer that does not prevent access to the underlying driver
- Consistent API across all functions
- Configuration over macros
- Simple
## Status
mongo-driver-3 is under active development but the existing API is unlikely to break.
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.
## Usage
@ -28,12 +32,166 @@ For Leinengen, add this to your project.clj:
```clojure
;; The underlying driver -- any newer version can also be used
[org.mongodb/mongodb-driver-sync "3.11.0"]
[org.mongodb/mongodb-driver-sync "4.11.1"]
;; This wrapper library
[mongo-driver-3 "0.1.0"]
[mongo-driver-3 "0.8.0"]
```
## 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
(mcl/connect-to-db "mongodb://localhost:27017/my-db")
; =>
; {
; :client - a MongoClient instance
; :db - a Database that you can pass to all the collection functions
; }
```
You can also create a client and get a DB separately:
```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"))
```
### Collection functions
All the collection functions closely mirror the naming in 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,
and an optional map of options as the last. Full documentation of options can be found on
[cljdoc](https://cljdoc.org/d/mongo-driver-3/mongo-driver-3/CURRENT/api/mongo-driver-3.collection).
As an example:
```clojure
(ns my.app
(:require [mongo-driver-3.collection :as mc]))
;; Insert some documents
(mc/insert-many db "test" [{:v "hello"} {:v "world"}])
;; Count all documents
(mc/count-documents db "test")
; => 2
;; Count with a query
(mc/count-documents db "test" {:v "hello"})
; => 1
;; Find the documents, returning a seq
(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})
; => a MongoIterable
;; Find a single document or return nil
(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.
```clojure
;; These are equivalent
(mc/rename db "test" "new-test" {:drop-target? true})
(mc/rename db "test" "new-test" {:rename-collection-options (.dropTarget (RenameCollectionOptions.) true)})
```
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

View file

@ -1,5 +1,5 @@
(defproject mongo-driver-3 "0.1.0"
:description "A Clojure wrapper for the Java MongoDB driver 3.11+."
(defproject mongo-driver-3 "0.9.0-SNAPSHOT"
:description "A Clojure wrapper for the Java MongoDB driver 3.11/4.0+."
: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.10.1"]
[org.mongodb/mongodb-driver-sync "3.11.0"]]}})
:profiles {:dev {:dependencies [[org.clojure/clojure "1.11.1"]
[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,7 +1,12 @@
(ns mongo-driver-3.client
(:refer-clojure :exclude [find])
(:import (com.mongodb.client MongoClients MongoClient)
(com.mongodb ConnectionString)))
(: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)
;;; Core
@ -11,16 +16,15 @@
`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."
([] (MongoClients/create))
([^String connection-string]
(MongoClients/create connection-string)))
(^MongoClient [] (MongoClients/create))
(^MongoClient [^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."
[^MongoClient client ^String name]
^MongoDatabase [^MongoClient client ^String name]
(.getDatabase client name))
(defn close
@ -28,6 +32,154 @@
[^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

File diff suppressed because it is too large Load diff

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

@ -0,0 +1,282 @@
(ns mongo-driver-3.operator
"Define constants corresponding to mongo operators")
(defmacro defoperator
[op]
`(def ^{:const true} ~op ~(str op)))
;;; --- Query operators
;;; https://docs.mongodb.com/manual/reference/operator/query/
;;; Comparison
(defoperator $eq)
(defoperator $gt)
(defoperator $gte)
(defoperator $in)
(defoperator $lt)
(defoperator $lte)
(defoperator $ne)
(defoperator $nin)
;;; Logical
(defoperator $and)
(defoperator $not)
(defoperator $nor)
(defoperator $or)
;;; Element
(defoperator $exists)
(defoperator $type)
;;; Evaluation
(defoperator $expr)
(defoperator $jsonSchema)
(defoperator $mod)
(defoperator $regex)
(defoperator $text)
(defoperator $where)
;;; Geospatial
(defoperator $geoIntersects)
(defoperator $geoWithin)
(defoperator $near)
(defoperator $geoSphere)
;;; Array
(defoperator $all)
(defoperator $elemMatch)
(defoperator $size)
;;; Bitwise
(defoperator $bitsAllClear)
(defoperator $bitsAllSet)
(defoperator $bitsAnyClear)
(defoperator $bitsAnySet)
;;; Comments
(defoperator $comment)
;;; Projection
(defoperator $)
(defoperator $meta)
(defoperator $slice)
;;; --- Update operators
;;; https://docs.mongodb.com/manual/reference/operator/update/
;;; Fields
(defoperator $currentDate)
(defoperator $inc)
(defoperator $min)
(defoperator $max)
(defoperator $mul)
(defoperator $rename)
(defoperator $set)
(defoperator $setOnInsert)
(defoperator $unset)
;;; Array operators
(defoperator $addToSet)
(defoperator $pop)
(defoperator $pull)
(defoperator $push)
(defoperator $pushAll)
;;; Array modifiers
(defoperator $each)
(defoperator $position)
(defoperator $slice)
(defoperator $sort)
;;; Bitwise
(defoperator $bit)
;;; --- Aggregation stages
;;; https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
(defoperator $addFields)
(defoperator $bucket)
(defoperator $bucketAuto)
(defoperator $collStats)
(defoperator $count)
(defoperator $currentOp)
(defoperator $facet)
(defoperator $geoNear)
(defoperator $graphLookup)
(defoperator $group)
(defoperator $indexStats)
(defoperator $limit)
(defoperator $listLocalSessions)
(defoperator $listSessions)
(defoperator $lookup)
(defoperator $match)
(defoperator $merge)
(defoperator $out)
(defoperator $planCacheStats)
(defoperator $project)
(defoperator $redact)
(defoperator $replaceRoot)
(defoperator $replaceWith)
(defoperator $sample)
(defoperator $set)
(defoperator $skip)
(defoperator $sort)
(defoperator $sortByCount)
(defoperator $unset)
(defoperator $unwind)
;;; --- Aggregation operators
;;; https://docs.mongodb.com/manual/reference/operator/aggregation/
(defoperator $abs)
(defoperator $acos)
(defoperator $acosh)
(defoperator $add)
(defoperator $addToSet)
(defoperator $allElementsTrue)
(defoperator $and)
(defoperator $anyElementTrue)
(defoperator $arrayElemAt)
(defoperator $arrayToObject)
(defoperator $asin)
(defoperator $asinh)
(defoperator $atan)
(defoperator $atan2)
(defoperator $atanh)
(defoperator $avg)
(defoperator $ceil)
(defoperator $cmp)
(defoperator $concat)
(defoperator $concatArrays)
(defoperator $cond)
(defoperator $convert)
(defoperator $cos)
(defoperator $dateFromParts)
(defoperator $dateToParts)
(defoperator $dateFromString)
(defoperator $dateToString)
(defoperator $dayOfMonth)
(defoperator $dayOfWeek)
(defoperator $dayOfYear)
(defoperator $degreesToRadians)
(defoperator $divide)
(defoperator $eq)
(defoperator $exp)
(defoperator $filter)
(defoperator $first)
(defoperator $floor)
(defoperator $gt)
(defoperator $gte)
(defoperator $hour)
(defoperator $ifNull)
(defoperator $in)
(defoperator $indexOfArray)
(defoperator $indexOfBytes)
(defoperator $indexOfCP)
(defoperator $isArray)
(defoperator $isoDayOfWeek)
(defoperator $isoWeek)
(defoperator $isoWeekYear)
(defoperator $last)
(defoperator $let)
(defoperator $literal)
(defoperator $ln)
(defoperator $log)
(defoperator $log10)
(defoperator $lt)
(defoperator $lte)
(defoperator $ltrim)
(defoperator $map)
(defoperator $max)
(defoperator $mergeObjects)
(defoperator $meta)
(defoperator $min)
(defoperator $millisecond)
(defoperator $minute)
(defoperator $mod)
(defoperator $month)
(defoperator $multiply)
(defoperator $ne)
(defoperator $not)
(defoperator $objectToArray)
(defoperator $or)
(defoperator $pow)
(defoperator $push)
(defoperator $radiansToDegrees)
(defoperator $range)
(defoperator $reduce)
(defoperator $regexFind)
(defoperator $regexFindAll)
(defoperator $regexMatch)
(defoperator $reverseArray)
(defoperator $round)
(defoperator $rtrim)
(defoperator $second)
(defoperator $setDifference)
(defoperator $setEquals)
(defoperator $setIntersection)
(defoperator $setIsSubset)
(defoperator $setUnion)
(defoperator $size)
(defoperator $sin)
(defoperator $slice)
(defoperator $split)
(defoperator $sqrt)
(defoperator $stdDevPop)
(defoperator $stdDevSamp)
(defoperator $strcasecmp)
(defoperator $strLenBytes)
(defoperator $strLenCP)
(defoperator $substr)
(defoperator $substrBytes)
(defoperator $substrCP)
(defoperator $subtract)
(defoperator $sum)
(defoperator $switch)
(defoperator $tan)
(defoperator $toBool)
(defoperator $toDate)
(defoperator $toDecimal)
(defoperator $toDouble)
(defoperator $toInt)
(defoperator $toLong)
(defoperator $toObjectId)
(defoperator $toString)
(defoperator $toLower)
(defoperator $toUpper)
(defoperator $trim)
(defoperator $trunc)
(defoperator $type)
(defoperator $week)
(defoperator $year)
(defoperator $zip)
;;; --- Query modifiers
;;; https://docs.mongodb.com/manual/reference/operator/query-modifier/
(defoperator $comment)
(defoperator $explain)
(defoperator $hint)
(defoperator $max)
(defoperator $maxTimeMS)
(defoperator $min)
(defoperator $orderby)
(defoperator $query)
(defoperator $returnKey)
(defoperator $showDiskLoc)
(defoperator $natural)

View file

@ -1,21 +1,135 @@
(ns mongo-driver-3.client-test
(:require [clojure.test :refer :all]
[mongo-driver-3.client :as mg])
(:import (com.mongodb.client MongoClient MongoDatabase)))
[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")))
;;; Integration
; docker run -it --rm -p 27017:27017 mongo:4.2
(def mongo-host "mongodb://localhost:27017")
(def mongo-host (or (System/getenv "MONGO_HOST") "mongodb://localhost:27017"))
(deftest test-create
(deftest ^:integration test-create
(is (instance? MongoClient (mg/create)))
(is (instance? MongoClient (mg/create mongo-host))))
(deftest test-connect-to-db
(deftest ^:integration 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" {}))))))))))

View file

@ -2,185 +2,9 @@
(:require [clojure.test :refer :all]
[mongo-driver-3.client :as mg]
[mongo-driver-3.collection :as mc])
(: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)
(:import (java.time ZoneId LocalDate LocalTime LocalDateTime)
(java.util Date UUID)
(com.mongodb.client MongoDatabase)))
;;; 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")))
(com.mongodb.client FindIterable)))
;;; Integration
@ -204,7 +28,7 @@
(let [db (new-db @client)
doc {:hello "world"}
_ (mc/insert-one db "test" doc)
res (mc/find-maps db "test" {})]
res (mc/find db "test" {})]
(is (= 1 (count res)))
(is (= doc (select-keys (first res) [:hello])))))
@ -225,7 +49,7 @@
:localdatetime (LocalDateTime/now)
:localtime (LocalTime/now)}
_ (mc/insert-one db "test" doc)
res (mc/find-one-as-map db "test" {} {:projection {:_id 0}})]
res (mc/find-one db "test" {} {:projection {:_id 0}})]
(is (= {:nil nil
:string "string"
:int 1
@ -245,7 +69,7 @@
(testing "basic insertions"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1} {:id 2}])
res (mc/find-maps db "test" {})]
res (mc/find db "test" {})]
(is (= 2 (count res)))
(is (= [1 2] (map :id res)))))
@ -279,58 +103,78 @@
(is (= 2 (.getDeletedCount (mc/delete-many db "test" {:v 1}))))
(is (= 0 (.getDeletedCount (mc/delete-many db "test" {:v 1})))))))
(deftest ^:integration test-find-maps
(deftest ^:integration test-find
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :a 1 :v 2} {:id 2 :a 1 :v 3} {:id 3 :v 1}])]
(testing "query"
(are [ids q] (= ids (map :id (mc/find-maps db "test" q)))
(are [ids q] (= ids (map :id (mc/find db "test" q)))
[1 2 3] {}
[1] {:id 1}
[1 2] {:a {:$exists true}}
[2] {:v {:$gt 2}}))
(testing "sort"
(are [ids s] (= ids (map :id (mc/find-maps db "test" {} {:sort s})))
(are [ids s] (= ids (map :id (mc/find db "test" {} {:sort s})))
[1 2 3] {}
[3 1 2] {:v 1}
[2 1 3] {:v -1}))
(testing "skip"
(are [cnt n] (= cnt (count (mc/find db "test" {} {:skip n})))
3 0
2 1
1 2
0 3))
(testing "limit"
(are [cnt n] (= cnt (count (mc/find-maps db "test" {} {:limit n})))
(are [cnt n] (= cnt (count (mc/find db "test" {} {:limit n})))
1 1
2 2
3 3
3 4))
(testing "projection"
(are [ks p] (= ks (keys (first (mc/find-maps db "test" {} {:projection p}))))
(are [ks p] (= ks (keys (first (mc/find db "test" {} {:projection p}))))
[:_id :id :a :v] {}
[:_id :a] {:a 1}
[:id :a :v] {:_id 0}))))
[:id :a :v] {:_id 0}))
(deftest ^:integration test-find-one-as-map
(testing "raw"
(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 [])}))))))
(deftest ^:integration test-find-one
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :a 1 :v 2} {:id 2 :a 1 :v 3} {:id 3 :v 1}])]
(testing "query"
(are [id q] (= id (:id (mc/find-one-as-map db "test" q)))
(are [id q] (= id (:id (mc/find-one db "test" q)))
1 {}
2 {:id 2}
1 {:a {:$exists true}}
2 {:v {:$gt 2}}))
(testing "sort"
(are [id s] (= id (:id (mc/find-one-as-map db "test" {} {:sort s})))
(are [id s] (= id (:id (mc/find-one db "test" {} {:sort s})))
1 {}
3 {:v 1}
2 {:v -1}))
(testing "projection"
(are [ks p] (= ks (keys (mc/find-one-as-map db "test" {} {:projection p})))
(are [ks p] (= ks (keys (mc/find-one db "test" {} {:projection p})))
[:_id :id :a :v] {}
[:_id :a] {:a 1}
[:id :a :v] {:_id 0}))))
[:id :a :v] {:_id 0}))
(testing "keywordize"
(is (= {"id" 1} (mc/find-one db "test" {} {:keywordize? false :projection {:_id 0 :id 1}}))))))
(deftest ^:integration test-count-documents
(let [db (new-db @client)
@ -376,7 +220,13 @@
(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))))))
(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
(testing "return new"
@ -392,6 +242,28 @@
(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)
@ -481,12 +353,7 @@
(testing "not existing"
(let [db (new-db @client)
_ (mc/create 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"))))))
(is (true? (coll-exists? db "my-coll"))))))
(deftest ^:integration test-rename
(testing "not existing"
@ -519,7 +386,11 @@
(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")))
(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
(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)))