fix #204 by adding on-connection macro

This commit is contained in:
Sean Corfield 2022-04-04 16:54:18 -07:00
parent 0cb10285af
commit 03092d023f
4 changed files with 57 additions and 0 deletions

View file

@ -3,6 +3,7 @@
Only accretive/fixative changes will be made from now on.
* 1.2.next in progress
* Address [#204](https://github.com/seancorfield/next-jdbc/issues/204) by adding `next.jdbc/on-connection`.
* Address [#203](https://github.com/seancorfield/next-jdbc/issues/203) by adding a note to the **PostgreSQL Tips & Tricks** section.
* Update `build-clj` to v0.8.0.

View file

@ -577,6 +577,21 @@ JDBC provides several features that let you introspect the database to obtain li
Several methods on `DatabaseMetaData` return a `ResultSet` object, e.g., `.getCatalogs()`, `.getClientInfoProperties()`, `.getSchemas()`.
All of those can be handled in a similar manner to the above. See the [Oracle documentation for `java.sql.DatabaseMetaData`](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/DatabaseMetaData.html) (Java 11) for more details.
If you are working with a generalized datasource that may be a `Connection`, a `DataSource`,
or a wrapped connectable (via something like `with-options` or `with-transaction`), you can
write generic, `Connection`-based code using `on-connection` which will reuse a `Connection`
if one is passed or create a new one if needed (and automatically close it afterward):
```clojure
(on-connection [con ds]
(-> (.getMetaData con) ; produces java.sql.DatabaseMetaData
;; return a java.sql.ResultSet describing all tables and views:
(.getTables nil nil nil (into-array ["TABLE" "VIEW"]))
(rs/datafiable-result-set ds opts)))
```
> Note: to avoid confusion and/or incorrect usage, you cannot pass options to `on-connection` because they would be ignored in some cases (existing `Connection` or a wrapped `Connection`).
## Logging
Sometimes it is convenient to have database operations logged automatically. `next.jdbc/with-logging`

View file

@ -339,6 +339,35 @@
(with-open [con (get-connection connectable)]
(execute-batch! con sql param-groups opts)))))
(defmacro on-connection
"Given a connectable object, gets a connection and binds it to `sym`,
then executes the `body` in that context.
This allows you to write generic, `Connection`-based code without
needing to know the exact type of an incoming datasource:
(on-connection [conn datasource]
(let [metadata (.getMetadata conn)
catalog (.getCatalog conn)]
...))
If passed a `Connection` or a `Connectable` that wraps a `Connection`,
then that `Connection` is used as-is.
Otherwise, creates a new `Connection` object from the connectable,
executes the body, and automatically closes it for you."
[[sym connectable] & body]
(let [con-sym (vary-meta sym assoc :tag 'java.sql.Connection)
con-obj connectable]
`(cond (instance? java.sql.Connection ~con-obj)
((^{:once true} fn* [~con-sym] ~@body) ~con-obj)
(and (satisfies? p/Connectable ~con-obj)
(instance? java.sql.Connection (:connectable ~con-obj)))
((^{:once true} fn* [~con-sym] ~@body) (:connectable ~con-obj))
:else
(with-open [con# (get-connection ~con-obj)]
((^{:once true} fn* [~con-sym] ~@body) con#)))))
(defn transact
"Given a transactable object and a function (taking a `Connection`),
execute the function over the connection in a transactional manner.

View file

@ -883,3 +883,15 @@ INSERT INTO fruit (name, appearance) VALUES (?,?)
(default-options)))))
(is (thrown? IllegalArgumentException
(doall (take 3 (jdbc/plan (ds) ["select * from fruit"]))))))
(deftest issue-204
(testing "against a Connection"
(is (seq (with-open [con (jdbc/get-connection (ds))]
(jdbc/on-connection [x con] (jdbc/execute! x ["select * from fruit"]))))))
(testing "against a wrapped Connection"
(is (seq (with-open [con (jdbc/get-connection (ds))]
(jdbc/on-connection [x (jdbc/with-options con {})] (jdbc/execute! x ["select * from fruit"]))))))
(testing "against a wrapped Datasource"
(is (seq (jdbc/on-connection [x (jdbc/with-options (ds) {})] (jdbc/execute! x ["select * from fruit"])))))
(testing "against a Datasource"
(is (seq (jdbc/on-connection [x (ds)] (jdbc/execute! x ["select * from fruit"]))))))