2019-03-31 23:54:34 +00:00
|
|
|
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
|
|
|
|
|
|
|
|
|
(ns next.jdbc.result-set
|
2019-04-18 06:43:32 +00:00
|
|
|
"An implementation of ResultSet handling functions.
|
|
|
|
|
|
|
|
|
|
Defines the following protocols:
|
|
|
|
|
* ReadableColumn -- to read column values by label or index
|
|
|
|
|
* RowBuilder -- for materializing a row
|
|
|
|
|
* ResultSetBuilder -- for materializing a result set
|
|
|
|
|
* DatafiableRow -- for turning a row into something datafiable
|
|
|
|
|
|
|
|
|
|
Also provides the default implemenations for Executable and
|
|
|
|
|
the default datafy/nav behavior for rows from a result set."
|
2019-03-31 23:54:34 +00:00
|
|
|
(:require [clojure.core.protocols :as core-p]
|
|
|
|
|
[next.jdbc.prepare :as prepare]
|
|
|
|
|
[next.jdbc.protocols :as p])
|
|
|
|
|
(:import (java.sql PreparedStatement
|
|
|
|
|
ResultSet ResultSetMetaData
|
|
|
|
|
SQLException)))
|
|
|
|
|
|
|
|
|
|
(set! *warn-on-reflection* true)
|
|
|
|
|
|
2019-04-18 06:43:32 +00:00
|
|
|
(defn get-column-names
|
2019-04-18 15:12:12 +00:00
|
|
|
"Given ResultSetMetaData, return a vector of column names, each qualified by
|
|
|
|
|
the table from which it came."
|
|
|
|
|
[^ResultSetMetaData rsmeta opts]
|
|
|
|
|
(mapv (fn [^Integer i] (keyword (not-empty (.getTableName rsmeta i))
|
|
|
|
|
(.getColumnLabel rsmeta i)))
|
|
|
|
|
(range 1 (inc (.getColumnCount rsmeta)))))
|
2019-04-01 06:17:12 +00:00
|
|
|
|
2019-04-18 15:12:12 +00:00
|
|
|
(defn get-unqualified-column-names
|
|
|
|
|
"Given ResultSetMetaData, return a vector of unqualified column names."
|
2019-04-11 06:59:19 +00:00
|
|
|
[^ResultSetMetaData rsmeta opts]
|
2019-04-18 15:12:12 +00:00
|
|
|
(mapv (fn [^Integer i] (keyword (.getColumnLabel rsmeta i)))
|
|
|
|
|
(range 1 (inc (.getColumnCount rsmeta)))))
|
2019-03-31 23:54:34 +00:00
|
|
|
|
2019-04-02 06:32:24 +00:00
|
|
|
(defprotocol ReadableColumn
|
2019-04-02 06:25:10 +00:00
|
|
|
"Protocol for reading objects from the java.sql.ResultSet. Default
|
|
|
|
|
implementations (for Object and nil) return the argument, and the
|
|
|
|
|
Boolean implementation ensures a canonicalized true/false value,
|
|
|
|
|
but it can be extended to provide custom behavior for special types."
|
|
|
|
|
(read-column-by-label [val label]
|
|
|
|
|
"Function for transforming values after reading them via a column label.")
|
|
|
|
|
(read-column-by-index [val rsmeta idx]
|
|
|
|
|
"Function for transforming values after reading them via a column index."))
|
|
|
|
|
|
2019-04-02 06:32:24 +00:00
|
|
|
(extend-protocol ReadableColumn
|
2019-04-02 06:25:10 +00:00
|
|
|
Object
|
|
|
|
|
(read-column-by-label [x _] x)
|
|
|
|
|
(read-column-by-index [x _2 _3] x)
|
|
|
|
|
|
|
|
|
|
Boolean
|
|
|
|
|
(read-column-by-label [x _] (if (= true x) true false))
|
|
|
|
|
(read-column-by-index [x _2 _3] (if (= true x) true false))
|
|
|
|
|
|
|
|
|
|
nil
|
|
|
|
|
(read-column-by-label [_1 _2] nil)
|
|
|
|
|
(read-column-by-index [_1 _2 _3] nil))
|
2019-04-02 05:19:02 +00:00
|
|
|
|
2019-04-11 06:59:19 +00:00
|
|
|
(defprotocol RowBuilder
|
|
|
|
|
"Protocol for building rows in various representations:
|
2019-04-18 06:34:31 +00:00
|
|
|
->row -- called once per row to create the basis of each row
|
|
|
|
|
column-count -- return the number of columns in each row
|
2019-04-18 21:06:14 +00:00
|
|
|
with-column -- called with the row and the index of the column to be added;
|
|
|
|
|
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"
|
2019-04-18 06:34:31 +00:00
|
|
|
(->row [_])
|
|
|
|
|
(column-count [_])
|
2019-04-11 06:59:19 +00:00
|
|
|
(with-column [_ row i])
|
2019-04-18 06:34:31 +00:00
|
|
|
(row! [_ row]))
|
|
|
|
|
|
|
|
|
|
(defprotocol ResultSetBuilder
|
|
|
|
|
"Protocol for building result sets in various representations:
|
|
|
|
|
->rs -- called to create the basis of the result set
|
2019-04-18 21:06:14 +00:00
|
|
|
with-row -- called with the result set and the row to be added
|
|
|
|
|
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"
|
2019-04-18 06:34:31 +00:00
|
|
|
(->rs [_])
|
|
|
|
|
(with-row [_ rs row])
|
|
|
|
|
(rs! [_ rs]))
|
|
|
|
|
|
2019-04-18 15:12:12 +00:00
|
|
|
(defrecord MapResultSetBuilder [^ResultSet rs rsmeta cols]
|
|
|
|
|
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 mrs row]
|
|
|
|
|
(conj! mrs row))
|
|
|
|
|
(rs! [this mrs] (persistent! mrs)))
|
|
|
|
|
|
|
|
|
|
(defn as-maps
|
2019-04-18 21:06:14 +00:00
|
|
|
"Given a ResultSet and options, return a RowBuilder / ResultSetBuilder
|
2019-04-18 15:12:12 +00:00
|
|
|
that produces bare vectors of hash map rows."
|
2019-04-18 06:34:31 +00:00
|
|
|
[^ResultSet rs opts]
|
|
|
|
|
(let [rsmeta (.getMetaData rs)
|
|
|
|
|
cols (get-column-names rsmeta opts)]
|
2019-04-18 15:12:12 +00:00
|
|
|
(->MapResultSetBuilder rs rsmeta cols)))
|
|
|
|
|
|
|
|
|
|
(defn as-unqualified-maps
|
2019-04-18 21:06:14 +00:00
|
|
|
"Given a ResultSet and options, return a RowBuilder / ResultSetBuilder
|
2019-04-18 15:12:12 +00:00
|
|
|
that produces bare vectors of hash map rows, with simple keys."
|
2019-04-18 06:34:31 +00:00
|
|
|
[^ResultSet rs opts]
|
2019-04-11 06:59:19 +00:00
|
|
|
(let [rsmeta (.getMetaData rs)
|
2019-04-18 15:12:12 +00:00
|
|
|
cols (get-unqualified-column-names rsmeta opts)]
|
|
|
|
|
(->MapResultSetBuilder rs rsmeta cols)))
|
|
|
|
|
|
|
|
|
|
(defrecord ArrayResultSetBuilder [^ResultSet rs rsmeta cols]
|
|
|
|
|
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 ars row]
|
|
|
|
|
(conj! ars row))
|
|
|
|
|
(rs! [this ars] (persistent! ars)))
|
2019-04-18 06:34:31 +00:00
|
|
|
|
|
|
|
|
(defn as-arrays
|
2019-04-18 21:09:07 +00:00
|
|
|
"Given a ResultSet and options, return a RowBuilder / ResultSetBuilder
|
2019-04-18 06:34:31 +00:00
|
|
|
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)]
|
2019-04-18 15:12:12 +00:00
|
|
|
(->ArrayResultSetBuilder rs rsmeta cols)))
|
|
|
|
|
|
|
|
|
|
(defn as-unqualified-arrays
|
2019-04-18 21:09:07 +00:00
|
|
|
"Given a ResultSet and options, return a RowBuilder / ResultSetBuilder
|
2019-04-18 15:12:12 +00:00
|
|
|
that produces a vector of simple column names followed by vectors of row
|
|
|
|
|
values."
|
|
|
|
|
[^ResultSet rs opts]
|
|
|
|
|
(let [rsmeta (.getMetaData rs)
|
|
|
|
|
cols (get-unqualified-column-names rsmeta opts)]
|
|
|
|
|
(->ArrayResultSetBuilder rs rsmeta cols)))
|
2019-04-11 06:59:19 +00:00
|
|
|
|
|
|
|
|
(declare navize-row)
|
|
|
|
|
|
2019-04-18 21:06:14 +00:00
|
|
|
(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]))
|
|
|
|
|
|
2019-04-18 06:34:31 +00:00
|
|
|
(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)))
|
|
|
|
|
|
2019-03-31 23:54:34 +00:00
|
|
|
(defn- mapify-result-set
|
|
|
|
|
"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
|
|
|
|
|
this wrapper so operations need to be eager (and fairly limited).
|
|
|
|
|
|
2019-04-20 04:53:55 +00:00
|
|
|
In particular, this does not satisfy `map?` because it does not implement
|
|
|
|
|
all of IPersistentMap.
|
|
|
|
|
|
2019-03-31 23:54:34 +00:00
|
|
|
Supports ILookup (keywords are treated as strings).
|
|
|
|
|
|
|
|
|
|
Supports Associative (again, keywords are treated as strings). If you assoc,
|
2019-04-18 21:06:14 +00:00
|
|
|
a full row will be realized (via `row-builder` above).
|
2019-03-31 23:54:34 +00:00
|
|
|
|
2019-04-20 04:53:55 +00:00
|
|
|
Supports Seqable which realizes a full row of the data.
|
|
|
|
|
|
|
|
|
|
Supports DatafiableRow (which realizes a full row of the data)."
|
2019-03-31 23:54:34 +00:00
|
|
|
[^ResultSet rs opts]
|
2019-04-20 04:53:55 +00:00
|
|
|
(let [gen (delay ((get opts :gen-fn as-maps) rs opts))]
|
2019-03-31 23:54:34 +00:00
|
|
|
(reify
|
|
|
|
|
|
|
|
|
|
clojure.lang.ILookup
|
|
|
|
|
(valAt [this k]
|
|
|
|
|
(try
|
2019-04-02 06:25:10 +00:00
|
|
|
(read-column-by-label (.getObject rs (name k)) (name k))
|
2019-03-31 23:54:34 +00:00
|
|
|
(catch SQLException _)))
|
|
|
|
|
(valAt [this k not-found]
|
|
|
|
|
(try
|
2019-04-02 06:25:10 +00:00
|
|
|
(read-column-by-label (.getObject rs (name k)) (name k))
|
2019-03-31 23:54:34 +00:00
|
|
|
(catch SQLException _
|
|
|
|
|
not-found)))
|
|
|
|
|
|
|
|
|
|
clojure.lang.Associative
|
|
|
|
|
(containsKey [this k]
|
|
|
|
|
(try
|
|
|
|
|
(.getObject rs (name k))
|
|
|
|
|
true
|
|
|
|
|
(catch SQLException _
|
|
|
|
|
false)))
|
|
|
|
|
(entryAt [this k]
|
|
|
|
|
(try
|
2019-04-02 06:25:10 +00:00
|
|
|
(clojure.lang.MapEntry. k (read-column-by-label
|
|
|
|
|
(.getObject rs (name k))
|
|
|
|
|
(name k)))
|
2019-03-31 23:54:34 +00:00
|
|
|
(catch SQLException _)))
|
|
|
|
|
(assoc [this k v]
|
2019-04-18 06:34:31 +00:00
|
|
|
(assoc (row-builder @gen) k v))
|
2019-03-31 23:54:34 +00:00
|
|
|
|
|
|
|
|
clojure.lang.Seqable
|
|
|
|
|
(seq [this]
|
2019-04-18 06:34:31 +00:00
|
|
|
(seq (row-builder @gen)))
|
2019-04-11 06:59:19 +00:00
|
|
|
|
|
|
|
|
DatafiableRow
|
|
|
|
|
(datafiable-row [this connectable opts]
|
|
|
|
|
(with-meta
|
2019-04-18 06:34:31 +00:00
|
|
|
(row-builder @gen)
|
2019-04-11 06:59:19 +00:00
|
|
|
{`core-p/datafy (navize-row connectable opts)})))))
|
|
|
|
|
|
|
|
|
|
(extend-protocol
|
|
|
|
|
DatafiableRow
|
2019-04-18 21:06:14 +00:00
|
|
|
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
|
2019-04-11 06:59:19 +00:00
|
|
|
(datafiable-row [this connectable opts]
|
2019-04-18 15:12:12 +00:00
|
|
|
(with-meta this
|
2019-04-18 21:06:14 +00:00
|
|
|
{`core-p/datafy (navize-row connectable opts)})))
|
2019-04-11 06:59:19 +00:00
|
|
|
|
2019-04-18 06:34:31 +00:00
|
|
|
(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 _)))))
|
2019-03-31 23:54:34 +00:00
|
|
|
|
|
|
|
|
(defn- reduce-stmt
|
2019-04-01 06:17:12 +00:00
|
|
|
"Execute the PreparedStatement, attempt to get either its ResultSet or
|
|
|
|
|
its generated keys (as a ResultSet), and reduce that using the supplied
|
|
|
|
|
function and initial value.
|
|
|
|
|
|
|
|
|
|
If the statement yields neither a ResultSet nor generated keys, return
|
2019-04-05 03:25:20 +00:00
|
|
|
a hash map containing :next.jdbc/update-count and the number of rows
|
|
|
|
|
updated, with the supplied function and initial value applied."
|
2019-03-31 23:54:34 +00:00
|
|
|
[^PreparedStatement stmt f init opts]
|
2019-04-18 06:34:31 +00:00
|
|
|
(if-let [rs (stmt->result-set stmt opts)]
|
2019-03-31 23:54:34 +00:00
|
|
|
(let [rs-map (mapify-result-set rs opts)]
|
|
|
|
|
(loop [init' init]
|
|
|
|
|
(if (.next rs)
|
|
|
|
|
(let [result (f init' rs-map)]
|
|
|
|
|
(if (reduced? result)
|
|
|
|
|
@result
|
|
|
|
|
(recur result)))
|
|
|
|
|
init')))
|
2019-04-05 03:25:20 +00:00
|
|
|
(f init {:next.jdbc/update-count (.getUpdateCount stmt)})))
|
2019-03-31 23:54:34 +00:00
|
|
|
|
|
|
|
|
(extend-protocol p/Executable
|
|
|
|
|
java.sql.Connection
|
|
|
|
|
(-execute [this sql-params opts]
|
2019-04-11 04:46:38 +00:00
|
|
|
(reify clojure.lang.IReduceInit
|
|
|
|
|
(reduce [_ f init]
|
|
|
|
|
(with-open [stmt (prepare/create this
|
|
|
|
|
(first sql-params)
|
|
|
|
|
(rest sql-params)
|
|
|
|
|
opts)]
|
|
|
|
|
(reduce-stmt stmt f init opts)))))
|
2019-04-18 06:34:31 +00:00
|
|
|
(-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)]
|
2019-04-18 15:12:12 +00:00
|
|
|
(let [gen-fn (get opts :gen-fn as-maps)
|
2019-04-18 07:28:23 +00:00
|
|
|
gen (gen-fn rs opts)]
|
|
|
|
|
(when (.next rs)
|
|
|
|
|
(datafiable-row (row-builder gen) this opts)))
|
2019-04-18 06:34:31 +00:00
|
|
|
{: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)]
|
2019-04-18 15:12:12 +00:00
|
|
|
(let [gen-fn (get opts :gen-fn as-maps)
|
2019-04-18 06:34:31 +00:00
|
|
|
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'))))
|
2019-04-20 05:50:10 +00:00
|
|
|
[{:next.jdbc/update-count (.getUpdateCount stmt)}])))
|
2019-04-18 06:34:31 +00:00
|
|
|
|
2019-04-11 04:46:38 +00:00
|
|
|
javax.sql.DataSource
|
|
|
|
|
(-execute [this sql-params opts]
|
|
|
|
|
(reify clojure.lang.IReduceInit
|
|
|
|
|
(reduce [_ f init]
|
|
|
|
|
(with-open [con (p/get-connection this opts)]
|
|
|
|
|
(with-open [stmt (prepare/create con
|
2019-04-01 00:30:10 +00:00
|
|
|
(first sql-params)
|
|
|
|
|
(rest sql-params)
|
2019-04-11 04:46:38 +00:00
|
|
|
opts)]
|
2019-03-31 23:54:34 +00:00
|
|
|
(reduce-stmt stmt f init opts))))))
|
2019-04-18 06:34:31 +00:00
|
|
|
(-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)]
|
2019-04-19 06:03:09 +00:00
|
|
|
(let [gen-fn (get opts :gen-fn as-maps)
|
|
|
|
|
gen (gen-fn rs opts)]
|
|
|
|
|
(when (.next rs)
|
|
|
|
|
(datafiable-row (row-builder gen) this opts)))
|
2019-04-18 06:34:31 +00:00
|
|
|
{: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)]
|
2019-04-18 15:12:12 +00:00
|
|
|
(let [gen-fn (get opts :gen-fn as-maps)
|
2019-04-18 06:34:31 +00:00
|
|
|
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'))))
|
2019-04-20 05:50:10 +00:00
|
|
|
[{:next.jdbc/update-count (.getUpdateCount stmt)}]))))
|
2019-04-18 06:34:31 +00:00
|
|
|
|
2019-03-31 23:54:34 +00:00
|
|
|
java.sql.PreparedStatement
|
2019-04-18 06:34:31 +00:00
|
|
|
;; 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
|
2019-03-31 23:54:34 +00:00
|
|
|
(-execute [this _ opts]
|
|
|
|
|
(reify clojure.lang.IReduceInit
|
|
|
|
|
(reduce [_ f init]
|
|
|
|
|
(reduce-stmt this f init (assoc opts :return-keys true)))))
|
2019-04-18 06:34:31 +00:00
|
|
|
(-execute-one [this _ opts]
|
|
|
|
|
(if-let [rs (stmt->result-set this (assoc opts :return-keys true))]
|
|
|
|
|
(when (.next rs)
|
2019-04-18 15:12:12 +00:00
|
|
|
(datafiable-row (row-builder (as-maps rs opts))
|
2019-04-18 06:34:31 +00:00
|
|
|
(.getConnection this) opts))
|
|
|
|
|
{:next.jdbc/update-count (.getUpdateCount this)}))
|
|
|
|
|
(-execute-all [this sql-params opts]
|
|
|
|
|
(if-let [rs (stmt->result-set this opts)]
|
2019-04-18 15:12:12 +00:00
|
|
|
(let [gen-fn (get opts :gen-fn as-maps)
|
2019-04-18 06:34:31 +00:00
|
|
|
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'))))
|
2019-04-20 05:50:10 +00:00
|
|
|
[{:next.jdbc/update-count (.getUpdateCount this)}]))
|
2019-04-18 06:34:31 +00:00
|
|
|
|
2019-03-31 23:54:34 +00:00
|
|
|
Object
|
|
|
|
|
(-execute [this sql-params opts]
|
2019-04-18 06:34:31 +00:00
|
|
|
(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)))
|
2019-03-31 23:54:34 +00:00
|
|
|
|
|
|
|
|
(defn- default-schema
|
|
|
|
|
"The default schema lookup rule for column names.
|
|
|
|
|
|
|
|
|
|
If a column name ends with _id or id, it is assumed to be a foreign key
|
|
|
|
|
into the table identified by the first part of the column name."
|
|
|
|
|
[col]
|
2019-04-19 05:43:19 +00:00
|
|
|
(let [[_ table] (re-find #"(?i)^(.+?)_?id$" (name col))]
|
2019-03-31 23:54:34 +00:00
|
|
|
(when table
|
|
|
|
|
[(keyword table) :id])))
|
|
|
|
|
|
|
|
|
|
(defn- navize-row
|
2019-04-01 06:17:12 +00:00
|
|
|
"Given a connectable object, return a function that knows how to turn a row
|
|
|
|
|
into a navigable object.
|
|
|
|
|
|
2019-04-18 21:06:14 +00:00
|
|
|
A `:schema` option can provide a map from qualified column names
|
|
|
|
|
(`:<table>/<column>`) to tuples that indicate for which table they are a
|
|
|
|
|
foreign key, the name of the key within that table, and (optionality) the
|
|
|
|
|
cardinality of that relationship (`:many`, `:one`).
|
2019-04-01 06:17:12 +00:00
|
|
|
|
2019-04-18 21:06:14 +00:00
|
|
|
If no `:schema` item is provided for a column, the convention of <table>id or
|
2019-04-01 06:17:12 +00:00
|
|
|
<table>_id is used, and the assumption is that such columns are foreign keys
|
2019-04-18 21:06:14 +00:00
|
|
|
in the <table> portion of their name, the key is called `id`, and the
|
2019-04-01 06:17:12 +00:00
|
|
|
cardinality is :one.
|
|
|
|
|
|
2019-04-18 21:06:14 +00:00
|
|
|
Rows are looked up using `-execute-all` or `-execute-one` and the `:table-fn`
|
|
|
|
|
option, if provided, is applied to both the assumed table name and the
|
2019-04-01 06:17:12 +00:00
|
|
|
assumed foreign key column name."
|
2019-03-31 23:54:34 +00:00
|
|
|
[connectable opts]
|
|
|
|
|
(fn [row]
|
|
|
|
|
(with-meta row
|
|
|
|
|
{`core-p/nav (fn [coll k v]
|
2019-04-18 21:06:14 +00:00
|
|
|
(try
|
|
|
|
|
(let [[table fk cardinality] (or (get-in opts [:schema k])
|
|
|
|
|
(default-schema k))]
|
|
|
|
|
(if fk
|
2019-04-18 15:12:12 +00:00
|
|
|
(let [entity-fn (:table-fn opts identity)
|
2019-03-31 23:54:34 +00:00
|
|
|
exec-fn! (if (= :many cardinality)
|
2019-04-18 06:56:44 +00:00
|
|
|
p/-execute-all
|
|
|
|
|
p/-execute-one)]
|
2019-03-31 23:54:34 +00:00
|
|
|
(exec-fn! connectable
|
|
|
|
|
[(str "SELECT * FROM "
|
|
|
|
|
(entity-fn (name table))
|
|
|
|
|
" WHERE "
|
|
|
|
|
(entity-fn (name fk))
|
|
|
|
|
" = ?")
|
|
|
|
|
v]
|
|
|
|
|
opts))
|
2019-04-18 21:06:14 +00:00
|
|
|
v))
|
|
|
|
|
(catch Exception _
|
|
|
|
|
;; assume an exception means we just cannot
|
|
|
|
|
;; navigate anywhere, so return just the value
|
2019-03-31 23:54:34 +00:00
|
|
|
v)))})))
|