From bb6eb02cbcfc39cf948230e446e0dfb8003b4d4d Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 17 Nov 2019 12:30:32 -0800 Subject: [PATCH] Provide a way to force builder construction in plan This still won't catch the empty result set or lack of result set cases (e.g., update count only). --- CHANGELOG.md | 1 + doc/middleware.md | 22 ++++++++++++++++++++++ src/next/jdbc/result_set.clj | 5 ++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c07721b..e9f76c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Only accretive/fixative changes will be made from now on. The following changes have been committed to the **master** branch since the 1.0.10 release: * Add `next.jdbc.middleware` containing a `wrapper` for connectable objects that can offer default options, as well as four "hooks" for pre- and post-processing functions that make it easier to add logging and timing code to your `next.jdbc`-based application. +* Make the "mapified" result set object implement `clojure.lang.IDeref` so you can "force" the result set builder to be constructed so that the `:post-execute-fn` hook will run in middleware. * Add testing against Microsoft SQL Server (run tests with environment variables `NEXT_JDBC_TEST_MSSQL=yes` and `MSSQL_SA_PASSWORD` set to your local -- `127.0.0.1:1433` -- SQL Server `sa` user password; assumes that it can create and drop `fruit` and `fruit_time` tables in the `model` database). * Add testing against MySQL (run tests with environment variables `NEXT_JDBC_TEST_MYSQL=yes` and `MYSQL_ROOT_PASSWORD` set to your local -- `127.0.0.1:3306` -- MySQL `root` user password; assumes you have already created an empty database called `clojure_test`). * Bump several JDBC driver versions for up-to-date testing. diff --git a/doc/middleware.md b/doc/middleware.md index fc66895..e363edf 100644 --- a/doc/middleware.md +++ b/doc/middleware.md @@ -63,6 +63,28 @@ As you can see, both `:pre-process-fn` and `:post-process-fn` can return updated Any of the hook functions may execute side-effects (such as logging) but must still return the expected data. +## Middleware and `plan` + +Because `next.jdbc/plan` tries to avoid realizing a result set, it is possible to perform reductions that do not even cause the result set builder to be constructed -- the `:post-execute-fn` hook will not executed in such cases. For example: + +```clojure +user=> (into [] (map :name) ; does not construct the builder! + (jdbc/plan db-spec ["select * from fruit"])) +["Apple" "Banana" "Peach" "Orange"] +``` + +You can force the result set builder to be constructed by calling `deref` on any row: + +```clojure +user=> (into [] (map (comp :name deref)) + (jdbc/plan db-spec ["select * from fruit"])) +["Apple" "Banana" "Peach" "Orange"] +``` + +That will ensure that the result set builder _is_ constructed and it will execute the `:post-execute-fn` hook, but it will not cause rows (or the overall result set) to be realized. Thus, the only overhead of calling `deref` on each row is the one-off cost of constructing the result set builder for the first row, and the cost of derefencing a realized delay object for each row (and throwing that value away). + +*Note: If your SQL operation produces no rows, or no `ResultSet` at all (only an update count), then there is no way to force the result set builder to be constructed and no way for the `:post-execute-fn` hook to be executed.* + ## Examples of Middleware Usage The usage for providing default options should be clear from the overview above diff --git a/src/next/jdbc/result_set.clj b/src/next/jdbc/result_set.clj index 06c6371..85c6aed 100644 --- a/src/next/jdbc/result_set.clj +++ b/src/next/jdbc/result_set.clj @@ -462,7 +462,10 @@ clojure.lang.IDeref (deref [this] - (deref builder)) + ;; force the builder to be created but return the row + ;; without actually building anything + (deref builder) + this) ;; from java.lang.Object: (toString [_]