Fixes #66 by adding support for :jdbcUrl in db-spec hash map

This commit is contained in:
Sean Corfield 2019-09-27 13:06:45 -07:00
parent 0f0f6fbe92
commit 6c42d7ce67
5 changed files with 84 additions and 55 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.7 release:
* Fix #66 by adding support for a db-spec hash map format containing a `:jdbcUrl` key (consistent with `->pool`) so that you can create a datasource from a JDBC URL string and additional options.
* Address #65 by adding a HugSQL "quick start" to the Friendly SQL Functions section of the docs.
* Add `next.jdbc.specs/unstrument`. PR #64 (@gerred).
* Address #63 by improving documentation around qualified column names and `:qualifier` (`clojure.java.jdbc`) migration.

View file

@ -68,6 +68,8 @@ We described the database with just `:dbtype` and `:dbname` because it is create
> Note: You can see the full list of `:dbtype` values supported in [next.jdbc/get-datasource](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/api/next.jdbc#get-datasource)'s docstring. If you need this programmatically, you can get it from the [next.jdbc.connection/dbtypes](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/api/next.jdbc.connection#dbtypes) hash map. If those lists differ, the hash map is the definitive list (and I'll need to fix the docstring!). The docstring of that Var explains how to tell `next.jdbc` about additional databases.
If you already have a JDBC URL (string), you can use that as-is instead of the db-spec hash map. If you have a JDBC URL and still need additional options passed into the JDBC driver, you can use a hash map with the `:jdbcUrl` key specifying the string and whatever additional options you need.
### `execute!` & `execute-one!`
We used `execute!` to create the `address` table, to insert a new row into it, and to query it. In all three cases, `execute!` returns a vector of hash maps with namespace-qualified keys, representing the result set from the operation, if available. When no result set is produced, `next.jdbc` returns a "result set" containing the "update count" from the operation (which is usually the number of rows affected). By default, H2 uses uppercase names and `next.jdbc` returns these as-is.
@ -172,7 +174,7 @@ Then import the appropriate classes into your code:
(com.mchange.v2.c3p0 ComboPooledDataSource PooledDataSource)))
```
Finally, create the connection pooled datasource. `db-spec` here contains the regular `next.jdbc` options (`:dbtype`, `:dbname`, and maybe `:host`, `:port`, `:classname` etc). Those are used to construct the JDBC URL that is passed into the datasource object (by calling `.setJdbcUrl` on it). You can also specify any of the connection pooling library's options, as mixed case keywords corresponding to any simple setter methods on the class being passed in, e.g., `:connectionTestQuery`, `:maximumPoolSize` (HikariCP), `:maxPoolSize`, `:preferredTestQuery` (c3p0).
Finally, create the connection pooled datasource. `db-spec` here contains the regular `next.jdbc` options (`:dbtype`, `:dbname`, and maybe `:host`, `:port`, `:classname` etc -- or the `:jdbcUrl` format mentioned above). Those are used to construct the JDBC URL that is passed into the datasource object (by calling `.setJdbcUrl` on it). You can also specify any of the connection pooling library's options, as mixed case keywords corresponding to any simple setter methods on the class being passed in, e.g., `:connectionTestQuery`, `:maximumPoolSize` (HikariCP), `:maxPoolSize`, `:preferredTestQuery` (c3p0).
Some important notes regarding HikariCP:

View file

@ -58,7 +58,11 @@
"Given some sort of specification of a database, return a `DataSource`.
A specification can be a JDBC URL string (which is passed to the JDBC
driver as-is), or a hash map. For the hash map, these keys are required:
driver as-is), or a hash map.
For the hash map, there are two formats accepted:
In the first format, these keys are required:
* `:dbtype` -- a string indicating the type of the database
* `:dbname` -- a string indicating the name of the database to be used
@ -81,10 +85,13 @@
* `:host-prefix` -- override the `//` that normally precedes the IP
address or hostname in the JDBC URL
In the second format, this key is required:
* `:jdbcUrl` -- a JDBC URL string
Any additional options provided will be passed to the JDBC driver's
`.getConnection` call as a `java.util.Properties` structure.
Database types supported, and their defaults:
Database types supported (for `:dbtype`), and their defaults:
* `derby` -- `org.apache.derby.jdbc.EmbeddedDriver` -- also pass `:create true`
if you want the database to be automatically created
* `h2` -- `org.h2.Driver` -- for an on-disk database

View file

@ -136,61 +136,68 @@
(atom {}))
(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.
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."
[{:keys [dbtype dbname host port classname
dbname-separator host-prefix]
dbname-separator host-prefix
jdbcUrl]
:as db-spec}]
(let [;; allow aliases for dbtype
subprotocol (-> dbtype dbtypes :alias-for (or dbtype))
host (or host (-> dbtype dbtypes :host) "127.0.0.1")
port (or port (-> dbtype dbtypes :port))
db-sep (or dbname-separator (-> dbtype dbtypes :dbname-separator (or "/")))
local-sep (or dbname-separator (-> dbtype dbtypes :dbname-separator (or ":")))
url (cond (#{"h2"} subprotocol)
(str "jdbc:" subprotocol local-sep
(if (re-find #"^([A-Za-z]:)?[\./\\]" dbname)
;; DB name starts with relative or absolute path
dbname
;; otherwise make it local
(str "./" dbname)))
(#{"h2:mem"} subprotocol)
(str "jdbc:" subprotocol local-sep dbname ";DB_CLOSE_DELAY=-1")
(= :none host)
(str "jdbc:" subprotocol local-sep dbname)
:else
(str "jdbc:" subprotocol ":"
(or host-prefix (-> dbtype dbtypes :host-prefix (or "//")))
host
(when port (str ":" port))
db-sep dbname))
etc (dissoc db-spec
(let [etc (dissoc db-spec
:dbtype :dbname :host :port :classname
:dbname-separator :host-prefix)]
;; verify the datasource is loadable
(if-let [class-name (or classname (-> dbtype dbtypes :classname))]
(swap! driver-cache update class-name
#(if % %
(do
;; force DriverManager to be loaded
(DriverManager/getLoginTimeout)
(if (string? class-name)
(clojure.lang.RT/loadClassForName class-name)
(loop [[clazz & more] class-name]
(let [loaded
(try
(clojure.lang.RT/loadClassForName clazz)
(catch Exception e
e))]
(if (instance? Throwable loaded)
(if (seq more)
(recur more)
(throw loaded))
loaded)))))))
(throw (ex-info (str "Unknown dbtype: " dbtype) db-spec)))
[url etc]))
:dbname-separator :host-prefix
:jdbcUrl)]
(if jdbcUrl
[jdbcUrl etc]
(let [;; allow aliases for dbtype
subprotocol (-> dbtype dbtypes :alias-for (or dbtype))
host (or host (-> dbtype dbtypes :host) "127.0.0.1")
port (or port (-> dbtype dbtypes :port))
db-sep (or dbname-separator (-> dbtype dbtypes :dbname-separator (or "/")))
local-sep (or dbname-separator (-> dbtype dbtypes :dbname-separator (or ":")))
url (cond (#{"h2"} subprotocol)
(str "jdbc:" subprotocol local-sep
(if (re-find #"^([A-Za-z]:)?[\./\\]" dbname)
;; DB name starts with relative or absolute path
dbname
;; otherwise make it local
(str "./" dbname)))
(#{"h2:mem"} subprotocol)
(str "jdbc:" subprotocol local-sep dbname ";DB_CLOSE_DELAY=-1")
(= :none host)
(str "jdbc:" subprotocol local-sep dbname)
:else
(str "jdbc:" subprotocol ":"
(or host-prefix (-> dbtype dbtypes :host-prefix (or "//")))
host
(when port (str ":" port))
db-sep dbname))]
;; verify the datasource is loadable
(if-let [class-name (or classname (-> dbtype dbtypes :classname))]
(swap! driver-cache update class-name
#(if % %
(do
;; force DriverManager to be loaded
(DriverManager/getLoginTimeout)
(if (string? class-name)
(clojure.lang.RT/loadClassForName class-name)
(loop [[clazz & more] class-name]
(let [loaded
(try
(clojure.lang.RT/loadClassForName clazz)
(catch Exception e
e))]
(if (instance? Throwable loaded)
(if (seq more)
(recur more)
(throw loaded))
loaded)))))))
(throw (ex-info (str "Unknown dbtype: " dbtype) db-spec)))
[url etc]))))
(defn ->pool
"Given a (connection pooled datasource) class and a database spec, return a

View file

@ -103,6 +103,18 @@
(is (str/index-of (pr-str ds) url))
(with-open [con (p/get-connection ds {})]
(is (instance? java.sql.Connection con)))))
(testing "datasource via jdbcUrl"
(let [[url etc] (#'c/spec->url+etc db)
ds (p/get-datasource (assoc etc :jdbcUrl url))]
(if (= "derby" (:dbtype db))
(is {:create true} etc)
(is {} etc))
(is (instance? javax.sql.DataSource ds))
(is (str/index-of (pr-str ds) (str "jdbc:" (:dbtype db))))
;; checks get-datasource on a DataSource is identity
(is (identical? ds (p/get-datasource ds)))
(with-open [con (p/get-connection ds {})]
(is (instance? java.sql.Connection con)))))
(testing "datasource via HikariCP"
;; the type hint is only needed because we want to call .close
(with-open [^HikariDataSource ds (c/->pool HikariDataSource db)]