Liking cljdoc? Tell your friends :D

Murmeli, a Clojure wrapper for the MongoDB Java driver

Build status

Clojars Project

cljdoc badge

A relative thin wrapper around the modern API of the 5.x MongoDB Java sync driver.

Status: alpha

Installation

Download from https://github.com/lassemaatta/murmeli.

Features

Supports the majority of the modern API.

(Proof-of-Concept) Supports transactions.

(Proof-of-Concept) Supports transforming plumatic/schema structures into draft 4 JSON schemas.

Unsupported features

The reactive streams API is not supported.

The legacy API is not supported.

Deprecated constructs of the modern API (e.g., MapReduce) are not supported.

Some Design/Implementation Decisions

Use :pre conditions to check mandatory parameters.

Avoid lazyness. Use IReduceInit as the root construct for queries. It gives us better control wrt. cleaning up cursors and offers better performance. Caller can use e.g. eduction to slap more processing steps before reducing the result.

TODO

  • Strict / loose JSON schema construction (ie. whether to throw if schema can't be fully represented)
  • Support for specifying JSON schema validation for a collection
  • ...

Examples

Connecting

(require '[murmeli.core :as m])

(def conn (-> {:uri       "mongodb://localhost:27017"
               :keywords? true}
              m/connect-client!
              (m/with-db "some-database")))

;; A random collection name for the examples below
(def coll :some-collection)

Inserting documents

(m/insert-many! conn coll [{:counter 1
                            :name    "bar"}
                           {:counter  2
                            :name     "quuz"
                            :location "somewhere"}
                           {:counter  3
                            :name     "asdf"
                            :location [123 456]}
                           {:name    "no counter here"
                            :aliases ["foo" "bar"]}
                           {:foo "bar"}])

Query options

Query options can be supplied as keyword arguments

(require '[murmeli.operators :refer [$exists $jsonSchema]])

(-> (m/find-all conn coll :query {:counter {$exists 1}} :projection [:name :counter] :limit 10)
    count)
;; => 3

or as a trailing map

(-> (m/find-all conn coll {:query      {:counter {$exists 1}}
                           :projection [:name :counter]
                           :limit      10})
    count)
;; => 3

Using prismatic/schema schemas for JSON schema validation / queries

(require '[murmeli.validators.schema :as vs])
(require '[schema.core :as s :refer [defschema]])

(defschema MySchema
  {(s/optional-key :_id)      (s/pred #(instance? org.bson.types.ObjectId %))
   :name                      s/Str
   (s/optional-key :counter)  s/Int
   (s/optional-key :aliases)  #{s/Str}
   (s/optional-key :location) (s/cond-pre
                                s/Str
                                [s/Int])})

(def json-schema (vs/schema->json-schema MySchema))

(-> (m/find-all conn coll :query {$jsonSchema json-schema})
    count)
;; => 4

Transactions

(m/count-collection conn coll)
;; => 5

(m/with-session [conn (m/with-client-session-options conn {:read-preference :nearest})]
  (m/insert-one! conn coll {:name "foo"})
  (m/insert-one! conn coll {:name "quuz"}))

(m/count-collection conn coll)
;; => 7

(try
  (m/with-session [conn (m/with-client-session-options conn {:read-preference :nearest})]
    (m/insert-one! conn coll {:name "another"})
    (m/insert-one! conn coll {:name "one"})
    ;; Something goes wrong within `with-session`
    (throw (RuntimeException. "oh noes")))
  (catch Exception _
    ;; Rollback occurs
    nil))

(m/count-collection conn coll)
;; => 7

Adding processing steps to the reducible

(require '[schema.coerce :as sc])

(def coerce-my-record! (sc/coercer! MySchema sc/json-coercion-matcher))

(def by-schema (->> (m/find-reducible conn coll {:query {$jsonSchema json-schema}})
                    (eduction (map coerce-my-record!)
                              (filter (comp seq :aliases)))
                    (into [])))

(count by-schema)
;; => 1

(every? (comp set? :aliases) by-schema)
;; => true

Cleanup

(m/drop-db! conn "some-database")

Development

Linting

Initialize clj-kondo cache for the project:

bb run init-kondo!

Lint the project:

bb run lint

Tests

Unit tests:

lein test

Check code examples in this README.md:

docker compose -f docker/docker-compose.yml up -d

lein run-doc-tests

License

Copyright © 2024 Lasse Määttä

This program and the accompanying materials are made available under the terms of the European Union Public License 1.2 which is available at https://eupl.eu/1.2/en/

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close