First cut at RowBuilder

Temporarily disables as-arrays
This commit is contained in:
Sean Corfield 2019-04-10 23:59:19 -07:00
parent eefea54358
commit 788015909f
3 changed files with 94 additions and 59 deletions

View file

@ -148,9 +148,9 @@
([stmt] ([stmt]
(rs/execute! stmt [] (partial into {}) {})) (rs/execute! stmt [] (partial into {}) {}))
([connectable sql-params] ([connectable sql-params]
(rs/execute! connectable sql-params (rs/datafiable-row connectable {}) {})) (rs/execute! connectable sql-params #(rs/datafiable-row % connectable {}) {}))
([connectable sql-params opts] ([connectable sql-params opts]
(rs/execute! connectable sql-params (rs/datafiable-row connectable opts) opts)) (rs/execute! connectable sql-params #(rs/datafiable-row % connectable opts) opts))
([connectable sql-params f opts] ([connectable sql-params f opts]
(rs/execute! connectable sql-params f opts))) (rs/execute! connectable sql-params f opts)))
@ -167,9 +167,9 @@
([stmt] ([stmt]
(rs/execute-one! stmt [] (partial into {}) {})) (rs/execute-one! stmt [] (partial into {}) {}))
([connectable sql-params] ([connectable sql-params]
(rs/execute-one! connectable sql-params (rs/datafiable-row connectable {}) {})) (rs/execute-one! connectable sql-params #(rs/datafiable-row % connectable {}) {}))
([connectable sql-params opts] ([connectable sql-params opts]
(rs/execute-one! connectable sql-params (rs/datafiable-row connectable opts) opts)) (rs/execute-one! connectable sql-params #(rs/datafiable-row % connectable opts) opts))
([connectable sql-params f opts] ([connectable sql-params f opts]
(rs/execute-one! connectable sql-params f opts))) (rs/execute-one! connectable sql-params f opts)))
@ -240,7 +240,7 @@
opts) opts)
(rs/execute! connectable (rs/execute! connectable
sql-params sql-params
(rs/datafiable-row connectable opts) #(rs/datafiable-row % connectable opts)
opts)))) opts))))
(defn find-by-keys (defn find-by-keys
@ -258,7 +258,7 @@
opts) opts)
(rs/execute! connectable (rs/execute! connectable
(sql/for-query table key-map opts) (sql/for-query table key-map opts)
(rs/datafiable-row connectable opts) #(rs/datafiable-row % connectable opts)
opts)))) opts))))
(defn get-by-id (defn get-by-id
@ -281,7 +281,7 @@
opts) opts)
(rs/execute-one! connectable (rs/execute-one! connectable
(sql/for-query table {pk-name pk} opts) (sql/for-query table {pk-name pk} opts)
(rs/datafiable-row connectable opts) #(rs/datafiable-row % connectable opts)
opts)))) opts))))
(defn update! (defn update!

View file

@ -12,12 +12,12 @@
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
(defn- get-column-names (defn- get-column-names
"Given a ResultSet, return a vector of columns names, each qualified by "Given ResultSetMetaData, return a vector of columns names, each qualified by
the table from which it came. the table from which it came.
If :identifiers was specified, apply that to both the table qualifier If :identifiers was specified, apply that to both the table qualifier
and the column name." and the column name."
[^ResultSet rs ^ResultSetMetaData rsmeta opts] [^ResultSetMetaData rsmeta opts]
(let [idxs (range 1 (inc (.getColumnCount rsmeta)))] (let [idxs (range 1 (inc (.getColumnCount rsmeta)))]
(if-let [ident-fn (:identifiers opts)] (if-let [ident-fn (:identifiers opts)]
(mapv (fn [^Integer i] (mapv (fn [^Integer i]
@ -30,12 +30,6 @@
(.getColumnLabel rsmeta i))) (.getColumnLabel rsmeta i)))
idxs)))) idxs))))
(defprotocol ColumnarResultSet
"To allow reducing functions to access a result set's column names and
row values, such as the as-arrays reducing function in this namespace."
(column-names [this] "Return the column names from a result set.")
(row-values [this] "Return the values from the current row of a result set."))
(defprotocol ReadableColumn (defprotocol ReadableColumn
"Protocol for reading objects from the java.sql.ResultSet. Default "Protocol for reading objects from the java.sql.ResultSet. Default
implementations (for Object and nil) return the argument, and the implementations (for Object and nil) return the argument, and the
@ -59,6 +53,37 @@
(read-column-by-label [_1 _2] nil) (read-column-by-label [_1 _2] nil)
(read-column-by-index [_1 _2 _3] nil)) (read-column-by-index [_1 _2 _3] nil))
(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
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 [_])
(with-column [_ row i])
(build [_ row]))
(defn map-row-builder [^ResultSet rs opts]
(let [rsmeta (.getMetaData rs)
cols (get-column-names rsmeta opts)]
(reify
RowBuilder
(row [this] (transient {}))
(columns [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)))))
(declare navize-row)
(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."
(datafiable-row [this connectable opts]))
(defn- mapify-result-set (defn- mapify-result-set
"Given a result set, return an object that wraps the current row as a hash "Given a result set, return an object that wraps the current row as a hash
map. Note that a result set is mutable and the current row will change behind map. Note that a result set is mutable and the current row will change behind
@ -71,8 +96,12 @@
Supports Seqable which realizes a full row of the data." Supports Seqable which realizes a full row of the data."
[^ResultSet rs opts] [^ResultSet rs opts]
(let [rsmeta (.getMetaData rs) (let [gen (when-let [gen-fn (:gen-fn opts)] (gen-fn rs opts))
cols (delay (get-column-names rs rsmeta 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)))]
(reify (reify
clojure.lang.ILookup clojure.lang.ILookup
@ -100,27 +129,28 @@
(name k))) (name k)))
(catch SQLException _))) (catch SQLException _)))
(assoc [this k v] (assoc [this k v]
(assoc (into {} (seq this)) k v)) (assoc (row-builder) k v))
clojure.lang.Seqable clojure.lang.Seqable
(seq [this] (seq [this]
(seq (mapv (fn [^Integer i] (seq (row-builder)))
(clojure.lang.MapEntry. (nth @cols (dec i))
(read-column-by-index
(.getObject rs i)
rsmeta i)))
(range 1 (inc (count @cols))))))
ColumnarResultSet DatafiableRow
(column-names [this] @cols) (datafiable-row [this connectable opts]
(row-values [this] (with-meta
(mapv (fn [^Integer i] (read-column-by-index (row-builder)
(.getObject rs i) {`core-p/datafy (navize-row connectable opts)})))))
rsmeta i))
(range 1 (inc (count @cols))))))))
(defn as-arrays (extend-protocol
"A reducing function that can be used on a result set to produce an DatafiableRow
clojure.lang.IObj
(datafiable-row [this connectable opts]
(with-meta
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 array-based representation, where the first element is a vector of the
column names in the result set, and subsequent elements are vectors of column names in the result set, and subsequent elements are vectors of
the rows from the result set. the rows from the result set.
@ -128,10 +158,10 @@
It should be used with a nil initial value: It should be used with a nil initial value:
(reduce rs/as-arrays nil (reducible! con sql-params))" (reduce rs/as-arrays nil (reducible! con sql-params))"
[result rs-map] [result rs-map]
(if result (if result
(conj result (row-values rs-map)) (conj result (row-values rs-map))
(conj [(column-names rs-map)] (row-values rs-map)))) (conj [(column-names rs-map)] (row-values rs-map))))
(defn- reduce-stmt (defn- reduce-stmt
"Execute the PreparedStatement, attempt to get either its ResultSet or "Execute the PreparedStatement, attempt to get either its ResultSet or
@ -190,15 +220,6 @@
(-execute [this sql-params opts] (-execute [this sql-params opts]
(p/-execute (p/get-datasource this) sql-params opts))) (p/-execute (p/get-datasource this) sql-params opts)))
(declare navize-row)
(defn datafiable-row
"Given a connectable object, return a function that knows how to turn a row
into a datafiable object that can be 'nav'igated."
[connectable opts]
(fn [row]
(into (with-meta {} {`core-p/datafy (navize-row connectable opts)}) row)))
(defn execute! (defn execute!
"Given a connectable object and SQL and parameters, execute it and reduce it "Given a connectable object and SQL and parameters, execute it and reduce it
into a vector of processed hash maps (rows). into a vector of processed hash maps (rows).
@ -207,7 +228,9 @@
[connectable sql-params f opts] [connectable sql-params f opts]
(into [] (into []
(map f) (map f)
(p/-execute connectable sql-params opts))) (p/-execute connectable
sql-params
(update opts :gen-fn #(or % map-row-builder)))))
(defn execute-one! (defn execute-one!
"Given a connectable object and SQL and parameters, execute it and return "Given a connectable object and SQL and parameters, execute it and return
@ -217,7 +240,9 @@
[connectable sql-params f opts] [connectable sql-params f opts]
(reduce (fn [_ row] (reduced (f row))) (reduce (fn [_ row] (reduced (f row)))
nil nil
(p/-execute connectable sql-params opts))) (p/-execute connectable
sql-params
(update opts :gen-fn #(or % map-row-builder)))))
(defn- default-schema (defn- default-schema
"The default schema lookup rule for column names. "The default schema lookup rule for column names.
@ -265,7 +290,7 @@
(entity-fn (name fk)) (entity-fn (name fk))
" = ?") " = ?")
v] v]
(datafiable-row connectable opts) #(datafiable-row % connectable opts)
opts)) opts))
(catch Exception _ (catch Exception _
;; assume an exception means we just cannot ;; assume an exception means we just cannot

View file

@ -61,6 +61,12 @@
["select * from fruit where appearance = ?" "red"] ["select * from fruit where appearance = ?" "red"]
:name :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 ;; 5.7 micros -- 3.7x
(quick-bench (quick-bench
(jdbc/query {:connection con} (jdbc/query {:connection con}
@ -79,10 +85,12 @@
(execute! con ["select * from fruit"])) (execute! con ["select * from fruit"]))
;; this is not quite equivalent ;; this is not quite equivalent
(quick-bench ; 5.34-5.4 (quick-bench ; 5.34-5.4
(into [] (map (partial into {})) (reducible! con ["select * from fruit"]))) (into [] (map (partial into {})) (reducible! con ["select * from fruit"]
{:gen-fn rs/map-row-builder})))
;; but this is (equivalent to execute!) ;; but this is (equivalent to execute!)
(quick-bench ; 5.58-5.8 (quick-bench ; 5.58-5.8
(into [] (map (rs/datafiable-row con {})) (reducible! con ["select * from fruit"]))) (into [] (map #(rs/datafiable-row % con {})) (reducible! con ["select * from fruit"]
{:gen-fn rs/map-row-builder})))
(quick-bench ; 7.84-7.96 -- 1.3x (quick-bench ; 7.84-7.96 -- 1.3x
(jdbc/query {:connection con} ["select * from fruit"])) (jdbc/query {:connection con} ["select * from fruit"]))
@ -137,16 +145,18 @@
(execute-one! con ["select * from fruit where appearance = ?" "red"])) (execute-one! con ["select * from fruit where appearance = ?" "red"]))
;; test assoc works ;; test assoc works
(execute-one! con (quick-bench
["select * from fruit where appearance = ?" "red"] (execute-one! con
#(assoc % :test :value) ["select * from fruit where appearance = ?" "red"]
{}) #(assoc % :test :value)
{}))
;; test assoc works ;; test assoc works
(execute! con (quick-bench
["select * from fruit where appearance = ?" "red"] (execute! con
#(assoc % :test :value) ["select * from fruit where appearance = ?" "red"]
{}) #(assoc % :test :value)
{}))
(with-transaction [t con {:rollback-only true}] (with-transaction [t con {:rollback-only true}]
(insert! t :fruit {:id 5, :name "Pear", :appearance "green", :cost 49, :grade 47}) (insert! t :fruit {:id 5, :name "Pear", :appearance "green", :cost 49, :grade 47})