Fixes #75 by adding java.sql.Statement support
This commit is contained in:
parent
b38afcb501
commit
d561a7108e
6 changed files with 97 additions and 6 deletions
|
|
@ -6,8 +6,9 @@ Only accretive/fixative changes will be made from now on.
|
|||
|
||||
The following changes have been committed to the **master** branch since the 1.0.9 release:
|
||||
|
||||
* Fix link to **All The Options** in **Migration from `clojure.java.jdbc`**. PR #71 (@laurio).
|
||||
* Fix #75 by adding support for `java.sql.Statement` to `plan`, `execute!`, and `execute-one!`.
|
||||
* Address #74 by making several small changes to satisfy Eastwood.
|
||||
* Fix link to **All The Options** in **Migration from `clojure.java.jdbc`**. PR #71 (@laurio).
|
||||
* Address #70 by adding **CLOB & BLOB SQL Types** to the **Tips & Tricks** section of **Friendly SQL Functions** and by adding `next.jdbc.result-set/clob-column-reader` and `next.jdbc.result-set/clob->string` helper to make it easier to deal with `CLOB` column data.
|
||||
* Clarify what `execute!` and `execute-one!` produce when the result set is empty (`[]` and `nil` respectively, and there are now tests for this). Similarly for `find-by-keys` and `get-by-id`.
|
||||
* Add **MS SQL Server** section to **Tips & Tricks** to note that returns an empty string for table names by default (so table-qualified column names are not available). Using the `:result-type` (scroll) and `:concurrency` options will cause table names to be returned.
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ Any operation that can perform key-based lookup can be used here without creatin
|
|||
|
||||
## Datasources, Connections & Transactions
|
||||
|
||||
In the examples above, we created a datasource and then passed it into each function call. When `next.jdbc` is given a datasource, it creates a `java.sql.Connection` from it, uses it for the SQL operation, and then closes it. If you're not using a connection pooling datasource (see below), that can be quite an overhead: setting up database connections to remote servers is not cheap!
|
||||
In the examples above, we created a datasource and then passed it into each function call. When `next.jdbc` is given a datasource, it creates a `java.sql.Connection` from it, uses it for the SQL operation (by creating and populating a `java.sql.PreparedStatement` from the connection and the SQL string and parameters passed in), and then closes it. If you're not using a connection pooling datasource (see below), that can be quite an overhead: setting up database connections to remote servers is not cheap!
|
||||
|
||||
If you want to run multiple SQL operations without that overhead each time, you can create the connection yourself and reuse it across several operations using `with-open` and `next.jdbc/get-connection`:
|
||||
|
||||
|
|
@ -183,6 +183,17 @@ If `with-transaction` is given a datasource, it will create and close the connec
|
|||
|
||||
You can read more about [working with transactions](/doc/transactions.md) further on in the documentation.
|
||||
|
||||
### Prepared Statement Caveat
|
||||
|
||||
*Note: Not all databases support using a `PreparedStatement` for every type of SQL operation. You might have to create a `java.sql.Statement` yourself, directly from a `java.sql.Connection` and use that, without parameters, in `plan`, `execute!`, or `execute-one!`. See the following example:*
|
||||
|
||||
```clojure
|
||||
(with-open [con (jdbc/get-connection ds)]
|
||||
(jdbc/execute! (.createStatement con) ["...just a SQL string..."])
|
||||
(jdbc/execute! con ["...some SQL..." "and" "parameters"]) ; uses PreparedStatement
|
||||
(into [] (map :column) (jdbc/plan (.createStatement con) ["..."])))
|
||||
```
|
||||
|
||||
## Connection Pooling
|
||||
|
||||
`next.jdbc` makes it easy to use either HikariCP or c3p0 for connection pooling.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
Under the hood, whenever you ask `next.jdbc` to execute some SQL it creates a `java.sql.PreparedStatement`, adds in the parameters you provide, and then calls `.execute` on it. Then it attempts to get a `ResultSet` from that and either return it or process it. If you asked for generated keys to be returned, that `ResultSet` will contain those generated keys if your database supports it, otherwise it will be whatever the `.execute` function produces. If no `ResultSet` is available at all, `next.jdbc` will ask for the count of updated rows and return that as if it were a result set.
|
||||
|
||||
*Note: Some databases do not support all SQL operations via `PreparedStatement`, in which case you may need to create a `java.sql.Statement` and pass that into `plan`, `execute!`, or `execute-one!`, along with the SQL you wish to execute. Note that such statement execution may not have parameters. See the [Prepared Statement Caveat in Getting Started](/doc/getting-started.md#prepared-statement-caveat) for an example.*
|
||||
|
||||
If you have a SQL operation that you intend to run multiple times on the same `java.sql.Connection`, it may be worth creating the prepared statement yourself and reusing it. `next.jdbc/prepare` accepts a connection and a vector of SQL and optional parameters and returns a `java.sql.PreparedStatement` which can be passed to `plan`, `execute!`, or `execute-one!` as the first argument. It is your responsibility to close the prepared statement after it has been used.
|
||||
|
||||
If you need to pass an option map to `plan`, `execute!`, or `execute-one!` when passing a prepared statement, you must pass `nil` or `[]` as the second argument:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
(:import (java.sql Clob
|
||||
PreparedStatement
|
||||
ResultSet ResultSetMetaData
|
||||
Statement
|
||||
SQLException)
|
||||
(java.util Locale)))
|
||||
|
||||
|
|
@ -532,6 +533,38 @@
|
|||
init')))
|
||||
(f init {:next.jdbc/update-count (.getUpdateCount stmt)})))
|
||||
|
||||
(defn- stmt-sql->result-set
|
||||
"Given a `Statement`, a SQL command, and options, execute it and return a
|
||||
`ResultSet` if possible."
|
||||
^ResultSet
|
||||
[^Statement stmt ^String sql opts]
|
||||
(if (.execute stmt sql)
|
||||
(.getResultSet stmt)
|
||||
(when (:return-keys opts)
|
||||
(try
|
||||
(.getGeneratedKeys stmt)
|
||||
(catch Exception _)))))
|
||||
|
||||
(defn- reduce-stmt-sql
|
||||
"Execute the SQL command on the given `Statement`, attempt to get either
|
||||
its `ResultSet` or its generated keys (as a `ResultSet`), and reduce
|
||||
that using the supplied function and initial value.
|
||||
|
||||
If the statement yields neither a `ResultSet` nor generated keys, return
|
||||
a hash map containing `:next.jdbc/update-count` and the number of rows
|
||||
updated, with the supplied function and initial value applied."
|
||||
[^Statement stmt sql f init opts]
|
||||
(if-let [rs (stmt-sql->result-set stmt sql opts)]
|
||||
(let [rs-map (mapify-result-set rs opts)]
|
||||
(loop [init' init]
|
||||
(if (.next rs)
|
||||
(let [result (f init' rs-map)]
|
||||
(if (reduced? result)
|
||||
@result
|
||||
(recur result)))
|
||||
init')))
|
||||
(f init {:next.jdbc/update-count (.getUpdateCount stmt)})))
|
||||
|
||||
(extend-protocol p/Executable
|
||||
java.sql.Connection
|
||||
(-execute [this sql-params opts]
|
||||
|
|
@ -618,6 +651,34 @@
|
|||
(datafiable-result-set rs (.getConnection this) opts)
|
||||
[{:next.jdbc/update-count (.getUpdateCount this)}]))
|
||||
|
||||
java.sql.Statement
|
||||
;; we can't tell if this PreparedStatement will return generated
|
||||
;; keys so we pass a truthy value to at least attempt it if we
|
||||
;; do not get a ResultSet back from the execute call
|
||||
(-execute [this sql-params opts]
|
||||
(assert (= 1 (count sql-params))
|
||||
"Parameters cannot be provided when executing a non-prepared Statement")
|
||||
(reify clojure.lang.IReduceInit
|
||||
(reduce [_ f init]
|
||||
(reduce-stmt-sql this (first sql-params) f init (assoc opts :return-keys true)))
|
||||
(toString [_] "`IReduceInit` from `plan` -- missing reduction?")))
|
||||
(-execute-one [this sql-params opts]
|
||||
(assert (= 1 (count sql-params))
|
||||
"Parameters cannot be provided when executing a non-prepared Statement")
|
||||
(if-let [rs (stmt-sql->result-set this (first sql-params) (assoc opts :return-keys true))]
|
||||
(let [builder-fn (get opts :builder-fn as-maps)
|
||||
builder (builder-fn rs opts)]
|
||||
(when (.next rs)
|
||||
(datafiable-row (row-builder builder)
|
||||
(.getConnection this) opts)))
|
||||
{:next.jdbc/update-count (.getUpdateCount this)}))
|
||||
(-execute-all [this sql-params opts]
|
||||
(assert (= 1 (count sql-params))
|
||||
"Parameters cannot be provided when executing a non-prepared Statement")
|
||||
(if-let [rs (stmt-sql->result-set this (first sql-params) opts)]
|
||||
(datafiable-result-set rs (.getConnection this) opts)
|
||||
[{:next.jdbc/update-count (.getUpdateCount this)}]))
|
||||
|
||||
Object
|
||||
(-execute [this sql-params opts]
|
||||
(p/-execute (p/get-datasource this) sql-params opts))
|
||||
|
|
|
|||
|
|
@ -93,19 +93,19 @@
|
|||
(s/fdef jdbc/plan
|
||||
:args (s/alt :prepared (s/cat :stmt ::prepared-statement)
|
||||
:sql (s/cat :connectable ::connectable
|
||||
:sql-params ::sql-params
|
||||
:sql-params (s/nilable ::sql-params)
|
||||
:opts (s/? ::opts-map))))
|
||||
|
||||
(s/fdef jdbc/execute!
|
||||
:args (s/alt :prepared (s/cat :stmt ::prepared-statement)
|
||||
:sql (s/cat :connectable ::connectable
|
||||
:sql-params ::sql-params
|
||||
:sql-params (s/nilable ::sql-params)
|
||||
:opts (s/? ::opts-map))))
|
||||
|
||||
(s/fdef jdbc/execute-one!
|
||||
:args (s/alt :prepared (s/cat :stmt ::prepared-statement)
|
||||
:sql (s/cat :connectable ::connectable
|
||||
:sql-params ::sql-params
|
||||
:sql-params (s/nilable ::sql-params)
|
||||
:opts (s/? ::opts-map))))
|
||||
|
||||
(s/fdef jdbc/transact
|
||||
|
|
|
|||
|
|
@ -124,7 +124,23 @@
|
|||
ps (jdbc/prepare
|
||||
con
|
||||
["select * from fruit where id = ?"])]
|
||||
(jdbc/execute! (prep/set-parameters ps [4])))]
|
||||
(jdbc/execute! (prep/set-parameters ps [4]) nil {}))]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
(is (= 1 (count rs)))
|
||||
(is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs))))))
|
||||
(testing "statement"
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))]
|
||||
(jdbc/execute! (.createStatement con)
|
||||
["select * from fruit order by id"]))]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
(is (= 4 (count rs)))
|
||||
(is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs))))
|
||||
(is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) (last rs)))))
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))]
|
||||
(jdbc/execute! (.createStatement con)
|
||||
["select * from fruit where id = 4"]))]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
(is (= 1 (count rs)))
|
||||
|
|
|
|||
Loading…
Reference in a new issue