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 # 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]
### Added
- list collections
- start session
## [0.3.1] ## [0.3.1]
### Added ### Added
- More documentation - More documentation

View file

@ -40,13 +40,15 @@ For Leinengen, add this to your project.clj:
## Getting started ## 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. 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. `connect-to-db` is a convenience function that allows you to do this directly.
```clojure ```clojure
(ns my.app
(:require [mongo-driver-3.client :as mcl]))
(mcl/connect-to-db "mongodb://localhost:27017/my-db") (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 ```clojure
;; Calling create without an arg will try and connect to the default host/port. ;; 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. 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. options will be applied on top of this object.
```clojure ```clojure

View file

@ -1,7 +1,9 @@
(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])
(:import (com.mongodb.client MongoClients MongoClient) (:import (com.mongodb.client MongoClients MongoClient)
(com.mongodb ConnectionString))) (com.mongodb ConnectionString ClientSessionOptions TransactionOptions)
(java.util.concurrent TimeUnit)))
;;; Core ;;; Core
@ -28,6 +30,93 @@
[^MongoClient client] [^MongoClient client]
(.close 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 ;;; Utility
(defn connect-to-db (defn connect-to-db

View file

@ -1,21 +1,77 @@
(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]
(:import (com.mongodb.client MongoClient MongoDatabase))) [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 ;;; 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)))
(is (instance? MongoClient (mg/create mongo-host)))) (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))) (is (thrown? IllegalArgumentException (mg/connect-to-db mongo-host)))
(let [res (mg/connect-to-db (str mongo-host "/my-db"))] (let [res (mg/connect-to-db (str mongo-host "/my-db"))]
(is (instance? MongoClient (:client res))) (is (instance? MongoClient (:client res)))
(is (instance? MongoDatabase (:db 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 ;;; 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)) (def client (atom nil))