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

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) )))

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).

Setting ReadPreference in Clojure MongoDb Driver Monger

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))))

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.

How can I use ElephantSQL (Postgresql) with a Clojure app?

I added the ElephantSQL add-on to my cloudbees(?) hosted service and named the DB.
I've seen documentation on the MySQL service, and how to integrate it, but nothing for ElephantSQL.
Do I need environment config settings, or a connection string, or what?
On Heroku, I use DATABASE_URL config variable as my connection string.
We recommend using BoneCP and clojure.java.jdbc:
(ns myapp.db
(:import com.jolbox.bonecp.BoneCPDataSource)
(:import java.net.URI)
(:require [clojure.java.jdbc :as jdbc]
[clojure.string :as string]))
(def url (get (System/getenv) "ELEPHANTSQL_URL" "postgres://localhost/mydb"))
(defn pool [url]
(let [uri (URI. url)
host (.getHost uri)
port (if (pos? (.getPort uri)) (.getPort uri) 5432)
path (.getPath uri)
user-info (or (.getUserInfo uri) ":")
[user password] (string/split user-info #":")]
{:datasource
(doto (BoneCPDataSource.)
(.setJdbcUrl (str "jdbc:postgresql://" host ":" port path))
(.setPartitionCount 1)
(.setMinConnectionsPerPartition 3)
(.setMaxConnectionsPerPartition 10)
(.setUsername user)
(.setPassword password))}))
(def pooled-db (delay (pool url)))
(defn account [id]
(jdbc/query
#pooled-db
["SELECT * FROM accounts WHERE id = ?" id]))

"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"