Setting ReadPreference in Clojure MongoDb Driver Monger - mongodb

How do I set ReadPreference while using find-maps in Monger ? The Monger documentation only specifies the usage with with-collection of monger.query as shown below
(ns my.service.server
(:refer-clojure :exclude [sort find])
(:require [monger.core :as mg]
[monger.query :refer :all])
(:import com.mongodb.ReadPreference))
(let [conn (mg/connect)
db (mg/get-db conn "monger-test")
coll "scores"]
;; reads from primary (master) to guarantee consistency
;; (at the cost of putting extra load on the primary)
(with-collection db coll
(find {:email "joe#example.com"})
(read-preference (ReadPreference/primary))))

One way of doing it is setting the read preference while connecting to Mongo:
(mg/connect
(mg/server-address "127.0.0.1" 27017)
(mg/mongo-options {:read-preference (com.mongodb.ReadPreference/secondaryPreferred)}))
If you're thinking about using this method in the Java API: https://mongodb.github.io/mongo-java-driver/3.4/javadoc/com/mongodb/DBCollection.html#setReadPreference-com.mongodb.ReadPreference- I did not find a way to use this via monger. However, you can roll your own functions to take advantage of this API:
(defn find-with-read-preference [db coll]
(.find (doto
(.getCollection db (name coll))
(.setReadPreference (ReadPreference/secondaryPreferred)))))
(defn find-maps-with-read-preference
([^DB db ^String coll]
(with-open [result (find-with-read-preference db coll)]
(map (fn [x] (monger.conversion/from-db-object x true)) result))))

Related

The connection string is invalid mongo db

I am using monger to connect with the database, but I'm getting that the connection string is invalid:
Caused by: java.lang.IllegalArgumentException: The connection string is invalid. Connection strings must start with 'mongodb://'
Here's my code in which I'm putting the connection string:
(let [{:keys [conn db]} (mg/connect-via-uri "mongodb+srv://username:pass#cluster0-ww5gh.mongodb.net/test?retryWrites=true&w=majority")
fs (mg/get-gridfs conn "test")]
(defn store-file [{:keys [file filename format content-type]}]
(gfs/store-file (gfs/make-input-file fs file)
(gfs/filename filename)
(gfs/metadata {:format format})
(gfs/content-type content-type)))
(defn gfs-find-by-id [id]
(gfs/find-by-id fs id))
(defn find-file [id] (gfs/find-by-id fs id))
(defn find-one [coll query] (mc/find-one-as-map db coll query))
(defn find-by-id [coll id] (mc/find-map-by-id db coll id))
(defn find [coll query] (mc/find-maps db coll query))
(defn insert [coll query] (mc/insert-and-return db coll query))
(defn update [coll entry query] (mc/update db coll entry query {:upsert true}))
(defn update-multi [coll entry query] (mc/update db coll entry query {:upsert true :multi true}))
(defn update-by-id [coll id query] (mc/update-by-id db coll id query {:upsert true}))
(defn find-page-n [coll query page n]
(prn "finding" n "items on page " page)
(q/with-collection
db
coll
(q/find query)
(q/paginate :page page :per-page n))))
How to fix this error?
The connection string, that also allows for the mongodb+srv "protocol" was introduced for MongoDB 3.6. If you instance is of this or higher version, then you must update your monger dependency to a current version. Otherwise use mongodb for the protocol (as the error message suggests).

Running "repairDatabase" with Monger?

Is it possible to execute the "repairDatabase" command with Monger? If so how?
you can do like this:
(:require [clojure.test :refer :all]
[monger.core :as mc]))
(deftest ^:focused repair-db-test
(let [conn (mc/connect)
db (mc/get-db conn "test-db")]
(is (= {"ok" 1.0}
(mc/command db {:repairDatabase 1})))))
I found out that you can just do:
(monger.core/command db {:repairDatabase 1})

clojure.data.xml and parsing jdbc xml object

I'm attempting to read xml (actual xml type) from a postgres database in clojure, however i'm not sure how to cast the org.postgresql.jdbc4.Jdbc4SQLXML object to something clojure.data.xml can make sense of.
When running (xml/parse ...) on this object i'm getting the following error:
IllegalArgumentException No matching method found: createXMLStreamReader for class com.sun.xml.internal.stream.XMLInputFactoryImpl clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:80)
code below:
(ns clj-xml-dbms.core-test
(:require [clojure.test :refer :all]
[clojure.java.jdbc :as j]
[clojure.data.xml :as xml]
[clj-xml-dbms.core :refer :all])
(:use [clojure.pprint ] ))
(def db-spec {
:classname "org.postgres.Driver"
:subprotocol "postgres"
:subname "//localhost:5432/mydb"
:user "me"
:password "secret"
})
;; get the results of a query
(def results
(let [
sql "select row_id, xml_col from stg.some_table limit 1 "
db-connection (j/get-connection db-spec )
statement (j/prepare-statement db-connection sql)
query-results (j/query db-connection [statement]) ]
(first query-results)))
(pprint results)
;; {:row_id 18627,
;; :xml_col #object[org.postgresql.jdbc4.Jdbc4SQLXML 0x6897f635 "org.postgresql.jdbc4.Jdbc4SQLXML#6897f635"]}
;; obviously clojure.data.xml/parse isn't going to work on that:
(xml/parse (:xml_col results))
;; IllegalArgumentException No matching method found: createXMLStreamReader for class com.sun.xml.internal.stream.XMLInputFactoryImpl clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:80)
I guess i could cast the xml type in the db to a string, but there has to be something cleaner that that i would think. Any tips appreciated, thanks.

"Shared" Connection on Monger Clojure

I am start with monger today, the docs use examples like this:
(let [conn (mg/connect)
db (mg/get-db conn "monger-test")
coll "documents"]
(mc/insert db coll {:first_name "John" :last_name "Lennon"})
(mc/insert db coll {:first_name "Ringo" :last_name "Starr"})
(mc/find db coll {:first_name "Ringo"}))
All documentation examples use that structure. Always connect to MongoDB, then use db on mc/insert... . The question is, how I can put this code on a function and use it in my functions that execute mongodb queries, without repeat below code all time:
(let [conn (mg/connect)
db (mg/get-db conn "monger-test")
coll "documents"] ...
Thanks.
Here's how I did it:
(defmacro with-db [body]
`(let [conn# (mg/connect)
db# (mg/get-db conn "monger-test")]
(-> db#
~body)))
Used like this:
(with-db (mc/find-maps "mycoll"))
you can also do this without defining a macro, using just a plain old function:
(def db-name "monger-test")
(defn with-db [op & args]
(let [conn (mongo/connect)
db (mongo/get-db conn db-name)]
(apply op db args)))
(with-db monger.collection/find-maps "test-collection")
will list all the entries in the collection named "test-collection"

How do I drop or create a database from clojure.java.jdbc?

I would like to create/drop a database from clojure.java.jdbc. This fails:
(require '[clojure.java.jdbc :as sql])
(def db
{:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//localhost/postgres"
:user "postgres"})
(defn drop-database [name]
(sql/do-commands (str "drop database " name)))
(sql/with-connection db
(drop-database "db_name"))
because do-commands starts a transaction, and apparently you can't drop or create databases inside a transaction. Any ideas?
Thanks!
Take the source for do-commands (here) and remove the call to transaction:
(defn drop-database [name]
(sql/with-connection db
(with-open [s (.createStatement (sql/connection))]
(.addBatch s (str "drop database " name))
(seq (.executeBatch s)))))
The transactionless execution functionality was rolled into db-do-commands.
Now this slightly simpler version is working:
(jdbc/db-do-commands postgres-db false "CREATE DATABASE foo")
If you don't specify false as the second argument, it won't work as it will attempt to start a transaction.
With newer clojure versions, the suggested approach no longer works. I was successful with this function:
(defn exec-db-command [db command]
(jdbc/with-db-connection [conn db]
(with-open [s (.createStatement (:connection conn))]
(.executeUpdate s command))))
(exec-db-command db "create database foo")
This is the only solution that worked for me
(def datasource-options {:auto-commit true
:read-only false
:connection-timeout 30000
:validation-timeout 5000
:idle-timeout 600000
:max-lifetime 1800000
:minimum-idle 10
;; :maximum-pool-size 10
:pool-name "db-pool"
:adapter (:database-adapter env)
:username (:database-username env)
:password (:database-password env)
:database-name (:database-name env)
:server-name (:database-host env)
:port-number (:database-port env)
:register-mbeans false})
(defonce datasource
(delay (make-datasource datasource-options)))
(defn db-jdbc-uri [& {:as args}]
(let [datasource-options (merge datasource-options args)]
(format "jdbc:%s://%s:%s/%s?user=%s&password=%s"
(datasource-options :adapter) (datasource-options :server-name)
(datasource-options :port-number) (datasource-options :database-name)
(datasource-options :username) (datasource-options :password))))
(defn create-database [name]
(println {:connection-uri (db-jdbc-uri :database-name "")})
(jdbc/with-db-connection [conn {:connection-uri (db-jdbc-uri :database-name "")}]
(jdbc/db-do-commands conn false (str "CREATE DATABASE " name))))
(defn drop-database [name]
(jdbc/with-db-connection [conn {:connection-uri (db-jdbc-uri :database-name "")}]
(jdbc/db-do-commands conn false (str "DROP DATABASE " name) )))
Basically you need to connect without providing a database or connecting to a different database (not the one you are deleting)
This will get transilated to this code.
(defn create-database [name]
(jdbc/with-db-connection [conn {:connection-uri "jdbc:postgresql://localhost/postgres?user=<name>&password=<pass>"}]
(jdbc/db-do-commands conn false (str "CREATE DATABASE " name) )))