I am parsing the postgresql uri in my config settings on Heroku. But I cannot seem to get it working. Any help would be greatly appreciated, I'm probably missing something straight forward.
Here is the code used.
(def dev-db-info
{:db "dbname"
:user "username"})
(defn parse-db-uri
[uri]
(drop 1 (split uri #"://|:|#|/")))
(defn create-map-from-uri
[uri]
(let [parsed (parse-db-uri uri)]
(zipmap [:user :password :host :port :db] parsed)))
(defn db-info
[]
(if production?
(create-map-from-uri (System/getenv "DATABASE_URL"))
dev-db-info))
(defdb connected-db
(postgres (db-info)))
The map I retrieve from the uri looks like this:
{:db "dbname"
:port "5662"
:host "ec2-url.compute-1.amazonaws.com"
:password "pwd"
:user "username"}
I get the following error:
Connections could not be acquired from the underlying database!
EDIT:
I have since given up on using Korma, and switched to using Clojure.JDBC 0.2.3 which supports "connection-uri" and therefore ssl connections to the db. Korma doesn't currently support this. I will file an issue on Github to allow this connection method.
EDIT:
There's no reason to use [org.clojars.ccfontes/korma "0.3.0-beta12-pgssl"] anymore. Read this to know more about it. Also, please ignore the following instructions.
Added postgres SSL support.
In project.clj insert:
[org.clojars.ccfontes/korma "0.3.0-beta12-pgssl"]
Defining a connection to a postgres database on heroku:
(ns app.db
(:require [clojure.java.jdbc :as sql]
[korma.db :as db]
[clojure.string :as string])
(:import (java.net URI)))
(defn set-app-pg-db! [mode]
(let [db-uri (java.net.URI. (System/getenv "DATABASE_URL"))]
(->> (string/split (.getUserInfo db-uri) #":")
(#(identity {:db (last (string/split (System/getenv "DATABASE_URL") #"\/"))
:host (.getHost db-uri)
:port (.getPort db-uri)
:user (% 0)
:password (% 1)
:ssl true
:sslfactory (when (= mode :dev) "org.postgresql.ssl.NonValidatingFactory")}))
(db/postgres)
(db/defdb app-pg-db))))
The fix uses Tomcat JDBC Connection Pool and their configuration sample for the connection pool, so it may not be well suited for everyone's needs, plus this is only a hack. Ideally the original Korma project should integrate these changes or other possible solution.
Would appreciate some feedback from other people since it was only tested in my own project. Thanks.
Actually the solution is really simple and just works locally:
(defn- convert-db-uri [db-uri]
(let [[_ user password host port db] (re-matches #"postgres://(?:(.+):(.*)#)?([^:]+)(?::(\d+))?/(.+)" db-uri)]
{
:user user
:password password
:host host
:port (or port 80)
:db db
}))
(def db-spec (postgres
(convert-db-uri
(config/get "DATABASE_URL"))))
Where DATABASE_URL is "postgres://user:pw#host:port/dbname?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory"
It seems the db name forwards the SSL parameters to the underlying driver and it just works.
This is with:
[korma "0.3.0-beta9"]
[org.clojure/java.jdbc "0.1.3"]
[postgresql/postgresql "9.1-901.jdbc4"]
In your EDIT you mention switching to clojure.java.jdbc because it allowed you to enable SSL using the connection URI. You can use the same technique with Korma using the function korma.db/defdb which allows you to provide your own connection URL and enable SSL using the query string like this:
(defdb korma-db {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//localhost:5432/test?ssl=true"
:user "my-username"
:password "my-password"})
FWIW, here's code I've used to get a clojure.java.jdbc db-spec (which I think is what Korma wants) from Heroku's DATABASE_URL.
(def db-uri (java.net.URI. (System/getenv "DATABASE_URL")))
(def user-and-password (clojure.string/split (.getUserInfo db-uri) #":"))
(def db
{:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:user (get user-and-password 0)
:password (get user-and-password 1) ; may be nil
:subname (if (= -1 (.getPort db-uri))
(format "//%s%s" (.getHost db-uri) (.getPath db-uri))
(format "//%s:%s%s" (.getHost db-uri) (.getPort db-uri) (.getPath db-uri)))})
Related
I'm trying to pass something like this in clojure
(def pg-uri {:connection-uri "jdbc:postgresql://user:password#host.aws-us-west-2.cockroachlabs:12345/defaultdb?sslmode=verify-full&options=--cluster%3Dfoobar"})
(defn query [q]
(let [conn (j/get-connection pg-uri)]
(j/query pg-db q))
(query (-> (select :*) (from :user) (sql/format)))
but am getting ; Execution error (SocketTimeoutException) at sun.nio.ch.NioSocketImpl/timedFinishConnect (NioSocketImpl.java:539). ; Connect timed out
I was wondering how I can fix this. I'm able to connect just fine using another client. Thank you
In JDBC URLs (at least, in the jdbc:postgresql: ones), the password and the username must be specified as parameters and not in front of the server:
(org.postgresql.Driver/parseURL
"jdbc:postgresql://host.aws-us-west-2.cockroachlabs:12345/defaultdb?sslmode=verify-full&user=user&password=password&options=--cluster%3Dfoobar"
nil)
=>
{"sslmode" "verify-full",
"PGDBNAME" "defaultdb",
"PGPORT" "12345",
"PGHOST" "host.aws-us-west-2.cockroachlabs",
"password" "password",
"options" "--cluster=foobar",
"user" "user"}
I have defined my postgres database as
(def db {:subprotocol "postgresql"
:subname "//localhost:5432/mydb"
:user "admin"
:password "password"})
I have also defined a function
(defn get-users []
(sql/query db ["select * from users"]))
where sql is [clojure.java.jdbc :as sql]
If I run (get-users) I get the error
SQLException No suitable driver found for jdbc:postgresql://127.0.0.1:5432/mydb java.sql.DriverManager.getConnection (DriverManager.java:689)
I've seen from other Java posts that I need to load the driver using Class.forName("org.postgresql.Driver");
1) What does this mean?
2) How do I do this/solve my error in Clojure?
The solution is to add this to your :dependencies in your project.clj:
[org.postgresql/postgresql "42.1.4"]
Also, while your db definition is fine, instead of the concatenated string for :subname, you can also separately define the host, port, and db name, which makes it more modular and composable in case any one of them changes:
(def db {:dbtype "postgresql"
:dbname "mydb"
:host "localhost"
:port 5432
:user "userrole"
:password "password"})
This means that the JVM needs to have loaded the Postgres driver class before the driver can be used. The sql/query call uses the driver. Usually in Java classes are instantiated, so the class is automatically loaded. But notice that your code has no new, nor is a static factory (constructor) method called. With the call to sql/query you are in fact directly calling the function java.sql.DriverManager.getConnection, without the class DriverManager ever having been loaded. Presumably loading Driver loads DriverManager.
From http://clojure-doc.org/articles/language/interop.html I found this:
(Class/forName "java.util.Date")
So you could try:
(Class/forName "org.postgresql.Driver")
I'm at that point where frustration just makes you incapable of seeing the solution...
My project.clj
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/java.jdbc "0.4.2"]
[org.postgresql/postgresql "9.4.1208"]]
Run lein deps (all is okay)
Run my query:
(db/query "postgresql://user:secret#host"
["select * from table limit 1"])
I get the following error:
Unhandled java.sql.SQLException
No suitable driver found for
jdbc:postgresql://host
...Please. Any ideas?
Probably need to specify the java driver to use and the other parameters in the db descriptor.
I usually use something similar to:
(use 'clojure.java.jdbc)
(let [db { :classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//192.168.99.100:5432/postgres"
:user "postgres"
:password "mysecretpassword"}]
(query db ["select count(*) from example" ]) )
; ({:count 6005247})
maybe your db spec is wrong, I use postgresql spec:
postgres://user:password#host:5432/mydb
I've been trying to make a basic web app in Compojure, hosted on Heroku. I've been following the tutorial on this website:
http://www.vijaykiran.com/2012/01/17/web-application-development-with-clojure-part-2/
and have been grinding away at the Lobos and Korma part for about 2 days now. My app can now connect to my local Postgres server, but when I try to push to Heroku or connect to my Heroku Postgres db, I get the following error:
PSQLException FATAL: no pg_hba.conf entry for host "the IP", user "the username", database "the dbname", SSL off org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication (ConnectionFactoryImpl.java:291)
Here's my project.clj:
(defproject portfolio "1.0.0-SNAPSHOT"
:description "My personal portfolio"
:url "the URL"
:license {:name "FIXME: choose"
:url "http://example.com/FIXME"}
:dependencies [[compojure "1.1.1"]
[ring/ring-jetty-adapter "1.1.0"]
[ring/ring-devel "1.1.0"]
[ring-basic-authentication "1.0.1"]
[environ "0.4.0"]
[com.cemerick/drawbridge "0.0.6"]
[hiccup "1.0.4"]
[lobos "1.0.0-beta1"]
[korma "0.3.0-RC5"]
[org.clojure/java.jdbc "0.2.3"]
[postgresql "9.1-901.jdbc4"]
[clj-yaml "0.3.1"]
[http.async.client "0.5.2"]
[clj-bonecp-url "0.1.0"]
[org.slf4j/slf4j-nop "1.7.2"]
[org.clojure/clojure "1.5.1"]]
:min-lein-version "2.0.0"
:plugins [[environ/environ.lein "0.2.1"]]
:hooks [environ.leiningen.hooks]
:profiles {:production {:env {:production true}}})
I'm using lobos (https://github.com/budu/lobos) for the data migration. I followed the github page's advice and made a config.clj, which I edited with advice from this page.
(ns lobos.config
(:refer-clojure :exclude [replace reverse])
(:use [clojure.string :as str]
lobos.connectivity)
(:import (java.net URI)))
(defn heroku-db
"Generate the db map according to Heroku environment when available."
[]
(when (System/getenv "DATABASE_URL")
(let [url (URI. (System/getenv "DATABASE_URL"))
host (.getHost url)
port (if (pos? (.getPort url)) (.getPort url) 5432)
path (.getPath url)]
(merge
{:subname (str "//" host ":" port path)}
(when-let [user-info (.getUserInfo url)]
{:user (first (str/split user-info #":"))
:password (second (str/split user-info #":"))})))))
(def db
(merge {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//localhost:5432/blogdb"}
(heroku-db)))
(defn open-global-when-necessary
"Open a global connection only when necessary, that is, when no previous
connection exist or when db-spec is different to the current global
connection."
[db-spec]
;; If the connection credentials has changed, close the connection.
(when (and (#lobos.connectivity/global-connections :default-connection)
(not= (:db-spec (#lobos.connectivity/global-connections :default-connection)) db-spec))
(lobos.connectivity/close-global))
;; Open a new connection or return the existing one.
(if (nil? (#lobos.connectivity/global-connections :default-connection))
((lobos.connectivity/open-global db-spec) :default-connection)
(#lobos.connectivity/global-connections :default-connection)))
(open-global-when-necessary db)
Which gives me the error I noted above.
I managed to figure out how to enable SSL, but adding :ssl "true" to the db map in config.clj. However, now I have a new error:
SunCertPathBuilderException unable to find valid certification path to requested target.
When I try to push to heroku, I get the following error, whether SSL is on or off:
Exception in thread "main" org.postgresql.util.PSQLException: Connection refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections., compiling:(config.clj:44:1)
If you need any more specifics, let me know.
I had trouble making a simple query to a postgres addon on a heroku instance, too.
A local setup that has a similar setup would not work on the heroku setup.
What I did was to add a :sslmode "require" key-value pair to my db-spec map.
e.g. This would be my local db spec that works.
(def db-str {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//localhost:5432/testdb"
:user "postgres"
:password "password"})
This would be my heroku db spec.
(def db-str {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//remotehost:5432/testdb"
:user "postgres"
:password "password"
:sslmode "require"})
Notice the :sslmode key and its value.
Without the :sslmode key-value pair, "SunCertPathBuilderException unable to find valid certification path to requested target." will occur.
For the time being the best thing is probably to simply use the ##'parse-properties-url trick to work around the fact that the function you need inside clojure.java.jdbc is private.
However, this change should make it easier to turn on SSL in future versions:
https://github.com/clojure/java.jdbc/pull/35#issuecomment-32956962
In the documentation, the mongodb connection is established once, before being used without passing the connection to each command, is that the proper way to use monger, or should I pass the database connection to each call?
If you work with single database then it's best to set the connection once:
(mg/connect! db-spec)
But it's not a good idea when you have multiple databases. Monger have with-connection macro (see API docs) for this case:
(mg/with-connection db-connection
...)
You may establish all connections once during the initialization of your app:
(def conn1 (mg/connect db-spec))
and then use them:
(mg/with-connection conn1
...)
Update. In our application we have a hash-map of all database connections:
(def ^:dynamic
^clojure.lang.PersistentArrayMap
*connections*
{})
(defn connect! [db]
{:pre [(contains? mongo-config db)]}
(if (-> db *connections* nil?)
(let [conn (mg/connect (get mongo-config db))]
(alter-var-root #'*connections*
assoc
db
{ :conn conn
:db (mg/get-db conn (name db))})))
(-> *connections* db :conn))
(defmacro with-db [db & body]
"Eval body using :amonplus or :statistic db"
`(mg/with-connection (connect! ~db)
(mg/with-db (clojure.core/-> *connections* ~db :db)
~#body)))
mongo-config variable stores specification for all our databases and with-db macro makes it easy to access them by their names:
(with-db :my-db
...)
Now (version 2.0) is necessary for all key public API functions use an explicit DB/connection/GridFS object.
so:
(require '[monger.collection :as mc])
(mc/insert db "libraries" {:name "Monger"})
To get this work:
(let [conn (mg/connect)
db (mg/get-db conn "monger-test")]
(mc/insert db "libraries" {:name "Monger"}))
How I can use the "db" reference accross all my code.