Fixes #167 by adding property separator

This commit is contained in:
Sean Corfield 2021-06-09 19:14:41 -07:00
parent a568f0fa87
commit 164572969b
5 changed files with 75 additions and 16 deletions

View file

@ -4,6 +4,7 @@ Only accretive/fixative changes will be made from now on.
* 1.2.next in progress * 1.2.next in progress
* _[Experimental! Will change in response to feedback!]_ Add `next.jdbc/with-logging` to create a wrapped connectable that will invoke logging functions with the SQL/parameters and optionally the result for each operation. * _[Experimental! Will change in response to feedback!]_ Add `next.jdbc/with-logging` to create a wrapped connectable that will invoke logging functions with the SQL/parameters and optionally the result for each operation.
* Fix #167 by adding `:property-separator` to `next.jdbc.connection/dbtypes` and using it in `jdbc-url`.
* Fix `:unit_count` references in **Getting Started** (were `:unit_cost`). * Fix `:unit_count` references in **Getting Started** (were `:unit_cost`).
* Update `test-runner`. * Update `test-runner`.

View file

@ -14,6 +14,7 @@ Although `get-datasource` does not accept options, the "db spec" hash map passed
* `:host` -- an optional string that identifies the IP address or hostname of the server on which the database is running; the default is `"127.0.0.1"`; if `:none` is specified, `next.jdbc` will assume this is for a local database and will omit the host/port segment of the JDBC URL, * `:host` -- an optional string that identifies the IP address or hostname of the server on which the database is running; the default is `"127.0.0.1"`; if `:none` is specified, `next.jdbc` will assume this is for a local database and will omit the host/port segment of the JDBC URL,
* `:host-prefix` -- an optional string that can be used to override the `//` that is normally placed in front of the IP address or hostname in the JDBC URL, * `:host-prefix` -- an optional string that can be used to override the `//` that is normally placed in front of the IP address or hostname in the JDBC URL,
* `:port` -- an optional integer that identifies the port on which the database is running; for common database types, `next.jdbc` knows the default so this should only be needed for non-standard setups or "exotic" database types, * `:port` -- an optional integer that identifies the port on which the database is running; for common database types, `next.jdbc` knows the default so this should only be needed for non-standard setups or "exotic" database types,
* `:property-separator` -- an optional string that can be used to override the separators used in `jdbc-url` for the properties (after the initial JDBC URL portion); by default `?` and `&` are used to build JDBC URLs with properties; for SQL Server drivers (both MS and jTDS) `:property-separator ";"` is used, so this option should only be necessary when you are specifying "unusual" databases that `next.jdbc` does not already know about,
* `:classname` -- an optional string that identifies the name of the JDBC driver class to be used for the connection; for common database types, `next.jdbc` knows the default so this should only be needed for "exotic" database types, * `:classname` -- an optional string that identifies the name of the JDBC driver class to be used for the connection; for common database types, `next.jdbc` knows the default so this should only be needed for "exotic" database types,
* `:user` -- an optional string that identifies the database username to be used when authenticating, * `:user` -- an optional string that identifies the database username to be used when authenticating,
* `:password` -- an optional string that identifies the database password to be used when authenticating. * `:password` -- an optional string that identifies the database password to be used when authenticating.

View file

@ -103,6 +103,11 @@
the database name in the JDBC URL the database name in the JDBC URL
* `:host-prefix` -- override the `//` that normally precedes the IP * `:host-prefix` -- override the `//` that normally precedes the IP
address or hostname in the JDBC URL address or hostname in the JDBC URL
* `:property-separator` -- an optional string that can be used to override
the separators used in `jdbc-url` for the properties (after the initial
JDBC URL portion); by default `?` and `&` are used to build JDBC URLs
with properties; for SQL Server drivers (both MS and jTDS)
`:property-separator \";\"` is used
In the second format, this key is required: In the second format, this key is required:
* `:jdbcUrl` -- a JDBC URL string * `:jdbcUrl` -- a JDBC URL string

View file

@ -22,7 +22,8 @@
string, this table includes `:dbname-separator` and/or `:host-prefix`. The string, this table includes `:dbname-separator` and/or `:host-prefix`. The
default prefix for `:dbname` is either `/` or `:` and for `:host` it is `//`. default prefix for `:dbname` is either `/` or `:` and for `:host` it is `//`.
For local databases, with no `:host`/`:port` segment in their JDBC URL, a For local databases, with no `:host`/`:port` segment in their JDBC URL, a
value of `:none` is provided for `:host` in this table. value of `:none` is provided for `:host` in this table. In addition,
`:property-separator` can specify how you build the JDBC URL.
For known database types, you can use `:dbtype` (and omit `:classname`). For known database types, you can use `:dbtype` (and omit `:classname`).
@ -79,14 +80,17 @@
:host :none} :host :none}
"jtds" {:classname "net.sourceforge.jtds.jdbc.Driver" "jtds" {:classname "net.sourceforge.jtds.jdbc.Driver"
:alias-for "jtds:sqlserver" :alias-for "jtds:sqlserver"
:property-separator ";"
:port 1433} :port 1433}
"jtds:sqlserver" {:classname "net.sourceforge.jtds.jdbc.Driver" "jtds:sqlserver" {:classname "net.sourceforge.jtds.jdbc.Driver"
:property-separator ";"
:port 1433} :port 1433}
"mariadb" {:classname "org.mariadb.jdbc.Driver" "mariadb" {:classname "org.mariadb.jdbc.Driver"
:port 3306} :port 3306}
"mssql" {:classname "com.microsoft.sqlserver.jdbc.SQLServerDriver" "mssql" {:classname "com.microsoft.sqlserver.jdbc.SQLServerDriver"
:alias-for "sqlserver" :alias-for "sqlserver"
:dbname-separator ";DATABASENAME=" :dbname-separator ";DATABASENAME="
:property-separator ";"
:port 1433} :port 1433}
"mysql" {:classname ["com.mysql.cj.jdbc.Driver" "mysql" {:classname ["com.mysql.cj.jdbc.Driver"
"com.mysql.jdbc.Driver"] "com.mysql.jdbc.Driver"]
@ -117,6 +121,7 @@
:host :none} :host :none}
"sqlserver" {:classname "com.microsoft.sqlserver.jdbc.SQLServerDriver" "sqlserver" {:classname "com.microsoft.sqlserver.jdbc.SQLServerDriver"
:dbname-separator ";DATABASENAME=" :dbname-separator ";DATABASENAME="
:property-separator ";"
:port 1433} :port 1433}
"timesten:client" {:classname "com.timesten.jdbc.TimesTenClientDriver" "timesten:client" {:classname "com.timesten.jdbc.TimesTenClientDriver"
:dbname-separator ":dsn=" :dbname-separator ":dsn="
@ -151,7 +156,7 @@
As a special case, the database spec can contain jdbcUrl (just like ->pool), As a special case, the database spec can contain jdbcUrl (just like ->pool),
in which case it will return that URL as-is and a map of any other options." in which case it will return that URL as-is and a map of any other options."
[{:keys [dbtype dbname host port classname [{:keys [dbtype dbname host port classname
dbname-separator host-prefix dbname-separator host-prefix property-separator
jdbcUrl] jdbcUrl]
:as db-spec}] :as db-spec}]
(let [etc (dissoc db-spec (let [etc (dissoc db-spec
@ -208,7 +213,8 @@
(throw (ex-info (str "Unknown dbtype: " dbtype (throw (ex-info (str "Unknown dbtype: " dbtype
", and :classname not provided.") ", and :classname not provided.")
db-spec))) db-spec)))
[url etc])))) [url etc (or property-separator
(-> dbtype dbtypes :property-separator))]))))
(defn jdbc-url (defn jdbc-url
"Given a database spec (as a hash map), return a JDBC URL with all the "Given a database spec (as a hash map), return a JDBC URL with all the
@ -236,13 +242,15 @@
sure they are properly URL-encoded as values in the database spec hash map. sure they are properly URL-encoded as values in the database spec hash map.
This function does **not** attempt to URL-encode values for you!" This function does **not** attempt to URL-encode values for you!"
[db-spec] [db-spec]
(let [[url etc] (spec->url+etc db-spec) (let [[url etc ps] (spec->url+etc db-spec)
url-and (if (str/index-of url "?") "&" "?")] url-and (or ps (if (str/index-of url "?") "&" "?"))]
(str url url-and (str/join "&" (if (seq etc)
(reduce-kv (fn [pairs k v] (str url url-and (str/join (or ps "&")
(conj pairs (str (name k) "=" v))) (reduce-kv (fn [pairs k v]
[] (conj pairs (str (name k) "=" v)))
etc))))) []
etc)))
url)))
(defn ->pool (defn ->pool
"Given a (connection pooled datasource) class and a database spec, return a "Given a (connection pooled datasource) class and a database spec, return a

View file

@ -54,28 +54,72 @@
(#'c/spec->url+etc {:dbtype "sqlserver" :dbname db-name :port 1433}))))) (#'c/spec->url+etc {:dbtype "sqlserver" :dbname db-name :port 1433})))))
(deftest custom-dbtypes (deftest custom-dbtypes
(is (= ["jdbc:acme:my-db" {}] (is (= ["jdbc:acme:my-db" {} nil]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String" (#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host :none}))) :dbname "my-db" :host :none})))
(is (= ["jdbc:acme://127.0.0.1/my-db" {}] (is (= ["jdbc:acme://127.0.0.1/my-db" {} nil]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String" (#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db"}))) :dbname "my-db"})))
(is (= ["jdbc:acme://12.34.56.70:1234/my-db" {}] (is (= ["jdbc:acme://12.34.56.70:1234/my-db" {} nil]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String" (#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host "12.34.56.70" :port 1234}))) :dbname "my-db" :host "12.34.56.70" :port 1234})))
(is (= ["jdbc:acme:dsn=my-db" {}] (is (= ["jdbc:acme:dsn=my-db" {} nil]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String" (#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host :none :dbname "my-db" :host :none
:dbname-separator ":dsn="}))) :dbname-separator ":dsn="})))
(is (= ["jdbc:acme:(*)127.0.0.1/my-db" {}] (is (= ["jdbc:acme:(*)127.0.0.1/my-db" {} nil]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String" (#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :dbname "my-db"
:host-prefix "(*)"}))) :host-prefix "(*)"})))
(is (= ["jdbc:acme:(*)12.34.56.70:1234/my-db" {}] (is (= ["jdbc:acme:(*)12.34.56.70:1234/my-db" {} nil]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String" (#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host "12.34.56.70" :port 1234 :dbname "my-db" :host "12.34.56.70" :port 1234
:host-prefix "(*)"})))) :host-prefix "(*)"}))))
(deftest jdbc-url-tests
(testing "basic URLs work"
(is (= "jdbc:acme:my-db"
(c/jdbc-url {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host :none})))
(is (= "jdbc:acme://127.0.0.1/my-db"
(c/jdbc-url {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db"})))
(is (= "jdbc:acme://12.34.56.70:1234/my-db"
(c/jdbc-url {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host "12.34.56.70" :port 1234})))
(is (= "jdbc:acme:dsn=my-db"
(c/jdbc-url {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host :none
:dbname-separator ":dsn="})))
(is (= "jdbc:acme:(*)127.0.0.1/my-db"
(c/jdbc-url {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db"
:host-prefix "(*)"})))
(is (= "jdbc:acme:(*)12.34.56.70:1234/my-db"
(c/jdbc-url {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host "12.34.56.70" :port 1234
:host-prefix "(*)"}))))
(testing "URLs with properties work"
(is (= "jdbc:acme:my-db?useSSL=true"
(c/jdbc-url {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host :none
:useSSL true})))
(is (boolean (#{"jdbc:acme:my-db?useSSL=true&user=dba"
"jdbc:acme:my-db?user=dba&useSSL=true"}
(c/jdbc-url {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host :none
:useSSL true :user "dba"}))))
(is (= "jdbc:jtds:sqlserver:my-db;useSSL=true"
(c/jdbc-url {:dbtype "jtds"
:dbname "my-db" :host :none
:useSSL true})))
(is (boolean (#{"jdbc:jtds:sqlserver:my-db;useSSL=true;user=dba"
"jdbc:jtds:sqlserver:my-db;user=dba;useSSL=true"}
(c/jdbc-url {:dbtype "jtds"
:dbname "my-db" :host :none
:useSSL true :user "dba"}))))))
;; these are the 'local' databases that we can always test against ;; these are the 'local' databases that we can always test against
(def test-db-type ["derby" "h2" "h2:mem" "hsqldb" "sqlite"]) (def test-db-type ["derby" "h2" "h2:mem" "hsqldb" "sqlite"])