Make datafy/nav implementation more robust

Also some documentation cleanup
This commit is contained in:
Sean Corfield 2019-04-18 14:06:14 -07:00
parent 8c508b8416
commit e85cbf7c95

View file

@ -61,8 +61,11 @@
"Protocol for building rows in various representations: "Protocol for building rows in various representations:
->row -- called once per row to create the basis of each row ->row -- called once per row to create the basis of each row
column-count -- return the number of columns in each row column-count -- return the number of columns in each row
with-column -- called with the index of the column to be added with-column -- called with the row and the index of the column to be added;
row! -- called once per row to finalize each row once it is complete" this is expected to read the column value from the ResultSet!
row! -- called once per row to finalize each row once it is complete
The default implementation for building hash maps: MapResultSetBuilder"
(->row [_]) (->row [_])
(column-count [_]) (column-count [_])
(with-column [_ row i]) (with-column [_ row i])
@ -71,8 +74,11 @@
(defprotocol ResultSetBuilder (defprotocol ResultSetBuilder
"Protocol for building result sets in various representations: "Protocol for building result sets in various representations:
->rs -- called to create the basis of the result set ->rs -- called to create the basis of the result set
with-row -- called with the row to be added with-row -- called with the result set and the row to be added
rs! -- called to finalize the result set once it is complete" rs! -- called to finalize the result set once it is complete
Default implementations for building vectors of hash maps and vectors
of column names and row values: MapResultSetBuilder & ArrayResultSetBuilder"
(->rs [_]) (->rs [_])
(with-row [_ rs row]) (with-row [_ rs row])
(rs! [_ rs])) (rs! [_ rs]))
@ -93,7 +99,7 @@
(rs! [this mrs] (persistent! mrs))) (rs! [this mrs] (persistent! mrs)))
(defn as-maps (defn as-maps
"Given a ResultSet and options, return a RowBuilder and ResultSetBuilder "Given a ResultSet and options, return a RowBuilder / ResultSetBuilder
that produces bare vectors of hash map rows." that produces bare vectors of hash map rows."
[^ResultSet rs opts] [^ResultSet rs opts]
(let [rsmeta (.getMetaData rs) (let [rsmeta (.getMetaData rs)
@ -101,7 +107,7 @@
(->MapResultSetBuilder rs rsmeta cols))) (->MapResultSetBuilder rs rsmeta cols)))
(defn as-unqualified-maps (defn as-unqualified-maps
"Given a ResultSet and options, return a RowBuilder and ResultSetBuilder "Given a ResultSet and options, return a RowBuilder / ResultSetBuilder
that produces bare vectors of hash map rows, with simple keys." that produces bare vectors of hash map rows, with simple keys."
[^ResultSet rs opts] [^ResultSet rs opts]
(let [rsmeta (.getMetaData rs) (let [rsmeta (.getMetaData rs)
@ -122,7 +128,7 @@
(rs! [this ars] (persistent! ars))) (rs! [this ars] (persistent! ars)))
(defn as-arrays (defn as-arrays
"Given a ResulSet and options, return a RowBuilder and ResultSetBuilder "Given a ResulSet and options, return a RowBuilder / ResultSetBuilder
that produces a vector of column names followed by vectors of row values." that produces a vector of column names followed by vectors of row values."
[^ResultSet rs opts] [^ResultSet rs opts]
(let [rsmeta (.getMetaData rs) (let [rsmeta (.getMetaData rs)
@ -130,7 +136,7 @@
(->ArrayResultSetBuilder rs rsmeta cols))) (->ArrayResultSetBuilder rs rsmeta cols)))
(defn as-unqualified-arrays (defn as-unqualified-arrays
"Given a ResulSet and options, return a RowBuilder and ResultSetBuilder "Given a ResulSet and options, return a RowBuilder / ResultSetBuilder
that produces a vector of simple column names followed by vectors of row that produces a vector of simple column names followed by vectors of row
values." values."
[^ResultSet rs opts] [^ResultSet rs opts]
@ -140,6 +146,11 @@
(declare navize-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- row-builder (defn- row-builder
"Given a RowBuilder -- a row materialization strategy -- produce a fully "Given a RowBuilder -- a row materialization strategy -- produce a fully
materialized row from it." materialized row from it."
@ -149,11 +160,6 @@
(range 1 (inc (column-count gen)))) (range 1 (inc (column-count gen))))
(row! 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."
(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
@ -162,7 +168,7 @@
Supports ILookup (keywords are treated as strings). Supports ILookup (keywords are treated as strings).
Supports Associative (again, keywords are treated as strings). If you assoc, Supports Associative (again, keywords are treated as strings). If you assoc,
a full row will be realized (via seq/into). a full row will be realized (via `row-builder` above).
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]
@ -208,12 +214,12 @@
(extend-protocol (extend-protocol
DatafiableRow DatafiableRow
java.util.Map ; assume we can "navigate" any kind of hash map for now clojure.lang.IObj ; assume we can "navigate" anything that accepts metadata
;; in reality, this is going to be over-optimistic and will like cause `nav`
;; to fail on attempts to navigate into result sets that are not hash maps
(datafiable-row [this connectable opts] (datafiable-row [this connectable opts]
(with-meta this (with-meta this
{`core-p/datafy (navize-row connectable opts)})) {`core-p/datafy (navize-row connectable opts)})))
clojure.lang.IObj ; but we cannot "navigate" other things
(datafiable-row [this connectable opts] this))
(defn- stmt->result-set (defn- stmt->result-set
"Given a PreparedStatement and options, execute it and return a ResultSet "Given a PreparedStatement and options, execute it and return a ResultSet
@ -370,27 +376,27 @@
"Given a connectable object, return a function that knows how to turn a row "Given a connectable object, return a function that knows how to turn a row
into a navigable object. into a navigable object.
A :schema option can provide a map of qualified column names (:table/column) A `:schema` option can provide a map from qualified column names
to tuples that indicate which table they are a foreign key for, the name of (`:<table>/<column>`) to tuples that indicate for which table they are a
the key within that table, and (optionality) the cardinality of that foreign key, the name of the key within that table, and (optionality) the
relationship (:many, :one). cardinality of that relationship (`:many`, `:one`).
If no :schema item is provided for a column, the convention of <table>id or If no `:schema` item is provided for a column, the convention of <table>id or
<table>_id is used, and the assumption is that such columns are foreign keys <table>_id is used, and the assumption is that such columns are foreign keys
in the <table> portion of their name, the key is called 'id', and the in the <table> portion of their name, the key is called `id`, and the
cardinality is :one. cardinality is :one.
Rows are looked up using 'execute!' or 'execute-one!' and the :table-fn Rows are looked up using `-execute-all` or `-execute-one` and the `:table-fn`
function, if provided, is applied to both the assumed table name and the option, if provided, is applied to both the assumed table name and the
assumed foreign key column name." assumed foreign key column name."
[connectable opts] [connectable opts]
(fn [row] (fn [row]
(with-meta row (with-meta row
{`core-p/nav (fn [coll k v] {`core-p/nav (fn [coll k v]
(let [[table fk cardinality] (or (get-in opts [:schema k]) (try
(default-schema k))] (let [[table fk cardinality] (or (get-in opts [:schema k])
(if fk (default-schema k))]
(try (if fk
(let [entity-fn (:table-fn opts identity) (let [entity-fn (:table-fn opts identity)
exec-fn! (if (= :many cardinality) exec-fn! (if (= :many cardinality)
p/-execute-all p/-execute-all
@ -403,8 +409,8 @@
" = ?") " = ?")
v] v]
opts)) opts))
(catch Exception _ v))
;; assume an exception means we just cannot (catch Exception _
;; navigate anywhere, so return just the value ;; assume an exception means we just cannot
v)) ;; navigate anywhere, so return just the value
v)))}))) v)))})))