From dd70625e8f5a048822db4e62131bc9fb469002e9 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 1 Nov 2019 09:34:14 -0700 Subject: [PATCH] Clarify behavior when no rows match a query --- CHANGELOG.md | 1 + doc/friendly-sql-functions.md | 4 ++++ doc/getting-started.md | 6 +++++- test/next/jdbc/sql_test.clj | 4 ++++ test/next/jdbc_test.clj | 8 ++++++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2eea21..e171e66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The following changes have been committed to the **master** branch since the 1.0 * Fix link to **All The Options** in **Migration from `clojure.java.jdbc`**. PR #71 (@laurio). * Address #70 by adding **CLOB & BLOB SQL Types** to the **Tips & Tricks** section of **Friendly SQL Functions** and by adding `next.jdbc.result-set/clob-column-reader` and `next.jdbc.result-set/clob->string` helper to make it easier to deal with `CLOB` column data. +* Clarify what `execute!` and `execute-one!` produce when the result set is empty (`[]` and `nil` respectively, and there are now tests for this). Similarly for `find-by-keys` and `get-by-id`. * Update `org.clojure/java.data` to `"0.1.4"` (0.1.2 fixes a number of reflection warnings). ## Stable Builds diff --git a/doc/friendly-sql-functions.md b/doc/friendly-sql-functions.md index ec84785..3b3315c 100644 --- a/doc/friendly-sql-functions.md +++ b/doc/friendly-sql-functions.md @@ -124,6 +124,8 @@ Given a table name (as a keyword) and either a hash map of column names and valu "Stella" "stella@artois.beer"]) ``` +If no rows match, `find-by-keys` returns `[]`, just like `execute!`. + ## `get-by-id` Given a table name (as a keyword) and a primary key value, with an optional primary key column name, execute a query on the database: @@ -140,6 +142,8 @@ Given a table name (as a keyword) and a primary key value, with an optional prim Note that in order to override the default primary key column name (of `:id`), you need to specify both the column name and an options hash map. +If no rows match, `get-by-id` returns `nil`, just like `execute-one!`. + ## Table & Column Entity Names By default, `next.jdbc.sql` functions construct SQL strings with the entity names exactly matching the (unqualified) keywords provided. If you are trying to use a table name or column name that is a reserved name in SQL for your database, you will need to tell those functions to quote those names. diff --git a/doc/getting-started.md b/doc/getting-started.md index 86ea193..a430a82 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -72,7 +72,9 @@ If you already have a JDBC URL (string), you can use that as-is instead of the d ### `execute!` & `execute-one!` -We used `execute!` to create the `address` table, to insert a new row into it, and to query it. In all three cases, `execute!` returns a vector of hash maps with namespace-qualified keys, representing the result set from the operation, if available. When no result set is produced, `next.jdbc` returns a "result set" containing the "update count" from the operation (which is usually the number of rows affected; note that `:builder-fn` does not affect this fake "result set"). By default, H2 uses uppercase names and `next.jdbc` returns these as-is. +We used `execute!` to create the `address` table, to insert a new row into it, and to query it. In all three cases, `execute!` returns a vector of hash maps with namespace-qualified keys, representing the result set from the operation, if available. +If the result set contains no rows, `execute!` returns an empty vector `[]`. +When no result set is produced, `next.jdbc` returns a "result set" containing the "update count" from the operation (which is usually the number of rows affected; note that `:builder-fn` does not affect this fake "result set"). By default, H2 uses uppercase names and `next.jdbc` returns these as-is. If you only want a single row back -- the first row of any result set, generated keys, or update counts -- you can use `execute-one!` instead. Continuing the REPL session, we'll insert another address and ask for the generated keys to be returned, and then we'll query for a single row: @@ -88,6 +90,8 @@ user=> ``` Since we used `execute-one!`, we get just one row back (a hash map). This also shows how you provide parameters to SQL statements -- with `?` in the SQL and then the corresponding parameter values in the vector after the SQL string. +If the result set contains no rows, `execute-one!` returns `nil`. +When no result is produced, and `next.jdbc` returns a fake "result set" containing the "update count", `execute-one!` returns just a single hash map with the key `next.jdbc/update-count` and the number of rows updated. > Note: In general, you should use `execute-one!` for DDL operations since you will only get back an update count. If you have a SQL statement that you know will only return an update count, `execute-one!` is the right choice. If you have a SQL statement that you know will only return a single row in the result set, you probably want to use `execute-one!`. If you use `execute-one!` for a SQL statement that would return multiple rows in a result set, even though you will only get the first row back (as a hash map), the full result set will still be retrieved from the database -- it does not limit the SQL in any way. diff --git a/test/next/jdbc/sql_test.clj b/test/next/jdbc/sql_test.clj index 4c916ed..8bb4e56 100644 --- a/test/next/jdbc/sql_test.clj +++ b/test/next/jdbc/sql_test.clj @@ -86,6 +86,9 @@ (is (= 4 ((if (postgres?) :fruit/id :FRUIT/ID) (last rs)))))) (deftest test-find-by-keys + (let [rs (sql/find-by-keys (ds) :fruit {:appearance "neon-green"})] + (is (vector? rs)) + (is (= [] rs))) (let [rs (sql/find-by-keys (ds) :fruit {:appearance "yellow"})] (is (= 1 (count rs))) (is (every? map? rs)) @@ -93,6 +96,7 @@ (is (= 2 ((if (postgres?) :fruit/id :FRUIT/ID) (first rs)))))) (deftest test-get-by-id + (is (nil? (sql/get-by-id (ds) :fruit -1))) (let [row (sql/get-by-id (ds) :fruit 3)] (is (map? row)) (is (= "Peach" ((if (postgres?) :fruit/name :FRUIT/NAME) row)))) diff --git a/test/next/jdbc_test.clj b/test/next/jdbc_test.clj index a6eac84..a067d8c 100644 --- a/test/next/jdbc_test.clj +++ b/test/next/jdbc_test.clj @@ -28,11 +28,19 @@ (ds) ["select * from fruit where appearance = ?" "red"]))))) (testing "execute-one!" + (is (nil? (jdbc/execute-one! + (ds) + ["select * from fruit where appearance = ?" "neon-green"]))) (is (= "Apple" ((if (postgres?) :fruit/name :FRUIT/NAME) (jdbc/execute-one! (ds) ["select * from fruit where appearance = ?" "red"]))))) (testing "execute!" + (let [rs (jdbc/execute! + (ds) + ["select * from fruit where appearance = ?" "neon-green"])] + (is (vector? rs)) + (is (= [] rs))) (let [rs (jdbc/execute! (ds) ["select * from fruit where appearance = ?" "red"])]