Fix #47 by refactoring how db-specs are built

This commit is contained in:
Sean Corfield 2019-07-14 21:46:59 -07:00
parent e0e209aa1c
commit 816fc7ce4b
2 changed files with 56 additions and 76 deletions

View file

@ -6,6 +6,7 @@ Only accretive/fixative changes will be made from now on.
The following changes have been committed to the **master** branch since the 1.0.1 release: The following changes have been committed to the **master** branch since the 1.0.1 release:
* Fix #47 by refactoring database specs to be a single hash map instead of pouring multiple maps into one.
* Fix #46 by allowing `:host` to be `:none` which tells `next.jdbc` to omit the host/port section of the JDBC URL, so that local databases can be used with `:dbtype`/`:classname` for database types that `next.jdbc` does not know. Also added `:dbname-separator` and `:host-prefix` to the "db-spec" to allow fine-grained control over how the JDBC URL is assembled. * Fix #46 by allowing `:host` to be `:none` which tells `next.jdbc` to omit the host/port section of the JDBC URL, so that local databases can be used with `:dbtype`/`:classname` for database types that `next.jdbc` does not know. Also added `:dbname-separator` and `:host-prefix` to the "db-spec" to allow fine-grained control over how the JDBC URL is assembled.
* Fix #45 by adding [TimesTen](https://www.oracle.com/database/technologies/related/timesten.html) driver support. * Fix #45 by adding [TimesTen](https://www.oracle.com/database/technologies/related/timesten.html) driver support.
* Fix #44 so that `insert-multi!` with an empty `rows` vector returns `[]`. * Fix #44 so that `insert-multi!` with an empty `rows` vector returns `[]`.

View file

@ -9,65 +9,7 @@
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
(def ^:private classnames (def ^:private dbtypes
"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"
"timesten:client" "com.timesten.jdbc.TimesTenClientDriver"
"timesten:direct" "com.timesten.jdbc.TimesTenDriver"})
(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" ":"
"timesten:client" ":dsn="
"timesten:direct" ":dsn="})
(def dbtypes
"A map of all known database types (including aliases) to the class name(s) "A map of all known database types (including aliases) to the class name(s)
and port that `next.jdbc` supports out of the box. Just for completeness, and port that `next.jdbc` supports out of the box. Just for completeness,
this also includes the prefixes used in the JDBC string for the `:host` this also includes the prefixes used in the JDBC string for the `:host`
@ -113,17 +55,54 @@
named class in order, until one succeeds. This allows for a given `:dbtype` named class in order, until one succeeds. This allows for a given `:dbtype`
to be used with different versions of a JDBC driver, if the class name to be used with different versions of a JDBC driver, if the class name
has changed over time (such as with MySQL)." has changed over time (such as with MySQL)."
(let [dbs (merge (zipmap (keys classnames) (keys classnames)) aliases)] {"derby" {:classname "org.apache.derby.jdbc.EmbeddedDriver"}
(reduce-kv (fn [m k v] "h2" {:classname "org.h2.Driver"}
(assoc m "h2:mem" {:classname "org.h2.Driver"}
k "hsql" {:classname "org.hsqldb.jdbcDriver"
(cond-> {:classname (classnames v) :alias-for "hsqldb"}
:host-prefix (host-prefixes v "//") "hsqldb" {:classname "org.hsqldb.jdbcDriver"}
:dbname-separator (dbname-separators v "/")} "jtds" {:classname "net.sourceforge.jtds.jdbc.Driver"
(ports v) :port 1433
(assoc :port (ports v))))) :alias-for "jtds:sqlserver"}
{} "jtds:sqlserver" {:classname "net.sourceforge.jtds.jdbc.Driver"
dbs))) :port 1433}
"mssql" {:classname "com.microsoft.sqlserver.jdbc.SQLServerDriver"
:port 1433
:alias-for "sqlserver"
:dbname-separator ";DATABASENAME="}
"mysql" {:classname ["com.mysql.cj.jdbc.Driver"
"com.mysql.jdbc.Driver"]
:port 3306}
"oracle" {:classname "oracle.jdbc.OracleDriver"
:port 1521
:alias-for "oracle:thin"
:host-prefix "@"}
"oracle:oci" {:classname "oracle.jdbc.OracleDriver"
:port 1521
:host-prefix "@"}
"oracle:sid" {:classname "oracle.jdbc.OracleDriver"
:port 1521
:alias-for "oracle:thin"
:dbname-separator ":"
:host-prefix "@"}
"oracle:thin" {:classname "oracle.jdbc.OracleDriver"
:port 1521
:host-prefix "@"}
"postgres" {:classname "org.postgresql.Driver"
:port 5432
:alias-for "postgresql"}
"postgresql" {:classname "org.postgresql.Driver"
:port 5432}
"pgsql" {:classname "com.impossibl.postgres.jdbc.PGDriver"}
"redshift" {:classname "com.amazon.redshift.jdbc.Driver"}
"sqlite" {:classname "org.sqlite.JDBC"}
"sqlserver" {:classname "com.microsoft.sqlserver.jdbc.SQLServerDriver"
:port 1433
:dbname-separator ";DATABASENAME="}
"timesten:client" {:classname "com.timesten.jdbc.TimesTenClientDriver"
:dbname-separator ":dsn="}
"timesten:direct" {:classname "com.timesten.jdbc.TimesTenDriver"
:dbname-separator ":dsn="}})
(defn- ^Properties as-properties (defn- ^Properties as-properties
"Convert any seq of pairs to a `java.util.Properties` instance." "Convert any seq of pairs to a `java.util.Properties` instance."
@ -150,11 +129,11 @@
dbname-separator host-prefix] dbname-separator host-prefix]
:as db-spec}] :as db-spec}]
(let [;; allow aliases for dbtype (let [;; allow aliases for dbtype
subprotocol (aliases dbtype dbtype) subprotocol (-> dbtype dbtypes :alias-for (or dbtype))
host (or host "127.0.0.1") host (or host "127.0.0.1")
port (or port (ports subprotocol)) port (or port (-> dbtype dbtypes :port))
db-sep (or dbname-separator (dbname-separators dbtype "/")) db-sep (or dbname-separator (-> dbtype dbtypes :dbname-separator (or "/")))
local-sep (or dbname-separator (dbname-separators dbtype ":")) local-sep (or dbname-separator (-> dbtype dbtypes :dbname-separator (or ":")))
url (cond (#{"derby" "hsqldb" "sqlite"} subprotocol) url (cond (#{"derby" "hsqldb" "sqlite"} subprotocol)
(str "jdbc:" subprotocol local-sep dbname) (str "jdbc:" subprotocol local-sep dbname)
@ -177,7 +156,7 @@
:else :else
(str "jdbc:" subprotocol ":" (str "jdbc:" subprotocol ":"
(or host-prefix (host-prefixes subprotocol "//")) (or host-prefix (-> dbtype dbtypes :host-prefix (or "//")))
host host
(when port (str ":" port)) (when port (str ":" port))
db-sep dbname)) db-sep dbname))
@ -185,7 +164,7 @@
:dbtype :dbname :host :port :classname :dbtype :dbname :host :port :classname
:dbname-separator :host-prefix)] :dbname-separator :host-prefix)]
;; verify the datasource is loadable ;; verify the datasource is loadable
(if-let [class-name (or classname (classnames subprotocol))] (if-let [class-name (or classname (-> dbtype dbtypes :classname))]
(swap! driver-cache update class-name (swap! driver-cache update class-name
#(if % % #(if % %
(do (do