Consolidate the three use cases

* Remove `execute!` and `execute-one!` from `result-set`.
* Add `-execute-all` and `-execute-one` to the `Executable` protocol 
instead.
* Remove `:row-fn` support.
* Allow a `PreparedStatement` to be `Connectable` so it can be used with 
`datafy`/`nav`.
* Rewrite `RowBuilder` protocol and add `ResultSetBuilder` protocol; add 
`as-arrays` as a builder.
This commit is contained in:
Sean Corfield 2019-04-17 23:34:31 -07:00
parent 788015909f
commit b5eb5b880e
6 changed files with 242 additions and 190 deletions

View file

@ -16,8 +16,6 @@ I also wanted `datafy`/`nav` support baked right in (it was added to `clojure.ja
The API so far is still very much a work-in-progress. I'm still very conflicted about the "syntactic sugar" SQL functions (`insert!`, `query`, `update!`, and `delete!`). They go beyond what I really want to include in the API, but I know that their equivalents in `clojure.java.jdbc` are heavily used (based on the number of questions and JIRA issues I get).
My latest round of changes exposed the mapped-function-over-rows API more prominently, but I'm still not happy with the "feel" of that aspect of the API yet (it creates a tension with the datafication behavior).
So, while I'm comfortable to put it out there and get feedback and I've had lots of great feedback so far expect to see more changes, possible some dramatic ones, in the next month or so before I actually settle on where the library will live and what the published artifacts will look like.
## Usage
@ -28,9 +26,10 @@ 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 reduces it into 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).
* `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.
* `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!` or `execute!`, and to run code inside a transaction (the `transact` function and the `with-transaction` macro).
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).
Since `next.jdbc` uses raw Java JDBC types, you can use `with-open` directly to reuse connections and ensure they are cleaned up correctly:
@ -45,8 +44,8 @@ Since `next.jdbc` uses raw Java JDBC types, you can use `with-open` directly to
### Usage scenarios
There are three intended usage scenarios that may drive the API to change:
* Execute a SQL statement to obtain a single, fully-realized, `Datafiable` hash map that represents either the first row from a `ResultSet`, the first generated keys result (again, from a `ResultSet`), or the first result where neither of those are available (`next.jdbc` will yield `[{:next.jdbc/update-count N}])` when it can only return an update count). This usage is currently supported by `execute-one!` but I'm not very happy with it.
* Execute a SQL statement to obtain a fully-realized, `Datafiable` result set -- a vector of hash maps. This usage is supported by `execute!`.
* Execute a SQL statement to obtain a single, fully-realized, `Datafiable` hash map that represents either the first row from a `ResultSet`, the first generated keys result (again, from a `ResultSet`), or the first result where neither of those are available (`next.jdbc` will yield `{:next.jdbc/update-count N}`) when it can only return an update count). This usage is currently supported by `execute-one!`.
* Execute a SQL statement to obtain a fully-realized, `Datafiable` result set -- a vector of hash maps. This usage is supported by `execute!`. You can also produce a vector of column names/row values (`next.jdbc.result-set/as-arrays`).
* Execute a SQL statement and process it in a single eager operation, which may allow for the results to be streamed from the database (how to persuade JDBC to do that is database-specific!), and which cleans up resources before returning the result -- even if the reduction is short-circuited via `reduced`. This usage is supported by `reducible!`.
In addition, convenience functions -- "syntactic sugar" -- are provided to insert rows, run queries, update rows, and delete rows, using the same names as in `clojure.java.jdbc`. These are currently in `next.jdbc` but may move to `next.jdbc.sql` since they involve SQL creation, or they may move into a separate "sibling" library -- since they are not part of the intended core API.
@ -63,7 +62,7 @@ Whereas `clojure.java.jdbc` supports a wide variety of options to describe how t
`:result-set-fn` is not supported: either call your function on the result of `execute!` or handle it via reducing the result of `reducible!`.
`:row-fn` is still (currently) supported in `execute!` and, if present, will prevent the default behavior of producing `Datafiable` rows. Depending on what your row-processing function does, you may be able to combine it with `next.jdbc.result-set/datafiable-row` (which is what is used by default to produce `Datafiable` rows).
`:row-fn` is not supported; either `map` your function over the result of `execute!` or handle it via reducing the result of `reducible!`.
### Clojure identifier creation

View file

@ -146,32 +146,23 @@
If it is called on a PreparedStatement, it cannot produce a datafiable
result (because that requires a connectable instead)."
([stmt]
(rs/execute! stmt [] (partial into {}) {}))
(p/-execute-all stmt [] {}))
([connectable sql-params]
(rs/execute! connectable sql-params #(rs/datafiable-row % connectable {}) {}))
(p/-execute-all connectable sql-params {}))
([connectable sql-params opts]
(rs/execute! connectable sql-params #(rs/datafiable-row % connectable opts) opts))
([connectable sql-params f opts]
(rs/execute! connectable sql-params f opts)))
(p/-execute-all connectable sql-params opts)))
(defn execute-one!
"General SQL execution function that returns just the first row of a result.
Invokes 'reducible!' but immediately returns the first row.
Can be called on a PreparedStatement, a Connection, or something that can
produce a Connection via a DataSource.
If it is called on a PreparedStatement, it cannot produce a datafiable
result (because that requires a connectable instead)."
produce a Connection via a DataSource."
([stmt]
(rs/execute-one! stmt [] (partial into {}) {}))
(p/-execute-one stmt [] {}))
([connectable sql-params]
(rs/execute-one! connectable sql-params #(rs/datafiable-row % connectable {}) {}))
(p/-execute-one connectable sql-params {}))
([connectable sql-params opts]
(rs/execute-one! connectable sql-params #(rs/datafiable-row % connectable opts) opts))
([connectable sql-params f opts]
(rs/execute-one! connectable sql-params f opts)))
(p/-execute-one connectable sql-params opts)))
(defn transact
"Given a connectable object and a function (taking a Connection),
@ -197,7 +188,7 @@
`(transact ~connectable ~opts (fn [~sym] ~@body)))
(defn insert!
"Syntactic sugar over execute! to make inserting hash maps easier.
"Syntactic sugar over execute-one! to make inserting hash maps easier.
Given a connectable object, a table name, and a data hash map, inserts the
data as a single row in the database and attempts to return a map of generated
@ -205,9 +196,8 @@
([connectable table key-map]
(insert! connectable table key-map {}))
([connectable table key-map opts]
(rs/execute! connectable
(execute-one! connectable
(sql/for-insert table key-map opts)
(partial into {})
(merge {:return-keys true} opts))))
(defn insert-multi!
@ -220,9 +210,8 @@
([connectable table cols rows]
(insert-multi! connectable table cols rows {}))
([connectable table cols rows opts]
(rs/execute! connectable
(execute! connectable
(sql/for-insert-multi table cols rows opts)
(partial into {})
(merge {:return-keys true} opts))))
(defn query
@ -233,15 +222,7 @@
([connectable sql-params]
(query connectable sql-params {}))
([connectable sql-params opts]
(if-let [row-fn (:row-fn opts)]
(rs/execute! connectable
sql-params
row-fn
opts)
(rs/execute! connectable
sql-params
#(rs/datafiable-row % connectable opts)
opts))))
(execute! connectable sql-params opts)))
(defn find-by-keys
"Syntactic sugar over execute! to make certain common queries easier.
@ -251,18 +232,10 @@
([connectable table key-map]
(find-by-keys connectable table key-map {}))
([connectable table key-map opts]
(if-let [row-fn (:row-fn opts)]
(rs/execute! connectable
(sql/for-query table key-map opts)
row-fn
opts)
(rs/execute! connectable
(sql/for-query table key-map opts)
#(rs/datafiable-row % connectable opts)
opts))))
(execute! connectable (sql/for-query table key-map opts) opts)))
(defn get-by-id
"Syntactic sugar over execute! to make certain common queries easier.
"Syntactic sugar over execute-one! to make certain common queries easier.
Given a connectable object, a table name, and a primary key value, returns
a hash map of the first row that matches.
@ -274,18 +247,10 @@
([connectable table pk opts]
(get-by-id connectable table pk :id opts))
([connectable table pk pk-name opts]
(if-let [row-fn (:row-fn opts)]
(rs/execute-one! connectable
(sql/for-query table {pk-name pk} opts)
row-fn
opts)
(rs/execute-one! connectable
(sql/for-query table {pk-name pk} opts)
#(rs/datafiable-row % connectable opts)
opts))))
(execute-one! connectable (sql/for-query table {pk-name pk} opts) opts)))
(defn update!
"Syntactic sugar over execute! to make certain common updates easier.
"Syntactic sugar over execute-one! to make certain common updates easier.
Given a connectable object, a table name, a hash map of columns and values
to set, and either a hash map of columns and values to search on or a vector
@ -293,13 +258,12 @@
([connectable table key-map where-params]
(update! connectable table key-map where-params {}))
([connectable table key-map where-params opts]
(rs/execute! connectable
(execute-one! connectable
(sql/for-update table key-map where-params opts)
(partial into {})
opts)))
(defn delete!
"Syntactic sugar over execute! to make certain common deletes easier.
"Syntactic sugar over execute-one! to make certain common deletes easier.
Given a connectable object, a table name, and either a hash map of columns
and values to search on or a vector of a SQL where clause and parameters,
@ -307,7 +271,4 @@
([connectable table where-params]
(delete! connectable table where-params {}))
([connectable table where-params opts]
(rs/execute! connectable
(sql/for-delete table where-params opts)
(partial into {})
opts)))
(execute-one! connectable (sql/for-delete table where-params opts) opts)))

View file

@ -165,5 +165,10 @@
(extend-protocol p/Connectable
javax.sql.DataSource
(get-connection [this opts] (make-connection this opts))
java.sql.PreparedStatement
;; note: options are ignored and this should not be closed independently
;; of the PreparedStatement to which it belongs: this done to allow
;; datafy/nav across a PreparedStatement only...
(get-connection [this _] (.getConnection this))
Object
(get-connection [this opts] (p/get-connection (p/get-datasource this) opts)))

View file

@ -18,6 +18,19 @@
PreparedStatement, and Object (on the assumption that an Object can be
turned into a DataSource and therefore used to get a Connection).
-execute-one -- given SQL and parameters, executes the SQL and produces
the first row of the ResultSet as a datafiable hash map (by default);
implementations are provided for Connection, DataSource,
PreparedStatement, and Object (on the assumption that an Object can be
turned into a DataSource and therefore used to get a Connection).
-execute-all -- given SQL and parameters, executes the SQL and produces
either a vector of datafiable hash maps from the ResultSet (default)
or a vector of column names followed by vectors of row values;
implementations are provided for Connection, DataSource,
PreparedStatement, and Object (on the assumption that an Object can be
turned into a DataSource and therefore used to get a Connection).
prepare -- given SQL and parameters, produce a PreparedStatement that can
be executed (by -execute above); implementation is provided for
Connection.
@ -34,7 +47,9 @@
(defprotocol Connectable
(get-connection ^java.lang.AutoCloseable [this opts]))
(defprotocol Executable
(-execute ^clojure.lang.IReduceInit [this sql-params opts]))
(-execute ^clojure.lang.IReduceInit [this sql-params opts])
(-execute-one [this sql-params opts])
(-execute-all [this sql-params opts]))
(defprotocol Preparable
(prepare ^java.sql.PreparedStatement [this sql-params opts]))
(defprotocol Transactable

View file

@ -55,30 +55,91 @@
(defprotocol RowBuilder
"Protocol for building rows in various representations:
row -- called once per row to create the basis of each row
columns -- return the number of columns in each row
->row -- called once per row to create the basis of each row
column-count -- return the number of columns in each row
with-column -- called with the index of the column to be added
build -- called once per row to finalize each row once it is complete"
(row [_])
(columns [_])
row! -- called once per row to finalize each row once it is complete"
(->row [_])
(column-count [_])
(with-column [_ row i])
(build [_ row]))
(row! [_ row]))
(defn map-row-builder [^ResultSet rs opts]
(defprotocol ResultSetBuilder
"Protocol for building result sets in various representations:
->rs -- called to create the basis of the result set
with-row -- called with the row to be added
rs! -- called to finalize the result set once it is complete"
(->rs [_])
(with-row [_ rs row])
(rs! [_ rs]))
(defn map-row-builder
"Given a ResultSet and options, return a RowBuilder that produces
bare hash map rows."
[^ResultSet rs opts]
(let [rsmeta (.getMetaData rs)
cols (get-column-names rsmeta opts)]
(reify
RowBuilder
(row [this] (transient {}))
(columns [this] (count cols))
(->row [this] (transient {}))
(column-count [this] (count cols))
(with-column [this row i]
(assoc! row
(nth cols (dec i))
(read-column-by-index (.getObject rs ^Integer i) rsmeta i)))
(build [this row] (persistent! row)))))
(row! [this row] (persistent! row)))))
(defn map-rs-builder
"Given a ResultSet and options, return a RowBuilder and ResultSetBuilder
that produces bare vectors of hash map rows."
[^ResultSet rs opts]
(let [rsmeta (.getMetaData rs)
cols (get-column-names rsmeta opts)]
(reify
RowBuilder
(->row [this] (transient {}))
(column-count [this] (count cols))
(with-column [this row i]
(assoc! row
(nth cols (dec i))
(read-column-by-index (.getObject rs ^Integer i) rsmeta i)))
(row! [this row] (persistent! row))
ResultSetBuilder
(->rs [this] (transient []))
(with-row [this rs row]
(conj! rs row))
(rs! [this rs] (persistent! rs)))))
(defn as-arrays
"Given a ResulSet and options, return a RowBuilder and ResultSetBuilder
that produces a vector of column names followed by vectors of row values."
[^ResultSet rs opts]
(let [rsmeta (.getMetaData rs)
cols (get-column-names rsmeta opts)]
(reify
RowBuilder
(->row [this] (transient []))
(column-count [this] (count cols))
(with-column [this row i]
(conj! row (read-column-by-index (.getObject rs ^Integer i) rsmeta i)))
(row! [this row] (persistent! row))
ResultSetBuilder
(->rs [this] (transient [cols]))
(with-row [this rs row]
(conj! rs row))
(rs! [this rs] (persistent! rs)))))
(declare navize-row)
(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)))
(defprotocol DatafiableRow
"Given a connectable object, return a function that knows how to turn a row
into a datafiable object that can be 'nav'igated."
@ -96,12 +157,7 @@
Supports Seqable which realizes a full row of the data."
[^ResultSet rs opts]
(let [gen (when-let [gen-fn (:gen-fn opts)] (gen-fn rs opts))
row-builder (fn [] ; blows up if :gen-fn not provided
(->> (reduce (fn [r i] (with-column gen r i))
(row gen)
(range 1 (inc (columns gen))))
(build gen)))]
(let [gen (when-let [gen-fn (:gen-fn opts)] (delay (gen-fn rs opts)))]
(reify
clojure.lang.ILookup
@ -129,16 +185,16 @@
(name k)))
(catch SQLException _)))
(assoc [this k v]
(assoc (row-builder) k v))
(assoc (row-builder @gen) k v))
clojure.lang.Seqable
(seq [this]
(seq (row-builder)))
(seq (row-builder @gen)))
DatafiableRow
(datafiable-row [this connectable opts]
(with-meta
(row-builder)
(row-builder @gen)
{`core-p/datafy (navize-row connectable opts)})))))
(extend-protocol
@ -149,19 +205,17 @@
this
{`core-p/datafy (navize-row connectable opts)})))
#_(defn as-arrays
"A reducing function that can be used on a result set to produce an
array-based representation, where the first element is a vector of the
column names in the result set, and subsequent elements are vectors of
the rows from the result set.
It should be used with a nil initial value:
(reduce rs/as-arrays nil (reducible! con sql-params))"
[result rs-map]
(if result
(conj result (row-values rs-map))
(conj [(column-names rs-map)] (row-values rs-map))))
(defn- stmt->result-set
"Given a PreparedStatement and options, execute it and return a ResultSet
if possible."
^ResultSet
[^PreparedStatement stmt opts]
(if (.execute stmt)
(.getResultSet stmt)
(when (:return-keys opts)
(try
(.getGeneratedKeys stmt)
(catch Exception _)))))
(defn- reduce-stmt
"Execute the PreparedStatement, attempt to get either its ResultSet or
@ -172,12 +226,7 @@
a hash map containing :next.jdbc/update-count and the number of rows
updated, with the supplied function and initial value applied."
[^PreparedStatement stmt f init opts]
(if-let [^ResultSet rs (if (.execute stmt)
(.getResultSet stmt)
(when (:return-keys opts)
(try
(.getGeneratedKeys stmt)
(catch Exception _))))]
(if-let [rs (stmt->result-set stmt opts)]
(let [rs-map (mapify-result-set rs opts)]
(loop [init' init]
(if (.next rs)
@ -198,6 +247,31 @@
(rest sql-params)
opts)]
(reduce-stmt stmt f init opts)))))
(-execute-one [this sql-params opts]
(with-open [stmt (prepare/create this
(first sql-params)
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(when (.next rs)
(datafiable-row (row-builder (map-row-builder rs opts)) this opts))
{:next.jdbc/update-count (.getUpdateCount stmt)})))
(-execute-all [this sql-params opts]
(with-open [stmt (prepare/create this
(first sql-params)
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(let [gen-fn (get opts :gen-fn map-rs-builder)
gen (gen-fn rs opts)]
(loop [rs' (->rs gen) more? (.next rs)]
(if more?
(recur (with-row gen rs'
(datafiable-row (row-builder gen) this opts))
(.next rs))
(rs! gen rs'))))
{:next.jdbc/update-count (.getUpdateCount stmt)})))
javax.sql.DataSource
(-execute [this sql-params opts]
(reify clojure.lang.IReduceInit
@ -208,41 +282,67 @@
(rest sql-params)
opts)]
(reduce-stmt stmt f init opts))))))
(-execute-one [this sql-params opts]
(with-open [con (p/get-connection this opts)]
(with-open [stmt (prepare/create con
(first sql-params)
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(when (.next rs)
(datafiable-row (row-builder (map-row-builder rs opts)) this opts))
{:next.jdbc/update-count (.getUpdateCount stmt)}))))
(-execute-all [this sql-params opts]
(with-open [con (p/get-connection this opts)]
(with-open [stmt (prepare/create con
(first sql-params)
(rest sql-params)
opts)]
(if-let [rs (stmt->result-set stmt opts)]
(let [gen-fn (get opts :gen-fn map-rs-builder)
gen (gen-fn rs opts)]
(loop [rs' (->rs gen) more? (.next rs)]
(if more?
(recur (with-row gen rs'
(datafiable-row (row-builder gen) this opts))
(.next rs))
(rs! gen rs'))))
{:next.jdbc/update-count (.getUpdateCount stmt)}))))
java.sql.PreparedStatement
(-execute [this _ opts]
(reify clojure.lang.IReduceInit
;; 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 _ opts]
(reify clojure.lang.IReduceInit
(reduce [_ f init]
(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))]
(when (.next rs)
(datafiable-row (row-builder (map-row-builder rs opts))
(.getConnection this) opts))
{:next.jdbc/update-count (.getUpdateCount this)}))
(-execute-all [this sql-params opts]
(if-let [rs (stmt->result-set this opts)]
(let [gen-fn (get opts :gen-fn map-rs-builder)
gen (gen-fn rs opts)]
(loop [rs' (->rs gen) more? (.next rs)]
(if more?
(recur (with-row gen rs'
(datafiable-row (row-builder gen)
(.getConnection this) opts))
(.next rs))
(rs! gen rs'))))
{:next.jdbc/update-count (.getUpdateCount this)}))
Object
(-execute [this sql-params opts]
(p/-execute (p/get-datasource this) sql-params opts)))
(defn execute!
"Given a connectable object and SQL and parameters, execute it and reduce it
into a vector of processed hash maps (rows).
By default, this will create datafiable rows but :row-fn can override that."
[connectable sql-params f opts]
(into []
(map f)
(p/-execute connectable
sql-params
(update opts :gen-fn #(or % map-row-builder)))))
(defn execute-one!
"Given a connectable object and SQL and parameters, execute it and return
just the first processed hash map (row).
By default, this will create a datafiable row but :row-fn can override that."
[connectable sql-params f opts]
(reduce (fn [_ row] (reduced (f row)))
nil
(p/-execute connectable
sql-params
(update opts :gen-fn #(or % map-row-builder)))))
(p/-execute (p/get-datasource this) sql-params opts))
(-execute-one [this sql-params opts]
(p/-execute-one (p/get-datasource this) sql-params opts))
(-execute-all [this sql-params opts]
(p/-execute-all (p/get-datasource this) sql-params opts)))
(defn- default-schema
"The default schema lookup rule for column names.

View file

@ -13,12 +13,12 @@
;; these should be equivalent
(def con (get-connection (get-datasource db-spec) {}))
(def con (get-connection db-spec {}))
(execute! con ["DROP TABLE fruit"])
(execute-one! con ["DROP TABLE fruit"])
;; h2
(execute! con ["CREATE TABLE fruit (id int default 0, name varchar(32) primary key, appearance varchar(32), cost int, grade real)"])
(execute-one! con ["CREATE TABLE fruit (id int default 0, name varchar(32) primary key, appearance varchar(32), cost int, grade real)"])
;; either this...
(execute! con ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (1,'Apple','red',59,87), (2,'Banana','yellow',29,92.2), (3,'Peach','fuzzy',139,90.0), (4,'Orange','juicy',89,88.6)"])
;; ...or this
(execute-one! con ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (1,'Apple','red',59,87), (2,'Banana','yellow',29,92.2), (3,'Peach','fuzzy',139,90.0), (4,'Orange','juicy',89,88.6)"])
;; ...or this (H2 can't return generated keys for this)
(insert-multi! con :fruit [:id :name :appearance :cost :grade]
[[1 "Apple" "red" 59 87]
[2,"Banana","yellow",29,92.2]
@ -48,73 +48,61 @@
value (.getObject rs "name")]
(.close ps)
value))
(quick-bench (select* con)) ; 1.06 micros
(quick-bench (select* con)) ; 1.15-1.19 micros
;; almost same as the Java example above -- 1.42-1.49 micros -- 1.4x Java
;; almost same as the Java example above -- 1.66-1.7 micros -- 1.4x Java
(quick-bench
(reduce (fn [rs m] (reduced (:name m)))
nil
(reducible! con ["select * from fruit where appearance = ?" "red"])))
;; run through convenience function -- 1.52-1.55 micros
;; run through convenience function -- 2.4 micros
(quick-bench
(execute-one! con
(:FRUIT/NAME (execute-one! con
["select * from fruit where appearance = ?" "red"]
:name
{}))
(quick-bench
(execute-one! con
["select * from fruit where appearance = ?" "red"]
:name ; turn off row builder as we don't need it
;; this gives a very slight improvement
{:gen-fn (constantly nil)}))
;; 5.7 micros -- 3.7x
{})))
;; 6.8 micros -- 3x
(quick-bench
(jdbc/query {:connection con}
["select * from fruit where appearance = ?" "red"]
{:row-fn :name :result-set-fn first}))
;; simple query -- 3.1-3.2 micros
;; simple query -- 2.6 micros
(quick-bench
(execute! con ["select * from fruit where appearance = ?" "red"]))
;; 5.9 -- ~2x
;; 6.9 -- ~2.6x
(quick-bench
(jdbc/query {:connection con} ["select * from fruit where appearance = ?" "red"]))
(quick-bench ; 5.77-5.89
(quick-bench ; 4.55-4.57
(execute! con ["select * from fruit"]))
;; this is not quite equivalent
(quick-bench ; 5.34-5.4
(into [] (map (partial into {})) (reducible! con ["select * from fruit"]
{:gen-fn rs/map-row-builder})))
;; but this is (equivalent to execute!)
(quick-bench ; 5.58-5.8
(into [] (map #(rs/datafiable-row % con {})) (reducible! con ["select * from fruit"]
{:gen-fn rs/map-row-builder})))
(quick-bench ; 4.34-4.4
(execute! con ["select * from fruit"] {:gen-fn rs/as-arrays}))
(quick-bench ; 7.84-7.96 -- 1.3x
(quick-bench ; 9.5 -- 2x
(jdbc/query {:connection con} ["select * from fruit"]))
(quick-bench ; 9.4-9.7
(quick-bench ; 8.2
(with-transaction [t con {:rollback-only true}]
(execute! t ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (5,'Pear','green',49,47)"])))
(quick-bench ; 14.14-14.63
(quick-bench ; 15.7
(with-transaction [t con {:rollback-only true}]
(insert! t :fruit {:id 5, :name "Pear", :appearance "green", :cost 49, :grade 47})))
(quick-bench ; 12.9-13 -- 1.36x
(quick-bench ; 13.6 -- 1.6x
(jdbc/with-db-transaction [t {:connection con}]
(jdbc/db-set-rollback-only! t)
(jdbc/execute! t ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (5,'Pear','green',49,47)"])))
(quick-bench ; 25.52-25.74 -- 1.77x
(quick-bench ; 27.9-28.8 -- 1.8x
(jdbc/with-db-transaction [t {:connection con}]
(jdbc/db-set-rollback-only! t)
(jdbc/insert! t :fruit {:id 5, :name "Pear", :appearance "green", :cost 49, :grade 47})))
(delete! con :fruit {:id 5})
;; with a prepopulated prepared statement
;; with a prepopulated prepared statement - 450ns
(with-open [ps (prepare con ["select * from fruit where appearance = ?" "red"] {})]
(quick-bench
[(reduce (fn [_ row] (reduced (:name row)))
@ -144,26 +132,10 @@
(quick-bench
(execute-one! con ["select * from fruit where appearance = ?" "red"]))
;; test assoc works
(quick-bench
(execute-one! con
["select * from fruit where appearance = ?" "red"]
#(assoc % :test :value)
{}))
;; test assoc works
(quick-bench
(execute! con
["select * from fruit where appearance = ?" "red"]
#(assoc % :test :value)
{}))
(with-transaction [t con {:rollback-only true}]
(insert! t :fruit {:id 5, :name "Pear", :appearance "green", :cost 49, :grade 47})
(query t ["select * from fruit where name = ?" "Pear"]))
(query con ["select * from fruit where name = ?" "Pear"])
(delete! con :fruit {:id 1})
(update! con :fruit {:appearance "Brown"} {:name "Banana"})
(reduce rs/as-arrays nil (reducible! con ["select * from fruit"])))
(update! con :fruit {:appearance "Brown"} {:name "Banana"}))