list collections, start session

This commit is contained in:
George Narroway 2019-11-19 13:28:46 +08:00
parent 8c206f626d
commit bd60183e1c
5 changed files with 166 additions and 14 deletions

View file

@ -1,6 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).
## [Unreleased]
### Added
- list collections
- start session
## [0.3.1]
### Added
- More documentation

View file

@ -40,13 +40,15 @@ For Leinengen, add this to your project.clj:
## Getting started
```clojure
(ns my.app
(:require [mongo-driver-3.client :as mcl]))
```
We usually start by creating a client and connecting to a database with a connection string.
`connect-to-db` is a convenience function that allows you to do this directly.
```clojure
(ns my.app
(:require [mongo-driver-3.client :as mcl]))
(mcl/connect-to-db "mongodb://localhost:27017/my-db")
; =>
; {
@ -55,7 +57,7 @@ We usually start by creating a client and connecting to a database with a connec
; }
```
You can also create a client and get a DB manually:
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.
@ -105,7 +107,7 @@ As an example:
```
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 as option. Any other
In such cases, you can pass in a configured java options object. Any other
options will be applied on top of this object.
```clojure

View file

@ -1,7 +1,9 @@
(ns mongo-driver-3.client
(:refer-clojure :exclude [find])
(:require [mongo-driver-3.collection :as mc])
(:import (com.mongodb.client MongoClients MongoClient)
(com.mongodb ConnectionString)))
(com.mongodb ConnectionString ClientSessionOptions TransactionOptions)
(java.util.concurrent TimeUnit)))
;;; Core
@ -28,6 +30,93 @@
[^MongoClient client]
(.close client))
(defn ->TransactionOptions
"Coerces options map into a TransactionOptions."
[{:keys [read-concern read-preference max-commit-time-ms] :as opts}]
(-> (TransactionOptions/builder)
(#(if max-commit-time-ms (.maxCommitTime % max-commit-time-ms (TimeUnit/MILLISECONDS)) %))
(#(if-let [rp (mc/->ReadPreference read-preference)] (.readPreference % rp) %))
(#(if-let [rc (mc/->ReadConcern read-concern)] (.readConcern % rc) %))
(#(if-let [wc (mc/->WriteConcern opts)] (.writeConcern % wc) %))
(.build)))
(defn ->ClientSessionOptions
"Coerces an options map into a ClientSessionOptions.
See `start-session` for usage"
[{:keys [client-session-options causally-consistent?] :as opts}]
(let [trans-opts (->TransactionOptions opts)]
(-> (if client-session-options (ClientSessionOptions/builder client-session-options) (ClientSessionOptions/builder))
(.defaultTransactionOptions trans-opts)
(#(if (some? causally-consistent?) (.causallyConsistent % causally-consistent?) %))
(.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."
([client] (start-session client {}))
([client opts]
(.startSession client (->ClientSessionOptions opts))))
(defn 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"
([db] (collections db {}))
([db {:keys [raw? keywordize? session] :or {keywordize? true}}]
(let [it (if session
(.listCollections db session)
(.listCollections db))]
(if-not raw?
(map #(mc/from-document % keywordize?) (seq it))
it))))
(defn 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"
([db] (collection-names db {}))
([db opts]
(let [it (if-let [session (:session opts)]
(.listCollectionNames db session)
(.listCollectionNames db))]
(if-not (:raw? opts)
(seq it)
it))))
;;; Utility
(defn connect-to-db

View file

@ -1,21 +1,77 @@
(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])
(: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
; docker run -it --rm -p 27017:27017 mongo:4.2 --replset rs1
(def mongo-host (or (System/getenv "MONGO_HOST") "mongodb://localhost:27017"))
(def 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))))))
(is (= "my-db" (.getName (:db res))))))
(def client (atom nil))
(defn- setup-connections [f]
(reset! client (mg/create mongo-host))
;; Ensure we have a replica set so we can run session tests
(let [admin-db (mg/get-db @client "admin")]
(try (.runCommand admin-db (mc/document {:replSetInitiate {}}))
(catch Exception _ "already initialized")))
(f)
(mg/close @client))
(use-fixtures :once setup-connections)
(defn new-db
[client]
(mg/get-db client (.toString (UUID/randomUUID))))
(deftest ^:integration test-collection-names
(let [db (new-db @client)
_ (mc/create db "test")]
(is (= ["test"] (mg/collection-names db)))
(is (instance? MongoIterable (mg/collection-names db {:raw? true})))))
(deftest ^:integration test-collections
(let [db (new-db @client)
_ (mc/create db "test")]
(is (= ["test"] (map :name (mg/collections db))))
(is (= ["test"] (map #(get % "name") (mg/collections db {:keywordize? false}))))
(is (instance? ListCollectionsIterable (mg/collections db {:raw? true})))))
(deftest ^:integration test-start-session
(is (instance? ClientSession (mg/start-session @client))))

View file

@ -184,7 +184,7 @@
;;; Integration
; docker run -it --rm -p 27017:27017 mongo:4.2
; docker run -it --rm -p 27017:27017 mongo:4.2 --replset rs1
(def client (atom nil))