2019-03-31 23:54:34 +00:00
|
|
|
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
|
|
|
|
|
|
|
|
|
(ns next.jdbc.connection
|
2019-04-01 06:17:12 +00:00
|
|
|
"Standard implementations of get-datasource and get-connection."
|
2019-03-31 23:54:34 +00:00
|
|
|
(:require [next.jdbc.protocols :as p])
|
|
|
|
|
(:import (java.sql Connection DriverManager)
|
|
|
|
|
(javax.sql DataSource)
|
|
|
|
|
(java.util Properties)))
|
|
|
|
|
|
|
|
|
|
(set! *warn-on-reflection* true)
|
|
|
|
|
|
|
|
|
|
(def ^:private classnames
|
|
|
|
|
"Map of subprotocols to classnames. dbtype specifies one of these keys.
|
|
|
|
|
|
|
|
|
|
The subprotocols map below provides aliases for dbtype.
|
|
|
|
|
|
|
|
|
|
Most databases have just a single class name for their driver but we
|
|
|
|
|
support a sequence of class names to try in order to allow for drivers
|
|
|
|
|
that change their names over time (e.g., MySQL)."
|
|
|
|
|
{"derby" "org.apache.derby.jdbc.EmbeddedDriver"
|
|
|
|
|
"h2" "org.h2.Driver"
|
|
|
|
|
"h2:mem" "org.h2.Driver"
|
|
|
|
|
"hsqldb" "org.hsqldb.jdbcDriver"
|
|
|
|
|
"jtds:sqlserver" "net.sourceforge.jtds.jdbc.Driver"
|
|
|
|
|
"mysql" ["com.mysql.cj.jdbc.Driver"
|
|
|
|
|
"com.mysql.jdbc.Driver"]
|
|
|
|
|
"oracle:oci" "oracle.jdbc.OracleDriver"
|
|
|
|
|
"oracle:thin" "oracle.jdbc.OracleDriver"
|
|
|
|
|
"postgresql" "org.postgresql.Driver"
|
|
|
|
|
"pgsql" "com.impossibl.postgres.jdbc.PGDriver"
|
|
|
|
|
"redshift" "com.amazon.redshift.jdbc.Driver"
|
|
|
|
|
"sqlite" "org.sqlite.JDBC"
|
|
|
|
|
"sqlserver" "com.microsoft.sqlserver.jdbc.SQLServerDriver"})
|
|
|
|
|
|
|
|
|
|
(def ^:private aliases
|
|
|
|
|
"Map of schemes to subprotocols. Used to provide aliases for dbtype."
|
|
|
|
|
{"hsql" "hsqldb"
|
|
|
|
|
"jtds" "jtds:sqlserver"
|
|
|
|
|
"mssql" "sqlserver"
|
|
|
|
|
"oracle" "oracle:thin"
|
|
|
|
|
"oracle:sid" "oracle:thin"
|
|
|
|
|
"postgres" "postgresql"})
|
|
|
|
|
|
|
|
|
|
(def ^:private host-prefixes
|
|
|
|
|
"Map of subprotocols to non-standard host-prefixes.
|
|
|
|
|
Anything not listed is assumed to use //."
|
|
|
|
|
{"oracle:oci" "@"
|
|
|
|
|
"oracle:thin" "@"})
|
|
|
|
|
|
|
|
|
|
(def ^:private ports
|
|
|
|
|
"Map of subprotocols to ports."
|
|
|
|
|
{"jtds:sqlserver" 1433
|
|
|
|
|
"mysql" 3306
|
|
|
|
|
"oracle:oci" 1521
|
|
|
|
|
"oracle:sid" 1521
|
|
|
|
|
"oracle:thin" 1521
|
|
|
|
|
"postgresql" 5432
|
|
|
|
|
"sqlserver" 1433})
|
|
|
|
|
|
|
|
|
|
(def ^:private dbname-separators
|
|
|
|
|
"Map of schemes to separators. The default is / but a couple are different."
|
|
|
|
|
{"mssql" ";DATABASENAME="
|
|
|
|
|
"sqlserver" ";DATABASENAME="
|
|
|
|
|
"oracle:sid" ":"})
|
|
|
|
|
|
|
|
|
|
(defn- ^Properties as-properties
|
|
|
|
|
"Convert any seq of pairs to a java.utils.Properties instance.
|
|
|
|
|
Uses as-sql-name to convert both keys and values into strings."
|
|
|
|
|
[m]
|
|
|
|
|
(let [p (Properties.)]
|
|
|
|
|
(doseq [[k v] m]
|
|
|
|
|
(.setProperty p (name k) (str v)))
|
|
|
|
|
p))
|
|
|
|
|
|
|
|
|
|
(defn- get-driver-connection
|
|
|
|
|
"Common logic for loading the DriverManager and the designed JDBC driver
|
|
|
|
|
class and obtaining the appropriate Connection object."
|
|
|
|
|
[url etc]
|
|
|
|
|
;; force DriverManager to be loaded
|
|
|
|
|
(DriverManager/getLoginTimeout)
|
|
|
|
|
(DriverManager/getConnection url (as-properties etc)))
|
|
|
|
|
|
|
|
|
|
(defn- spec->url+etc
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a database spec, return a JDBC URL and a map of any additional options."
|
2019-03-31 23:54:34 +00:00
|
|
|
[{:keys [dbtype dbname host port classname] :as db-spec}]
|
|
|
|
|
(let [;; allow aliases for dbtype
|
|
|
|
|
subprotocol (aliases dbtype dbtype)
|
|
|
|
|
host (or host "127.0.0.1")
|
|
|
|
|
port (or port (ports subprotocol))
|
|
|
|
|
db-sep (dbname-separators dbtype "/")
|
|
|
|
|
url (cond (= "h2:mem" dbtype)
|
|
|
|
|
(str "jdbc:" subprotocol ":" dbname ";DB_CLOSE_DELAY=-1")
|
|
|
|
|
(#{"derby" "h2" "hsqldb" "sqlite"} subprotocol)
|
|
|
|
|
(str "jdbc:" subprotocol ":" dbname)
|
|
|
|
|
:else
|
|
|
|
|
(str "jdbc:" subprotocol ":"
|
|
|
|
|
(host-prefixes subprotocol "//")
|
|
|
|
|
host
|
|
|
|
|
(when port (str ":" port))
|
|
|
|
|
db-sep dbname))
|
|
|
|
|
etc (dissoc db-spec :dbtype :dbname)]
|
|
|
|
|
;; verify the datasource is loadable
|
|
|
|
|
(if-let [class-name (or classname (classnames subprotocol))]
|
|
|
|
|
(do
|
|
|
|
|
(if (string? class-name)
|
|
|
|
|
(clojure.lang.RT/loadClassForName class-name)
|
|
|
|
|
(loop [[clazz & more] class-name]
|
|
|
|
|
(when-let [load-failure
|
|
|
|
|
(try
|
|
|
|
|
(clojure.lang.RT/loadClassForName clazz)
|
|
|
|
|
nil
|
|
|
|
|
(catch Exception e
|
|
|
|
|
e))]
|
|
|
|
|
(if (seq more)
|
|
|
|
|
(recur more)
|
|
|
|
|
(throw load-failure))))))
|
|
|
|
|
(throw (ex-info (str "Unknown dbtype: " dbtype) db-spec)))
|
|
|
|
|
[url etc]))
|
|
|
|
|
|
|
|
|
|
(defn- string->url+etc
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a JDBC URL, return it with an empty set of options with no parsing."
|
2019-03-31 23:54:34 +00:00
|
|
|
[s]
|
|
|
|
|
[s {}])
|
|
|
|
|
|
|
|
|
|
(defn- url+etc->datasource
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a JDBC URL and a map of options, return a DataSource that can be
|
|
|
|
|
used to obtain a new database connection."
|
2019-03-31 23:54:34 +00:00
|
|
|
[[url etc]]
|
|
|
|
|
(reify DataSource
|
|
|
|
|
(getConnection [_]
|
|
|
|
|
(get-driver-connection url etc))
|
|
|
|
|
(getConnection [_ username password]
|
|
|
|
|
(get-driver-connection url
|
|
|
|
|
(assoc etc
|
|
|
|
|
:username username
|
|
|
|
|
:password password)))))
|
|
|
|
|
|
|
|
|
|
(defn- make-connection
|
|
|
|
|
"Given a DataSource and a map of options, get a connection and update it
|
2019-04-01 06:17:12 +00:00
|
|
|
as specified by the options.
|
|
|
|
|
|
|
|
|
|
The options supported are:
|
|
|
|
|
* :auto-commit -- whether the connection should be set to auto-commit or not;
|
|
|
|
|
without this option, the defaut is true -- connections will auto-commit,
|
|
|
|
|
* :read-only -- whether the connection should be set to read-only mode."
|
2019-03-31 23:54:34 +00:00
|
|
|
^Connection
|
|
|
|
|
[^DataSource datasource opts]
|
|
|
|
|
(let [^Connection connection (.getConnection datasource)]
|
2019-04-01 06:17:12 +00:00
|
|
|
(when (contains? opts :auto-commit)
|
|
|
|
|
(.setAutoCommit connection (boolean (:auto-commit opts))))
|
|
|
|
|
(when (contains? opts :read-only)
|
|
|
|
|
(.setReadOnly connection (boolean (:read-only opts))))
|
2019-03-31 23:54:34 +00:00
|
|
|
connection))
|
|
|
|
|
|
|
|
|
|
(extend-protocol p/Sourceable
|
|
|
|
|
clojure.lang.Associative
|
|
|
|
|
(get-datasource [this]
|
|
|
|
|
(url+etc->datasource (spec->url+etc this)))
|
|
|
|
|
javax.sql.DataSource
|
|
|
|
|
(get-datasource [this] this)
|
|
|
|
|
String
|
|
|
|
|
(get-datasource [this]
|
|
|
|
|
(url+etc->datasource (string->url+etc this))))
|
|
|
|
|
|
|
|
|
|
(extend-protocol p/Connectable
|
|
|
|
|
javax.sql.DataSource
|
|
|
|
|
(get-connection [this opts] (make-connection this opts))
|
|
|
|
|
Object
|
|
|
|
|
(get-connection [this opts] (p/get-connection (p/get-datasource this) opts)))
|