From 8c75e3a546920bed9d65922c47de5015e09d7576 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Fri, 11 Oct 2019 11:26:41 -0700 Subject: [PATCH] Addresses #69 by expanding docs for execute-one! Updates include adding notes on `execute!`, `execute-one!`, and `plan` in the README, as well as expanding the docstrings for those functions. --- CHANGELOG.md | 1 + README.md | 4 ++-- doc/getting-started.md | 6 +++++- src/next/jdbc.clj | 9 +++++++-- src/next/jdbc/protocols.clj | 2 +- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c1968..19a7a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,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.8 release: +* Address #69 by trying to clarify when to use `execute-one!` vs `execute!` vs `plan`. * Address #68 by clarifying that builder functions do not affect the "fake result set" containing `:next.jdbc/update-count`. * Fix #67 by adding `:jdbcUrl` version spec. * Add `next.jdbc.optional/as-maps-adapter` to provide a way to override the default result set reading behavior of using `.getObject` when omitting SQL `NULL` values from result set maps. diff --git a/README.md b/README.md index ca94dc1..d8dcf8d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ From a `DataSource`, either you or `next.jdbc` can create a `java.sql.Connection The primary SQL execution API in `next.jdbc` is: * `plan` -- yields an `IReduceInit` that, when reduced, executes the SQL statement and then reduces over the `ResultSet` with as little overhead as possible. * `execute!` -- executes the SQL statement and produces a vector of realized hash maps, that use qualified keywords for the column names, of the form `:/`. If you join across multiple tables, the qualified keywords will reflect the originating tables for each of the columns. If the SQL produces named values that do not come from an associated table, a simple, unqualified keyword will be used. The realized hash maps returned by `execute!` are `Datafiable` and thus `Navigable` (see Clojure 1.10's `datafy` and `nav` functions, and tools like Cognitect's REBL). Alternatively, you can specify `{:builder-fn rs/as-arrays}` and produce a vector with column names followed by vectors of row values. `rs/as-maps` is the default for `:builder-fn` but there are also `rs/as-unqualified-maps` and `rs/as-unqualified-arrays` if you want unqualified `:` column names (and there are also lower-case variants of all of these). -* `execute-one!` -- executes the SQL statement and produces a single realized hash map. The realized hash map returned by `execute-one!` is `Datafiable` and thus `Navigable`. +* `execute-one!` -- executes the SQL or DDL statement and produces a single realized hash map. The realized hash map returned by `execute-one!` is `Datafiable` and thus `Navigable`. In addition, there are API functions to create `PreparedStatement`s (`prepare`) from `Connection`s, which can be passed to `plan`, `execute!`, or `execute-one!`, and to run code inside a transaction (the `transact` function and the `with-transaction` macro). @@ -57,7 +57,7 @@ Since `next.jdbc` uses raw Java JDBC types, you can use `with-open` directly to There are three intended usage scenarios that have driven the design of the API: * Execute a SQL statement and process it in a single eager operation, which may allow for the results to be streamed from the database (how to persuade JDBC to do that is database-specific!), and which cleans up resources before returning the result -- even if the reduction is short-circuited via `reduced`. This usage is supported by `plan`. This is likely to be the fastest approach and should be the first option you consider for SQL queries. -* Execute a SQL statement to obtain a single, fully-realized, `Datafiable` hash map that represents either the first row from a `ResultSet`, the first generated keys result (again, from a `ResultSet`), or the first result where neither of those are available (`next.jdbc` yields `{:next.jdbc/update-count N}` when it can only return an update count). This usage is supported by `execute-one!`. This is probably your best choice for most non-query operations. +* Execute a SQL or DDL statement to obtain a single, fully-realized, `Datafiable` hash map that represents either the first row from a `ResultSet`, the first generated keys result (again, from a `ResultSet`), or the first result where neither of those are available (`next.jdbc` yields `{:next.jdbc/update-count N}` when it can only return an update count). This usage is supported by `execute-one!`. This is probably your best choice for most non-query operations. * Execute a SQL statement to obtain a fully-realized, `Datafiable` result set -- a vector of hash maps. This usage is supported by `execute!`. You can also produce a vector of column names/row values (`next.jdbc.result-set/as-arrays`). In addition, convenience functions -- "syntactic sugar" -- are provided to insert rows, run queries, update rows, and delete rows, using the same names as in `clojure.java.jdbc`. These are in `next.jdbc.sql` since they involve SQL creation -- they are not considered part of the core API. diff --git a/doc/getting-started.md b/doc/getting-started.md index 20fc3ef..36d74b8 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -89,9 +89,11 @@ 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. +> 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. + ### `plan` & Reducing Result Sets -While these functions are fine for retrieving result sets as data, most of the time you want to process that data efficiently, so `next.jdbc` provides a SQL execution function that works with `reduce` and with transducers to consume the result set without the intermediate overhead of creating Clojure data structures for every row: +While those functions are fine for retrieving result sets as data, most of the time you want to process that data efficiently, so `next.jdbc` provides a SQL execution function that works with `reduce` and with transducers to consume the result set without the intermediate overhead of creating Clojure data structures for every row: ```clojure user=> (into #{} @@ -113,6 +115,8 @@ user=> Any operation that can perform key-based lookup can be used here without creating hash maps: `get`, `contains?`, `find` (returns a `MapEntry` of whatever key you requested and the corresponding column value), or direct keyword access as shown above. Any operation that would require a Clojure hash map, such as `assoc` or anything that invokes `seq` (`keys`, `vals`), will cause the full row to be expanded into a hash map, such as produced by `execute!` or `execute-one!`. +> Note: since `plan` expects you to process the result set via reduction, you should not use it for DDL or for SQL statements that only produce update counts. + ## Datasources, Connections & Transactions In the examples above, we created a datasource and then passed it into each function call. When `next.jdbc` is given a datasource, it creates a `java.sql.Connection` from it, uses it for the SQL operation, and then closes it. If you're not using a connection pooling datasource (see below), that can be quite an overhead: setting up database connections to remote servers is not cheap! diff --git a/src/next/jdbc.clj b/src/next/jdbc.clj index 4ee8dff..35a1d82 100644 --- a/src/next/jdbc.clj +++ b/src/next/jdbc.clj @@ -157,7 +157,7 @@ (p/prepare connection sql-params opts))) (defn plan - "General SQL execution function. + "General SQL execution function (for working with result sets). Returns a reducible that, when reduced, runs the SQL and yields the result. @@ -193,9 +193,14 @@ (defn execute-one! "General SQL execution function that returns just the first row of a result. + For any DDL or SQL statement that will return just an update count, this is + the preferred function to use. Can be called on a `PreparedStatement`, a `Connection`, or something that can - produce a `Connection` via a `DataSource`." + produce a `Connection` via a `DataSource`. + + Note: although this only returns the first row of a result set, it does not + place any limit on the result of the SQL executed." ([stmt] (p/-execute-one stmt [] {})) ([connectable sql-params] diff --git a/src/next/jdbc/protocols.clj b/src/next/jdbc/protocols.clj index 6847de3..2d15320 100644 --- a/src/next/jdbc/protocols.clj +++ b/src/next/jdbc/protocols.clj @@ -40,7 +40,7 @@ "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` + "Executes the SQL or DDL 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