next-jdbc/src/next/jdbc/connection.clj

164 lines
5.6 KiB
Clojure
Raw Normal View History

2019-03-31 23:54:34 +00:00
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
(ns next.jdbc.connection
""
(: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
""
[{: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
""
[s]
[s {}])
(defn- url+etc->datasource
""
[[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
as specified by the options."
^Connection
[^DataSource datasource opts]
(let [^Connection connection (.getConnection datasource)]
(when (contains? opts :auto-commit?)
(.setAutoCommit connection (boolean (:auto-commit? opts))))
(when (contains? opts :read-only?)
(.setReadOnly connection (boolean (:read-only? opts))))
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)))