mongo-driver-3/src/mongo_driver_3/client.clj

159 lines
6.3 KiB
Clojure
Raw Normal View History

2019-11-14 02:01:38 +00:00
(ns mongo-driver-3.client
(:refer-clojure :exclude [find])
2020-01-09 14:54:54 +00:00
(:require [mongo-driver-3.model :as m])
2019-11-22 09:55:03 +00:00
(:import (com.mongodb.client MongoClients MongoClient ClientSession MongoDatabase TransactionBody)
2019-11-19 05:28:46 +00:00
(com.mongodb ConnectionString ClientSessionOptions TransactionOptions)
(java.util.concurrent TimeUnit)))
2019-11-14 02:01:38 +00:00
2019-11-22 09:55:03 +00:00
(set! *warn-on-reflection* true)
2019-11-14 02:01:38 +00:00
;;; Core
(defn create
"Creates a connection to a MongoDB
`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)))
(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]
(.getDatabase client name))
(defn close
"Close a MongoClient and release all resources"
[^MongoClient client]
(.close client))
2019-11-19 08:01:21 +00:00
(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 seq, default: false
- `:session` a ClientSession"
([^MongoDatabase db] (list-collections db {}))
([^MongoDatabase db {:keys [raw? keywordize? ^ClientSession session] :or {keywordize? true}}]
(let [it (if session
(.listCollections db session)
(.listCollections db))]
(if-not raw?
2020-01-09 14:54:54 +00:00
(map #(m/from-document % keywordize?) (seq it))
2019-11-19 08:01:21 +00:00
it))))
(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-not (:raw? opts)
(seq it)
it))))
2019-11-19 05:28:46 +00:00
(defn ->TransactionOptions
2019-11-19 08:01:21 +00:00
"Coerces options map into a TransactionOptions. See `start-session` for usage."
[{:keys [max-commit-time-ms] :as opts}]
2020-01-09 14:54:54 +00:00
(let [rp (m/->ReadPreference opts)
rc (m/->ReadConcern opts)
wc (m/->WriteConcern opts)]
2019-11-19 08:01:21 +00:00
2019-11-22 09:55:03 +00:00
(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))))
2019-11-19 05:28:46 +00:00
(defn ->ClientSessionOptions
2019-11-19 08:01:21 +00:00
"Coerces an options map into a ClientSessionOptions See `start-session` for usage.
2019-11-19 05:28:46 +00:00
See `start-session` for usage"
[{:keys [client-session-options causally-consistent?] :as opts}]
(let [trans-opts (->TransactionOptions opts)]
2019-11-19 08:01:21 +00:00
(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))))
2019-11-19 05:28:46 +00:00
(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."
2019-11-19 08:01:21 +00:00
([^MongoClient client] (start-session client {}))
([^MongoClient client opts]
2019-11-19 05:28:46 +00:00
(.startSession client (->ClientSessionOptions opts))))
2019-11-22 09:55:03 +00:00
(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.
2021-02-12 05:24:16 +00:00
```clojure
2019-11-22 09:55:03 +00:00
(with-open [s (start-session client)]
(with-transaction s
(fn []
(insert-one my-db \"coll\" {:name \"hello\"} {:session s})
2021-02-12 05:24:16 +00:00
(insert-one my-db \"coll\" {:name \"world\"} {:session s}))))
```"
2019-11-22 09:55:03 +00:00
([^ClientSession session body] (with-transaction session body {}))
([^ClientSession session body opts]
(.withTransaction session
(reify TransactionBody
(execute [_] (body)))
(->TransactionOptions opts))))
2019-11-14 02:01:38 +00:00
;;; Utility
(defn connect-to-db
"Connects to a MongoDB database using a URI, returning the client and database as a map with :client and :db.
This is useful to get a db from a single call, instead of having to create a client and get a db manually."
[connection-string]
(let [uri (ConnectionString. connection-string)
client (MongoClients/create uri)]
(if-let [db-name (.getDatabase uri)]
{:client client :db (.getDatabase client db-name)}
(throw (IllegalArgumentException. "No database name specified in URI. connect-to-db requires database to be explicitly configured.")))))