Rename :gen-fn to :builder-fn

This commit is contained in:
Sean Corfield 2019-04-24 14:22:35 -07:00
parent b5f0ef4daa
commit cf75268087
8 changed files with 61 additions and 57 deletions

View file

@ -1,6 +1,6 @@
# Change Log
* 2019-04-22 -- 1.0.0-alpha9 -- Fix #14 by respecting `:gen-fn` in `execute-one` for `PreparedStatement`.
* 2019-04-22 -- 1.0.0-alpha9 -- Fix #14 by respecting `:gen-fn` (as of 1.0.0-alpha10: `:builder-fn`) in `execute-one` for `PreparedStatement`.
* 2019-04-21 -- 1.0.0-alpha8 -- Initial publicly announced release.
## Unreleased Changes
@ -8,3 +8,4 @@
The following changes have been committed to the **master** branch and will be in the next release:
* Fix #15 by adding `:next.jdbc/sql-string` to options hash map that is passed down into the builder function.
* Rename `:gen-fn` option to `:builder-fn` since the term "builder" is used everywhere.

View file

@ -4,8 +4,11 @@ The next generation of `clojure.java.jdbc`: a new low-level Clojure wrapper for
## TL;DR
The latest versions on Clojars and on cljdoc:
[![Clojars Project](https://clojars.org/seancorfield/next.jdbc/latest-version.svg)](https://clojars.org/seancorfield/next.jdbc) [![cljdoc badge](https://cljdoc.org/badge/seancorfield/next.jdbc)](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT)
This documentation is for 1.0.0-alpha9.
* [Getting Started](/doc/getting-started.md)
* [Migrating from `clojure.java.jdbc`](/doc/migration-from-clojure-java-jdbc.md)
@ -35,7 +38,7 @@ From a `DataSource`, either you or `next.jdbc` can create a `java.sql.Connection
The primary SQL execution API in `next.jdbc` is:
* `reducible!` -- yields an `IReduceInit` that, when reduced, executes the SQL statement and then reduces over the `ResultSet` with as little overhead as possible.
* `execute!` -- executes the SQL statement and produces a vector of realized hash maps, that use qualified keywords for the column names, of the form `:<table>/<column>`. If you join across multiple tables, the qualified keywords will reflect the originating tables for each of the columns. If the SQL produces named values that do not come from an associated table, a simple, unqualified keyword will be used. The realized hash maps returned by `execute!` are `Datafiable` and thus `Navigable` (see Clojure 1.10's `datafy` and `nav` functions, and tools like Cognitect's REBL). Alternatively, you can specify `{:gen-fn rs/as-arrays}` and produce a vector with column names followed by vectors of row values. `rs/as-maps` is the default for `:gen-fn` but there are also `rs/as-unqualified-maps` and `rs/as-unqualified-arrays` if you want unqualified `:<column>` column names (and there are also lower-case variants of all of these).
* `execute!` -- executes the SQL statement and produces a vector of realized hash maps, that use qualified keywords for the column names, of the form `:<table>/<column>`. If you join across multiple tables, the qualified keywords will reflect the originating tables for each of the columns. If the SQL produces named values that do not come from an associated table, a simple, unqualified keyword will be used. The realized hash maps returned by `execute!` are `Datafiable` and thus `Navigable` (see Clojure 1.10's `datafy` and `nav` functions, and tools like Cognitect's REBL). Alternatively, you can specify `{:builder-fn rs/as-arrays}` and produce a vector with column names followed by vectors of row values. `rs/as-maps` is the default for `:builder-fn` but there are also `rs/as-unqualified-maps` and `rs/as-unqualified-arrays` if you want unqualified `:<column>` column names (and there are also lower-case variants of all of these).
* `execute-one!` -- executes the SQL statement and produces a single realized hash map. The realized hash map returned by `execute-one!` is `Datafiable` and thus `Navigable`.
In addition, there are API functions to create `PreparedStatement`s (`prepare`) from `Connection`s, which can be passed to `reducible!`, `execute!`, or `execute-one!`, and to run code inside a transaction (the `transact` function and the `with-transaction` macro).

View file

@ -36,7 +36,7 @@ The "friendly" SQL functions all accept the following options:
Any function that might realize a row or a result set will accept:
* `:gen-fn` -- a function that implements the `RowBuilder` and `ResultSetBuilder` protocols; strictly speaking, `reducible!` and `execute-one!` only need `RowBuilder` to be implemented (and `reducible!` only needs that if it actually has to realize a row) but most generation functions will implement both for ease of use.
* `:builder-fn` -- a function that implements the `RowBuilder` and `ResultSetBuilder` protocols; strictly speaking, `reducible!` and `execute-one!` only need `RowBuilder` to be implemented (and `reducible!` only needs that if it actually has to realize a row) but most generation functions will implement both for ease of use.
## Prepared Statements

View file

@ -10,9 +10,9 @@ This page attempts to list all of the differences between [clojure.java.jdbc](ht
### Rows and Result Sets
`clojure.java.jdbc` returned result sets (and generated keys) as hash maps with simple, lower-case keys by default. `next.jdbc` returns result sets (and generated keys) as hash maps with qualified, as-is keys by default: each key is qualified by the name of table from which it is drawn, if known. The as-is default is chosen to a) improve performance and b) not mess with the data. Using a `:gen-fn` option of `next.jdbc.result-set/as-unqualified-maps` will produce simple, as-is keys. Using a `:gen-fn` option of `next.jdbc.result-set/as-unqualified-lower-maps` will produce simple, lower-case keys -- the most compatible with `clojure.java.jdbc`'s default behavior.
`clojure.java.jdbc` returned result sets (and generated keys) as hash maps with simple, lower-case keys by default. `next.jdbc` returns result sets (and generated keys) as hash maps with qualified, as-is keys by default: each key is qualified by the name of table from which it is drawn, if known. The as-is default is chosen to a) improve performance and b) not mess with the data. Using a `:builder-fn` option of `next.jdbc.result-set/as-unqualified-maps` will produce simple, as-is keys. Using a `:builder-fn` option of `next.jdbc.result-set/as-unqualified-lower-maps` will produce simple, lower-case keys -- the most compatible with `clojure.java.jdbc`'s default behavior.
If you used `:as-arrays? true`, you will need to use a `:gen-fn` option of `next.jdbc.result-set/as-arrays` (or the unqualified or lower variant, as appropriate).
If you used `:as-arrays? true`, you will need to use a `:builder-fn` option of `next.jdbc.result-set/as-arrays` (or the unqualified or lower variant, as appropriate).
## Primary API
@ -43,7 +43,7 @@ The `next.jdbc.sql` namespace contains several functions with similarities to `c
* `update!` -- similar to `clojure.java.jdbc/update!` but also accepts a hash map of column name/value pairs,
* `delete!` -- similar to `clojure.java.jdbc/delete!` but also accepts a hash map of column name/value pairs.
If you are using `:identifiers` and/or `:entities`, you will need to change to appropriate `:gen-fn` and/or `:table-fn`/`:column-fn` options. For the latter, instead of the `quoted` function, there is `next.jdbc.quoted` which contains functions for the common quoting strategies.
If you are using `:identifiers` and/or `:entities`, you will need to change to appropriate `:builder-fn` and/or `:table-fn`/`:column-fn` options. For the latter, instead of the `quoted` function, there is `next.jdbc.quoted` which contains functions for the common quoting strategies.
If you are using `:result-set-fn` and/or `:row-fn`, you will need to change to explicit calls (to the result set function, or to `map` the row function), or to use the `reducible!` approach with `reduce` or various transducing functions. Note: this means that result sets are never exposed lazily in `next.jdbc` -- in `clojure.java.jdbc` you had to be careful that your `:result-set-fn` was eager, but in `next.jdbc` you either reduce the result set eagerly (via `reducible!`) or you get a fully-realized result set data structure back (from `execute!` and `execute-one!`). As with `clojure.java.jdbc` however, you can still stream result sets from the database and process them via reduction (was `reducible-query`, now `reducible!`). Remember that you can terminate a reduction early by using the `reduced` function to wrap the final value you produce.

View file

@ -40,7 +40,7 @@ Only `execute!` expects this protocol to be implemented. `execute-one!` and `red
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 `:gen-fn` key and the value is used at 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#L148-L164) 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 at 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#L148-L164) as an example of how you can extend this to satisfy your needs.
The options hash map passed to the builder function will contain a `:next.jdbc/sql-string` key, whose value is the SQL string passed into the top-level `next.jdbc` functions (`reducible!`, `execute!`, and `execute-one!`). If no SQL string was passed in -- those functions were called with just a `PreparedStatement` -- then `:next.jdbc/sql-string` will have a `nil` value.

View file

@ -214,11 +214,11 @@
(defn- row-builder
"Given a `RowBuilder` -- a row materialization strategy -- produce a fully
materialized row from it."
[gen]
(->> (reduce (fn [r i] (with-column gen r i))
(->row gen)
(range 1 (inc (column-count gen))))
(row! gen)))
[builder]
(->> (reduce (fn [r i] (with-column builder r i))
(->row builder)
(range 1 (inc (column-count builder))))
(row! builder)))
(defn- mapify-result-set
"Given a `ResultSet`, return an object that wraps the current row as a hash
@ -237,7 +237,7 @@
Supports `DatafiableRow` (which realizes a full row of the data)."
[^ResultSet rs opts]
(let [gen (delay ((get opts :gen-fn as-maps) rs opts))]
(let [builder (delay ((get opts :builder-fn as-maps) rs opts))]
(reify
clojure.lang.ILookup
@ -265,16 +265,16 @@
(name k)))
(catch SQLException _)))
(assoc [this k v]
(assoc (row-builder @gen) k v))
(assoc (row-builder @builder) k v))
clojure.lang.Seqable
(seq [this]
(seq (row-builder @gen)))
(seq (row-builder @builder)))
DatafiableRow
(datafiable-row [this connectable opts]
(with-meta
(row-builder @gen)
(row-builder @builder)
{`core-p/datafy (navize-row connectable opts)})))))
(extend-protocol
@ -334,10 +334,10 @@
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(let [gen-fn (get opts :gen-fn as-maps)
gen (gen-fn rs opts)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(when (.next rs)
(datafiable-row (row-builder gen) this opts)))
(datafiable-row (row-builder builder) this opts)))
{:next.jdbc/update-count (.getUpdateCount stmt)})))
(-execute-all [this sql-params opts]
(with-open [stmt (prepare/create this
@ -345,14 +345,14 @@
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(let [gen-fn (get opts :gen-fn as-maps)
gen (gen-fn rs opts)]
(loop [rs' (->rs gen) more? (.next rs)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(loop [rs' (->rs builder) more? (.next rs)]
(if more?
(recur (with-row gen rs'
(datafiable-row (row-builder gen) this opts))
(recur (with-row builder rs'
(datafiable-row (row-builder builder) this opts))
(.next rs))
(rs! gen rs'))))
(rs! builder rs'))))
[{:next.jdbc/update-count (.getUpdateCount stmt)}])))
javax.sql.DataSource
@ -372,10 +372,10 @@
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(let [gen-fn (get opts :gen-fn as-maps)
gen (gen-fn rs opts)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(when (.next rs)
(datafiable-row (row-builder gen) this opts)))
(datafiable-row (row-builder builder) this opts)))
{:next.jdbc/update-count (.getUpdateCount stmt)}))))
(-execute-all [this sql-params opts]
(with-open [con (p/get-connection this opts)]
@ -384,14 +384,14 @@
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(let [gen-fn (get opts :gen-fn as-maps)
gen (gen-fn rs opts)]
(loop [rs' (->rs gen) more? (.next rs)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(loop [rs' (->rs builder) more? (.next rs)]
(if more?
(recur (with-row gen rs'
(datafiable-row (row-builder gen) this opts))
(recur (with-row builder rs'
(datafiable-row (row-builder builder) this opts))
(.next rs))
(rs! gen rs'))))
(rs! builder rs'))))
[{:next.jdbc/update-count (.getUpdateCount stmt)}]))))
java.sql.PreparedStatement
@ -404,23 +404,23 @@
(reduce-stmt this f init (assoc opts :return-keys true)))))
(-execute-one [this _ opts]
(if-let [rs (stmt->result-set this (assoc opts :return-keys true))]
(let [gen-fn (get opts :gen-fn as-maps)
gen (gen-fn rs opts)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(when (.next rs)
(datafiable-row (row-builder gen)
(datafiable-row (row-builder builder)
(.getConnection this) opts)))
{:next.jdbc/update-count (.getUpdateCount this)}))
(-execute-all [this _ opts]
(if-let [rs (stmt->result-set this opts)]
(let [gen-fn (get opts :gen-fn as-maps)
gen (gen-fn rs opts)]
(loop [rs' (->rs gen) more? (.next rs)]
(let [builder-fn (get opts :builder-fn as-maps)
builder (builder-fn rs opts)]
(loop [rs' (->rs builder) more? (.next rs)]
(if more?
(recur (with-row gen rs'
(datafiable-row (row-builder gen)
(recur (with-row builder rs'
(datafiable-row (row-builder builder)
(.getConnection this) opts))
(.next rs))
(rs! gen rs'))))
(rs! builder rs'))))
[{:next.jdbc/update-count (.getUpdateCount this)}]))
Object

View file

@ -76,21 +76,21 @@
(testing "unqualified row builder"
(let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 2]
{:gen-fn rs/as-unqualified-maps})]
{:builder-fn rs/as-unqualified-maps})]
(is (map? row))
(is (= 2 (:ID row)))
(is (= "Banana" (:NAME row)))))
(testing "lower-case row builder"
(let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 3]
{:gen-fn rs/as-lower-maps})]
{:builder-fn rs/as-lower-maps})]
(is (map? row))
(is (= 3 (:fruit/id row)))
(is (= "Peach" (:fruit/name row)))))
(testing "lower-case row builder"
(let [row (p/-execute-one (ds)
["select * from fruit where id = ?" 4]
{:gen-fn rs/as-unqualified-lower-maps})]
{:builder-fn rs/as-unqualified-lower-maps})]
(is (map? row))
(is (= 4 (:id row)))
(is (= "Orange" (:name row))))))
@ -100,27 +100,27 @@
(is (= [false]
(into [] (map map?) ; it is not a real map
(p/-execute (ds) ["select * from fruit where id = ?" 1]
{:gen-fn (constantly nil)}))))
{:builder-fn (constantly nil)}))))
(is (= ["Apple"]
(into [] (map :name) ; but keyword selection works
(p/-execute (ds) ["select * from fruit where id = ?" 1]
{:gen-fn (constantly nil)}))))
{:builder-fn (constantly nil)}))))
(is (= [[2 [:name "Banana"]]]
(into [] (map (juxt #(get % "id") ; get by string key works
#(find % :name))) ; get MapEntry works
(p/-execute (ds) ["select * from fruit where id = ?" 2]
{:gen-fn (constantly nil)}))))
{:builder-fn (constantly nil)}))))
(is (= [{:id 3 :name "Peach"}]
(into [] (map #(select-keys % [:id :name])) ; select-keys works
(p/-execute (ds) ["select * from fruit where id = ?" 3]
{:gen-fn (constantly nil)}))))
{:builder-fn (constantly nil)}))))
(is (= [[:orange 4]]
(into [] (map #(vector (if (contains? % :name) ; contains works
(keyword (str/lower-case (:name %)))
:unnamed)
(get % :id 0))) ; get with not-found works
(p/-execute (ds) ["select * from fruit where id = ?" 4]
{:gen-fn (constantly nil)})))))
{:builder-fn (constantly nil)})))))
(testing "assoc and seq build maps"
(is (map? (reduce (fn [_ row] (reduced (assoc row :x 1)))
nil
@ -163,12 +163,12 @@
(deftest custom-map-builder
(let [row (p/-execute-one (ds)
["select * from fruit where appearance = ?" "red"]
{:gen-fn fruit-builder})]
{:builder-fn fruit-builder})]
(is (instance? Fruit row))
(is (= 1 (:id row))))
(let [rs (p/-execute-all (ds)
["select * from fruit where appearance = ?" "red"]
{:gen-fn fruit-builder})]
{:builder-fn fruit-builder})]
(is (every? #(instance? Fruit %) rs))
(is (= 1 (count rs)))
(is (= 1 (:id (first rs))))))

View file

@ -31,7 +31,7 @@
(let [rs (jdbc/execute!
(ds)
["select * from fruit order by id"]
{:gen-fn rs/as-maps})]
{:builder-fn rs/as-maps})]
(is (every? map? rs))
(is (every? meta rs))
(is (= 4 (count rs)))
@ -40,7 +40,7 @@
(let [rs (jdbc/execute!
(ds)
["select * from fruit order by id"]
{:gen-fn rs/as-arrays})]
{:builder-fn rs/as-arrays})]
(is (every? vector? rs))
(is (= 5 (count rs)))
(is (every? #(= 5 (count %)) rs))
@ -54,7 +54,7 @@
(let [rs (jdbc/execute!
(ds)
["select * from fruit order by id"]
{:gen-fn rs/as-unqualified-maps})]
{:builder-fn rs/as-unqualified-maps})]
(is (every? map? rs))
(is (every? meta rs))
(is (= 4 (count rs)))
@ -63,7 +63,7 @@
(let [rs (jdbc/execute!
(ds)
["select * from fruit order by id"]
{:gen-fn rs/as-unqualified-arrays})]
{:builder-fn rs/as-unqualified-arrays})]
(is (every? vector? rs))
(is (= 5 (count rs)))
(is (every? #(= 5 (count %)) rs))