Fixes #46 by allowing :host :none

Also adds `:dbname-separator` and `:host-prefix` for fine-grained 
control over the JDBC URL format.
This commit is contained in:
Sean Corfield 2019-07-11 15:43:49 -07:00
parent e361f875c1
commit 44dba4f29c
6 changed files with 86 additions and 16 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 #46 by allowing `:host` to be `:none` so `next.jdbc` will 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 adds `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 `[]`.
* Fix #43 by adjusting the spec for `insert-multi!` to "require less" of the `cols` and `rows` arguments. * Fix #43 by adjusting the spec for `insert-multi!` to "require less" of the `cols` and `rows` arguments.

View file

@ -10,7 +10,9 @@ Although `get-datasource` does not accept options, the "db spec" hash map passed
* `:dbtype` -- a string that identifies the type of JDBC database being used, * `:dbtype` -- a string that identifies the type of JDBC database being used,
* `:dbname` -- a string that identifies the name of the actual database being used, * `:dbname` -- a string that identifies the name of the actual database being used,
* `:dbname-separator` -- an optional string that can be used to override the `/` or `:` that is normally placed in front of the database name in 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"`, * `: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"`,
* `: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,
* `: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,

View file

@ -67,6 +67,14 @@
* `:classname` -- if you need to override the default for the `:dbtype` * `:classname` -- if you need to override the default for the `:dbtype`
(or you want to use a database that next.jdbc does not know about!) (or you want to use a database that next.jdbc does not know about!)
The following optional keys can be used to control how JDBC URLs are
assembled. This may be needed for `:dbtype` values that `next.jdbc`
does not recognize:
* `:dbname-separator` -- override the `/` or `:` that normally precedes
the database name in the JDBC URL
* `:host-prefix` -- override the `//` that normally precedes the IP
address or hostname in the JDBC URL
Any additional options provided will be passed to the JDBC driver's Any additional options provided will be passed to the JDBC driver's
`.getConnection` call as a `java.util.Properties` structure. `.getConnection` call as a `java.util.Properties` structure.

View file

@ -71,7 +71,8 @@
"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`
and `:dbname` (which are `//` and `/` respectively for nearly all types). and `:dbname` (which are `//` and either `/` or `:` respectively for
nearly all types).
For known database types, you can use `:dbtype` (and omit `:classname`). For known database types, you can use `:dbtype` (and omit `:classname`).
@ -81,6 +82,24 @@
`{:dbtype \"acme\" :classname \"com.acme.JdbcDriver\" ...}` `{:dbtype \"acme\" :classname \"com.acme.JdbcDriver\" ...}`
The value of `:dbtype` should be the string that the driver is associated
with in the JDBC URL, i.e., the value that comes between the `jdbc:`
prefix and the `://<host>...` part. In the above example, the JDBC URL
that would be generated would be `jdbc:acme://<host>:<port>/<dbname>`.
If you want `next.jdbc` to omit the host/port part of the URL, specify
`:host :none`, which would produce a URL like: `jdbc:acme:<dbname>`,
which allows you to work with local databases (or drivers that do not
need host/port information).
The default prefix for the host name (or IP address) is `//`. You
can override this via the `:host-prefix` option.
The default separator between the host/port and the database name is `/`.
The default separator between the subprotocol and the database name,
for local databases with no host/port, is `:`. You can override this
via the `:dbname-separator` option.
JDBC drivers are not provided by `next.jdbc` -- you need to specify the JDBC drivers are not provided by `next.jdbc` -- you need to specify the
driver(s) you need as additional dependencies in your project. For driver(s) you need as additional dependencies in your project. For
example: example:
@ -127,32 +146,44 @@
(defn- spec->url+etc (defn- spec->url+etc
"Given a database spec, return a JDBC URL and a map of any additional options." "Given a database spec, return a JDBC URL and a map of any additional options."
[{:keys [dbtype dbname host port classname] :as db-spec}] [{:keys [dbtype dbname host port classname
dbname-separator host-prefix]
:as db-spec}]
(let [;; allow aliases for dbtype (let [;; allow aliases for dbtype
subprotocol (aliases dbtype dbtype) subprotocol (aliases dbtype dbtype)
host (or host "127.0.0.1") host (or host "127.0.0.1")
port (or port (ports subprotocol)) port (or port (ports subprotocol))
db-sep (dbname-separators dbtype "/") db-sep (or dbname-separator (dbname-separators dbtype "/"))
url (cond (= "h2:mem" dbtype) local-sep (or dbname-separator (dbname-separators dbtype ":"))
(str "jdbc:" subprotocol ":" dbname ";DB_CLOSE_DELAY=-1") url (cond (#{"derby" "hsqldb" "sqlite"} subprotocol)
(str "jdbc:" subprotocol local-sep dbname)
(#{"h2"} subprotocol) (#{"h2"} subprotocol)
(str "jdbc:" subprotocol ":" (str "jdbc:" subprotocol local-sep
(if (re-find #"^([A-Za-z]:)?[\./\\]" dbname) (if (re-find #"^([A-Za-z]:)?[\./\\]" dbname)
;; DB name starts with relative or absolute path ;; DB name starts with relative or absolute path
dbname dbname
;; otherwise make it local ;; otherwise make it local
(str "./" dbname))) (str "./" dbname)))
(#{"derby" "hsqldb" "sqlite"} subprotocol)
(str "jdbc:" subprotocol ":" dbname) (#{"h2:mem"} subprotocol)
(#{"timesten:direct"} subprotocol) (str "jdbc:" subprotocol local-sep dbname ";DB_CLOSE_DELAY=-1")
(str "jdbc:" subprotocol db-sep dbname)
(#{"timesten:client" "timesten:direct"} subprotocol)
(str "jdbc:" subprotocol local-sep dbname)
(= :none host)
(str "jdbc:" subprotocol local-sep dbname)
:else :else
(str "jdbc:" subprotocol ":" (str "jdbc:" subprotocol ":"
(host-prefixes subprotocol "//") (or host-prefix (host-prefixes subprotocol "//"))
host host
(when port (str ":" port)) (when port (str ":" port))
db-sep dbname)) db-sep dbname))
etc (dissoc db-spec :dbtype :dbname :host :port :classname)] etc (dissoc db-spec
:dbtype :dbname :host :port :classname
: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 (classnames subprotocol))]
(swap! driver-cache update class-name (swap! driver-cache update class-name

View file

@ -26,15 +26,20 @@
(s/def ::dbtype string?) (s/def ::dbtype string?)
(s/def ::dbname string?) (s/def ::dbname string?)
(s/def ::dbname-separator string?)
(s/def ::classname string?) (s/def ::classname string?)
(s/def ::user string?) (s/def ::user string?)
(s/def ::password string?) (s/def ::password string?)
(s/def ::host string?) (s/def ::host (s/or :name string?
:none #{:none}))
(s/def ::host-prefix string?)
(s/def ::port pos-int?) (s/def ::port pos-int?)
(s/def ::db-spec-map (s/keys :req-un [::dbtype ::dbname] (s/def ::db-spec-map (s/keys :req-un [::dbtype ::dbname]
:opt-un [::classname :opt-un [::classname
::user ::password ::user ::password
::host ::port])) ::host ::port
::dbname-separator
::host-prefix]))
(s/def ::connection #(instance? Connection %)) (s/def ::connection #(instance? Connection %))
(s/def ::datasource #(instance? DataSource %)) (s/def ::datasource #(instance? DataSource %))

View file

@ -51,6 +51,29 @@
(is (= (#'c/spec->url+etc {:dbtype "sqlserver" :dbname db-name}) (is (= (#'c/spec->url+etc {:dbtype "sqlserver" :dbname db-name})
(#'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
(is (= ["jdbc:acme:my-db" {}]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host :none})))
(is (= ["jdbc:acme://127.0.0.1/my-db" {}]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db"})))
(is (= ["jdbc:acme://12.34.56.70:1234/my-db" {}]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host "12.34.56.70" :port 1234})))
(is (= ["jdbc:acme:dsn=my-db" {}]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host :none
:dbname-separator ":dsn="})))
(is (= ["jdbc:acme:(*)127.0.0.1/my-db" {}]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db"
:host-prefix "(*)"})))
(is (= ["jdbc:acme:(*)12.34.56.70:1234/my-db" {}]
(#'c/spec->url+etc {:dbtype "acme" :classname "java.lang.String"
:dbname "my-db" :host "12.34.56.70" :port 1234
:host-prefix "(*)"}))))
;; 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"])