Fixes #110 by documenting the solution
In addition to adding `row-number` and `column-names`, this also adds support for associative access by numeric key and indexed access.
This commit is contained in:
parent
1b2ce9767e
commit
91b1b25475
5 changed files with 57 additions and 6 deletions
|
|
@ -4,6 +4,7 @@ Only accretive/fixative changes will be made from now on.
|
|||
|
||||
The following changes have been made on **master** since the 1.0.424 release:
|
||||
|
||||
* Enhanced support in `plan` for "metadata" access: `row-number` and `column-names` can be called on the abstract row (even after calling `datafiable-row`). In addition, `Associative` access via numeric "keys" will read columns by index, and row abstractions now support `Indexed` access via `nth` (which will also read columns by index). Fixes #110.
|
||||
* Support for Stuart Sierra's Component library, via `next.jdbc.connection/component`. See updated **Getting Started** guide for usage.
|
||||
* Add example of getting generated keys from `execute-batch!`.
|
||||
* Add MySQL-specific result set streaming tip.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ for `deps.edn` or:
|
|||
```
|
||||
for `project.clj` or `build.boot`.
|
||||
|
||||
In addition, you will need to add dependencies for the JDBC drivers you wish to use for whatever databases you are using. You can see the drivers and versions that `next.jdbc` is tested against in [the project's `deps.edn` file](https://github.com/seancorfield/next-jdbc/blob/master/deps.edn#L11-L24), but many other JDBC drivers for other databases should also work (e.g., Oracle, Red Shift).
|
||||
**In addition, you will need to add dependencies for the JDBC drivers you wish to use for whatever databases you are using.** You can see the drivers and versions that `next.jdbc` is tested against in [the project's `deps.edn` file](https://github.com/seancorfield/next-jdbc/blob/master/deps.edn#L10-L25), but many other JDBC drivers for other databases should also work (e.g., Oracle, Red Shift).
|
||||
|
||||
## An Example REPL Session
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ If you already have a JDBC URL (string), you can use that as-is instead of the d
|
|||
|
||||
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.
|
||||
If the result set contains no rows, `execute!` returns an empty vector `[]`.
|
||||
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; note that `:builder-fn` does not affect this fake "result set"). By default, H2 uses uppercase names and `next.jdbc` returns these as-is.
|
||||
When no result set is available, `next.jdbc` returns a "result set" containing the "update count" from the operation (which is usually the number of rows affected; note that `:builder-fn` does not affect this fake "result set"). By default, H2 uses uppercase names and `next.jdbc` returns these as-is.
|
||||
|
||||
If you only want a single row back -- the first row of any result set, generated keys, or update counts -- you can use `execute-one!` instead. Continuing the REPL session, we'll insert another address and ask for the generated keys to be returned, and then we'll query for a single row:
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ user=>
|
|||
|
||||
Since we used `execute-one!`, we get just one row back (a hash map). This also shows how you provide parameters to SQL statements -- with `?` in the SQL and then the corresponding parameter values in the vector after the SQL string.
|
||||
If the result set contains no rows, `execute-one!` returns `nil`.
|
||||
When no result is produced, and `next.jdbc` returns a fake "result set" containing the "update count", `execute-one!` returns just a single hash map with the key `next.jdbc/update-count` and the number of rows updated.
|
||||
When no result is available, and `next.jdbc` returns a fake "result set" containing the "update count", `execute-one!` returns just a single hash map with the key `next.jdbc/update-count` and the number of rows updated.
|
||||
|
||||
> Note: In general, you should use `execute-one!` for DDL operations since you will only get back an update count. If you have a SQL statement that you know will only return an update count, `execute-one!` is the right choice. If you have a SQL statement that you know will only return a single row in the result set, you probably want to use `execute-one!`. If you use `execute-one!` for a SQL statement that would return multiple rows in a result set, even though you will only get the first row back (as a hash map), the full result set will still be retrieved from the database -- it does not limit the SQL in any way.
|
||||
|
||||
|
|
@ -220,6 +220,17 @@ user=> (into []
|
|||
|
||||
The latter produces a vector of hash maps, just like the result of `execute!`, where each "row" follows the case conventions of the database, the keys are qualified by the table name, and the hash map is datafiable and navigable.
|
||||
|
||||
In addition to the hash map operations described above, the abstraction over the `ResultSet` can also respond to a couple of functions in `next.jdbc.result-set`:
|
||||
|
||||
* `next.jdbc.result-set/row-number` - returns the 1-based row number, by calling `.getRow()` on the `ResultSet`,
|
||||
* `next.jdbc.result-set/column-names` - returns a vector of column names from the `ResultSet`, as created by the result set builder specified.
|
||||
|
||||
> Note: Apache Derby requires the following options to be provided in order to call `.getRow()` (and therefore `row-number`): `{:concurrency :read-only, :cursors :close, :result-type :scroll-insensitive}`
|
||||
|
||||
If you realize a row, by calling `datafiable-row` on the abstract row passed into the reducing function, you can still call `row-number` and `column-names` on that realized row. These functions are _not_ available on the realized rows returned from `execute!` or `execute-one!`, only within reductions over `plan`.
|
||||
|
||||
The order of the column names returned by `column-names` matches SQL's natural order, based on the operation performed, and will also match the order of column values provided in the reduction when using an array-based result set builder (`plan` provides just the column values, one row at a time, when using an array-based builder, without the leading vector of column names that you would get from `execute!`: if you call `datafiable-row` on such a row, you will get a realized vector of column values).
|
||||
|
||||
> Note: since `plan` expects you to process the result set via reduction, you should not use it for DDL or for SQL statements that only produce update counts.
|
||||
|
||||
## Datasources, Connections & Transactions
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ Only `execute!` expects this protocol to be implemented. `execute-one!` and `pla
|
|||
|
||||
The `as-*` functions described above are all implemented in terms of these protocols. They are passed the `ResultSet` object and the options hash map (as passed into various `next.jdbc` functions). They return an implementation of the protocols that is then used to build rows and the result set. Note that the `ResultSet` passed in is _mutable_ and is advanced from row to row by the SQL execution function, so each time `->row` is called, the underlying `ResultSet` object points at each new row in turn. By contrast, `->rs` (which is only called by `execute!`) is invoked _before_ the `ResultSet` is advanced to the first row.
|
||||
|
||||
The options hash map for any `next.jdbc` function can contain a `:builder-fn` key and the value is used as the row/result set builder function. The tests for `next.jdbc.result-set` include a [record-based builder function](https://github.com/seancorfield/next-jdbc/blob/master/test/next/jdbc/result_set_test.clj#L162-L180) as an example of how you can extend this to satisfy your needs.
|
||||
The options hash map for any `next.jdbc` function can contain a `:builder-fn` key and the value is used as the row/result set builder function. The tests for `next.jdbc.result-set` include a [record-based builder function](https://github.com/seancorfield/next-jdbc/blob/master/test/next/jdbc/result_set_test.clj#L335-L353) as an example of how you can extend this to satisfy your needs.
|
||||
|
||||
> Note: When `next.jdbc` cannot obtain a `ResultSet` object and returns `{:next.jdbc/count N}` instead, the builder function is not applied -- the `:builder-fn` option does not affect the shape of the result.
|
||||
|
||||
|
|
@ -80,6 +80,12 @@ The options hash map passed to the builder function will contain a `:next.jdbc/s
|
|||
|
||||
There is also a convenience function, `datafiable-result-set`, that accepts a `ResultSet` object (and a connectable and an options hash map) and returns a fully realized result set, per the `:builder-fn` option (or `as-maps` if that option is omitted).
|
||||
|
||||
The array-based builders warrant special mention:
|
||||
|
||||
* When used with `execute!`, the array-based builders will produce a data structure that is a vector of vectors, with the first element being a vector of column names and subsequent elements being vectors of column values in the same corresponding order. The order of column names and values follows the "natural" order from the SQL operation, as determined by the underlying `ResultSet`.
|
||||
* When used with `execute-one!`, the array-based builders will produce a single vector containing the column values in the "natural" SQL order but you will not get the corresponding column names back.
|
||||
* When used with `plan`, the array-based builders will cause each abstract row to represent a vector of column values rather than a hash map which limits the operations you can perform on the abstraction to just `Associative` (`get` with a numeric key), `Counted` (`count`), and `Indexed` (`nth`). All other operations will either realize a vector, as if by calling `datafiable-row`, or will fail if the operation does not make sense on a vector (as opposed to a hash map).
|
||||
|
||||
## `next.jdbc.optional`
|
||||
|
||||
This namespace contains variants of the six `as-maps`-style builders above that omit keys from the row hash maps if the corresponding column is `NULL`. This is in keeping with Clojure's views of "optionality" -- that optional elements should simply be omitted -- and is provided as an "opt-in" style of rows and result sets.
|
||||
|
|
|
|||
|
|
@ -459,14 +459,35 @@
|
|||
(equiv [this obj]
|
||||
(.equiv ^clojure.lang.IPersistentCollection (row-builder @builder) obj))
|
||||
|
||||
;; we support get with a numeric key for array-based builders:
|
||||
clojure.lang.ILookup
|
||||
(valAt [this k]
|
||||
(try
|
||||
(read-column-by-label (.getObject rs (name k)) (name k))
|
||||
(if (number? k)
|
||||
(let [^Integer i (inc k)]
|
||||
(read-column-by-index (.getObject rs i) (:rsmeta @builder) i))
|
||||
(read-column-by-label (.getObject rs (name k)) (name k)))
|
||||
(catch SQLException _)))
|
||||
(valAt [this k not-found]
|
||||
(try
|
||||
(read-column-by-label (.getObject rs (name k)) (name k))
|
||||
(if (number? k)
|
||||
(let [^Integer i (inc k)]
|
||||
(read-column-by-index (.getObject rs i) (:rsmeta @builder) i))
|
||||
(read-column-by-label (.getObject rs (name k)) (name k)))
|
||||
(catch SQLException _
|
||||
not-found)))
|
||||
|
||||
;; we support nth for array-based builders (i is primitive int here!):
|
||||
clojure.lang.Indexed
|
||||
(nth [this i]
|
||||
(try
|
||||
(let [i (inc i)]
|
||||
(read-column-by-index (.getObject rs i) (:rsmeta @builder) i))
|
||||
(catch SQLException _)))
|
||||
(nth [this i not-found]
|
||||
(try
|
||||
(let [i (inc i)]
|
||||
(read-column-by-index (.getObject rs i) (:rsmeta @builder) i))
|
||||
(catch SQLException _
|
||||
not-found)))
|
||||
|
||||
|
|
|
|||
|
|
@ -406,3 +406,15 @@ CREATE TABLE CLOBBER (
|
|||
rs/clob-column-reader)})
|
||||
(first)
|
||||
:stuff))))))
|
||||
|
||||
(deftest test-get-n-array
|
||||
(testing "get n on bare abstraction over arrays"
|
||||
(is (= [1 2 3]
|
||||
(into [] (map #(get % 0))
|
||||
(p/-execute (ds) ["select id from fruit where id < ?" 4]
|
||||
{:builder-fn rs/as-arrays})))))
|
||||
(testing "nth on bare abstraction over arrays"
|
||||
(is (= [1 2 3]
|
||||
(into [] (map #(nth % 0))
|
||||
(p/-execute (ds) ["select id from fruit where id < ?" 4]
|
||||
{:builder-fn rs/as-arrays}))))))
|
||||
|
|
|
|||
Loading…
Reference in a new issue