From 2c018654d3323314432ebf27c6672f7461c0e161 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Mon, 22 Apr 2019 17:41:31 -0700 Subject: [PATCH] Alpha 9; fixes #14; improves protocol docstrings * Move documentation from `ns` into `defprotocol` and the method declarations. * Indicate which protocols may be extended via metadata (`SettableParameter` and `Sourceable`). --- CHANGELOG.md | 3 +- doc/getting-started.md | 6 +- doc/migration-from-clojure-java-jdbc.md | 2 +- pom.xml | 2 +- src/next/jdbc/prepare.clj | 5 +- src/next/jdbc/protocols.clj | 98 +++++++++++++------------ src/next/jdbc/result_set.clj | 60 ++++++++------- 7 files changed, 95 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cef448a..dcd5c62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ # Change Log -2019-04-21 -- 1.0.0-alpha8 -- Initial publicly announced release. +* 2019-04-22 -- 1.0.0-alpha9 -- Fix #14 by respecting `:gen-fn` in `execute-one` for `PreparedStatement`. +* 2019-04-21 -- 1.0.0-alpha8 -- Initial publicly announced release. diff --git a/doc/getting-started.md b/doc/getting-started.md index 0d46134..e9714d6 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -9,12 +9,12 @@ It is designed to work with Clojure 1.10 or later, supports `datafy`/`nav`, and You can add `next.jdbc` to your project with either: ```clojure -{seancorfield/next.jdbc {:mvn/version "1.0.0-alpha8"}} +{seancorfield/next.jdbc {:mvn/version "1.0.0-alpha9"}} ``` for `deps.edn` or: ```clojure -[seancorfield/next.jdbc "1.0.0-alpha8"] +[seancorfield/next.jdbc "1.0.0-alpha9"] ``` for `project.clj` or `build.boot`. @@ -29,7 +29,7 @@ For the examples in this documentation, we will use a local H2 database on disk, ```clojure ;; deps.edn {:deps {org.clojure/clojure {:mvn/version "1.10.0"} - seancorfield/next.jdbc {:mvn/version "1.0.0-alpha8"} + seancorfield/next.jdbc {:mvn/version "1.0.0-alpha9"} com.h2database/h2 {:mvn/version "1.4.197"}}} ``` diff --git a/doc/migration-from-clojure-java-jdbc.md b/doc/migration-from-clojure-java-jdbc.md index 8103e9a..801de05 100644 --- a/doc/migration-from-clojure-java-jdbc.md +++ b/doc/migration-from-clojure-java-jdbc.md @@ -4,7 +4,7 @@ This page attempts to list all of the differences between [clojure.java.jdbc](ht ## Conceptually -`clojure.java.jdbc` focuses heavily on a `db-spec` hash map to describe the various ways of interacting with the database and grew from very imperative origins that expose a lot of the JDBC API (multiple type of SQL execution, some operations returned hash maps, others update counts as integers, etc). +`clojure.java.jdbc` focuses heavily on a `db-spec` hash map to describe the various ways of interacting with the database and grew from very imperative origins that expose a lot of the JDBC API (multiple types of SQL execution, some operations returned hash maps, others update counts as integers, etc). `next.jdbc` focuses on using protocols and native Java JDBC types where possible (for performance and simplicity) and strives to present a more modern Clojure API with namespace-qualified keywords in hash maps, reducible SQL operations as part of the primary API, and a streamlined set of SQL execution primitives. Execution always returns a hash map (for one result) or a vector of hash maps (for multiple results) -- even update counts are returned as if they were result sets. diff --git a/pom.xml b/pom.xml index 471a56a..ca6f799 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 seancorfield next.jdbc - 1.0.0-alpha8 + 1.0.0-alpha9 next.jdbc The next generation of clojure.java.jdbc: a new low-level Clojure wrapper for JDBC-based access to databases. diff --git a/src/next/jdbc/prepare.clj b/src/next/jdbc/prepare.clj index 7c77e84..33e5a51 100644 --- a/src/next/jdbc/prepare.clj +++ b/src/next/jdbc/prepare.clj @@ -20,8 +20,9 @@ (defprotocol SettableParameter :extend-via-metadata true "Protocol for setting SQL parameters in statement objects, which can convert from Clojure values. The default implementation just - calls `.setObject` on the parameter value. It can be extended to use other - methods of `PreparedStatement` to convert and set parameter values." + calls `.setObject` on the parameter value. It can be extended to + use other methods of `PreparedStatement` to convert and set parameter + values. Extension via metadata is supported." (set-parameter [val stmt ix] "Convert a Clojure value into a SQL value and store it as the ix'th parameter in the given SQL statement object.")) diff --git a/src/next/jdbc/protocols.clj b/src/next/jdbc/protocols.clj index c3ef2b5..0aa83a1 100644 --- a/src/next/jdbc/protocols.clj +++ b/src/next/jdbc/protocols.clj @@ -3,59 +3,61 @@ (ns next.jdbc.protocols "This is the extensible core of the next generation java.jdbc library. - `Sourceable` protocol: - `get-datasource` -- turn something into a `javax.sql.DataSource`; implementations - are provided for strings, hash maps (`db-spec` structures), and also a - `DataSource` (which just returns itself). - - `Connectable` protocol: - `get-connection` -- create a new JDBC connection that should be closed when you - are finished with it; implementations are provided for `DataSource`, - `PreparedStatement`, and `Object`, on the assumption that an `Object` - can possibly be turned into a `DataSource`. - - `Executable` protocol: - `-execute` -- given SQL and parameters, produce a 'reducible' that, when - reduced, executes the SQL and produces a `ResultSet` that can be processed; - 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-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`). - - `Preparable` protocol: - `prepare` -- given SQL and parameters, produce a `PreparedStatement` that can - be executed (by -execute above); implementation is provided for - `Connection` only. - - `Transactable` protocol: - `-transact` -- given a function (presumably containing SQL operations), - run the function inside a SQL transaction; implementations are provided - for `Connection`, `DataSource`, and `Object` (on the assumption that an - `Object` can be turned into a `DataSource`).") + `Sourceable` -- for producing `javax.sql.DataSource` objects, + `Connectable` -- for producing new `java.sql.Connection` objects, + `Executable` -- for executing SQL operations, + `Preparable` -- for producing new `java.sql.PreparedStatement` objects, + `Transactable` -- for executing SQL operations transactionally.") (set! *warn-on-reflection* true) (defprotocol Sourceable :extend-via-metadata true - (get-datasource ^javax.sql.DataSource [this])) + "Protocol for producing a `javax.sql.DataSource`. + + Implementations are provided for strings, hash maps (`db-spec` structures), + and also a `DataSource` (which just returns itself). + + Extension via metadata is supported." + (get-datasource ^javax.sql.DataSource [this] + "Produce a `javax.sql.DataSource`.")) + (defprotocol Connectable - (get-connection ^java.lang.AutoCloseable [this opts])) + "Protocol for producing a new JDBC connection that should be closed when you + are finished with it. + + Implementations are provided for `DataSource`, `PreparedStatement`, and + `Object`, on the assumption that an `Object` can be turned into a `DataSource`." + (get-connection ^java.lang.AutoCloseable [this opts] + "Produce a new `java.sql.Connection` for use with `with-open`.")) + (defprotocol Executable - (-execute ^clojure.lang.IReduceInit [this sql-params opts]) - (-execute-one [this sql-params opts]) - (-execute-all [this sql-params opts])) + "Protocol for executing SQL operations. + + 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 ^clojure.lang.IReduceInit [this sql-params opts] + "Produce a 'reducible' that, when reduced, executes the SQL and + processes the rows of the `ResultSet` directly.") + (-execute-one [this sql-params opts] + "Executes the SQL and produces the first row of the `ResultSet` + as a fully-realized, datafiable hash map (by default).") + (-execute-all [this sql-params opts] + "Executes the SQL and produces (by default) a vector of + fully-realized, datafiable hash maps from the `ResultSet`.")) + (defprotocol Preparable - (prepare ^java.sql.PreparedStatement [this sql-params opts])) + "Protocol for producing a new `java.sql.PreparedStatement` that should + be closed after use. Can be used by `Executable` functions. + + Implementation is provided for `Connection` only." + (prepare ^java.sql.PreparedStatement [this sql-params opts] + "Produce a new `java.sql.PreparedStatement` for use with `with-open`.")) + (defprotocol Transactable - (-transact [this body-fn opts])) + "Protocol for running SQL operations in a transaction. + + Implementations are provided for `Connection`, `DataSource`, and `Object` + (on the assumption that an `Object` can be turned into a `DataSource`)." + (-transact [this body-fn opts] + "Run the `body-fn` inside a transaction.")) diff --git a/src/next/jdbc/result_set.clj b/src/next/jdbc/result_set.clj index e47febf..6fa2886 100644 --- a/src/next/jdbc/result_set.clj +++ b/src/next/jdbc/result_set.clj @@ -76,30 +76,30 @@ (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 - `column-count` -- return the number of columns in each row - `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 + "Protocol for building rows in various representations. The default implementation for building hash maps: `MapResultSetBuilder`" - (->row [_]) - (column-count [_]) - (with-column [_ row i]) - (row! [_ 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 [_ row i] + "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! [_ row] + "Called once per row to finalize each row once it is complete.")) (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 result set and the row to be added - `rs!` -- called to finalize the result set once it is complete + "Protocol for building result sets in various representations. - Default implementations for building vectors of hash maps and vectors - of column names and row values: `MapResultSetBuilder` & `ArrayResultSetBuilder`" - (->rs [_]) - (with-row [_ rs row]) - (rs! [_ rs])) + Default implementations for building vectors of hash maps and vectors of + column names and row values: `MapResultSetBuilder` & `ArrayResultSetBuilder`" + (->rs [_] + "Called to create the basis of the result set.") + (with-row [_ rs row] + "Called with the result set and the row to be added.") + (rs! [_ rs] + "Called to finalize the result set once it is complete.")) (defrecord MapResultSetBuilder [^ResultSet rs rsmeta cols] RowBuilder @@ -199,9 +199,17 @@ (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])) + "Protocol for making rows datafiable and therefore navigable. + + The default implementation just adds metadata so that `datafy` can be + called on the row, which will produce something that `nav` can be called + on, to lazily navigate through foreign key relationships into other tables. + + If `datafiable-row` is called when reducing the result set produced by + `next.jdbc/reducible!`, the row is fully-realized from the `ResultSet` + first." + (datafiable-row [this connectable opts] + "Produce a datafiable representation of a row from a `ResultSet`.")) (defn- row-builder "Given a `RowBuilder` -- a row materialization strategy -- produce a fully @@ -396,9 +404,11 @@ (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 (as-maps rs opts)) - (.getConnection this) opts)) + (let [gen-fn (get opts :gen-fn as-maps) + gen (gen-fn rs opts)] + (when (.next rs) + (datafiable-row (row-builder gen) + (.getConnection this) opts))) {:next.jdbc/update-count (.getUpdateCount this)})) (-execute-all [this _ opts] (if-let [rs (stmt->result-set this opts)]