Update to latest API
Declares API to have reached stability and updates usage and examples to latest API.
This commit is contained in:
parent
f7c753d696
commit
3b2d2a194c
1 changed files with 22 additions and 8 deletions
30
README.md
30
README.md
|
|
@ -7,16 +7,16 @@ The next generation of `clojure.java.jdbc`: a new low-level Clojure wrapper for
|
|||
Why another JDBC library? Why a different API from `clojure.java.jdbc`?
|
||||
|
||||
* Performance: there's a surprising amount of overhead in how `ResultSet` objects are converted to sequences of hash maps – which can be really noticeable for large result sets – so I want a better way to handle that. There's also quite a bit of overhead and complexity in all the conditional logic and parsing that is associated with `db-spec`-as-hash-map.
|
||||
* A more modern API, based on using qualified keywords and transducers etc: `:qualifier` and `reducible-query` in recent `clojure.java.jdbc` versions were steps toward that but there's a lot of "legacy" API in the library and I want to present a more focused, more streamlined API so folks naturally use the `IReduceInit` / transducer approach from day one and benefit from qualified keywords. I'm still contemplating whether there are reasonable ways to integrate with `clojure.spec` (for example, if you have specs of your data model, could `next.jdbc` leverage that somehow?).
|
||||
* A more modern API, based on using qualified keywords and transducers etc: `:qualifier` and `reducible-query` in recent `clojure.java.jdbc` versions were steps toward that but there's a lot of "legacy" API in the library and I want to present a more focused, more streamlined API so folks naturally use the `IReduceInit` / transducer approach from day one and benefit from qualified keywords.
|
||||
* Simplicity: `clojure.java.jdbc` uses a variety of ways to execute SQL which can lead to inconsistencies and surprises – `query`, `execute!`, and `db-do-commands` are all different ways to execute different types of SQL statement so you have to remember which is which and you often have to watch out for restrictions in the underlying JDBC API.
|
||||
|
||||
Those are my three primary drivers. In addition, the `db-spec`-as-hash-map approach in `clojure.java.jdbc` has caused a lot of frustration and confusion in the past, especially with the wide range of conflicting options that are supported. `next.jdbc` is heavily protocol-based so it's easier to mix'n'match how you use it with direct Java JDBC code (and the protocol-based approach contributes to the improved performance overall). There's a much clearer path of `db-spec` -> `DataSource` -> `Connection` now, which should steer people toward more connection reuse and better performing apps.
|
||||
|
||||
I also wanted `datafy`/`nav` support baked right in (it was added to `clojure.java.jdbc` back in December 2018 as an undocumented, experimental API in a separate namespace). I wanted it to be "free" in terms of performance (it isn't quite – my next round of changes should address that).
|
||||
I also wanted `datafy`/`nav` support baked right in (it was added to `clojure.java.jdbc` back in December 2018 as an undocumented, experimental API in a separate namespace). It is the default behavior for `execute!` and `execute!`. The protocol `next.jdbc.result-set/datafiable-row` can be used with `reducible!` if you need to add `datafy`/`nav` support to rows you are creating in your reduction.
|
||||
|
||||
The API so far is still very much a work-in-progress. I'm still very conflicted about the "syntactic sugar" SQL functions (`insert!`, `query`, `update!`, and `delete!`). They go beyond what I really want to include in the API, but I know that their equivalents in `clojure.java.jdbc` are heavily used (based on the number of questions and JIRA issues I get).
|
||||
At this point, I would consider the API to be fairly stable (2019-04-18). The "syntactic sugar" SQL functions (`insert!`, `query`, `update!`, and `delete!`) go beyond what I wanted to include in the core API so they are in `next.jdbc.sql`. I know that their equivalents in `clojure.java.jdbc` are heavily used (based on the number of questions and JIRA issues I get).
|
||||
|
||||
So, while I'm comfortable to put it out there and get feedback – and I've had lots of great feedback so far – expect to see more changes, possible some dramatic ones, in the next month or so before I actually settle on where the library will live and what the published artifacts will look like.
|
||||
I am still considering whether this should go into Contrib as new namespaces in `clojure.java.jdbc` or whether it will continue to live standalone so I'm not accepting Pull Requests yet (but I haven't disabled them on GitHub!). There are pros and cons to both choices, in my mind.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ From a `DataSource`, either you or `next.jdbc` can create a `java.sql.Connection
|
|||
|
||||
The primary SQL execution API in `next.jdbc` is:
|
||||
* `reducible!` -- 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 `:<table>/<column>`. 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 `{:gen-fn rs/as-arrays}` and produce a vector with column names followed by vectors of row values.
|
||||
* `execute!` -- executes the SQL statement and produces a vector of realized hash maps, that use qualified keywords for the column names, of the form `:<table>/<column>`. 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 `{:gen-fn rs/as-arrays}` and produce a vector with column names followed by vectors of row values. `rs/as-maps` is the default for `:gen-fn` but there are also `rs/as-unqualified-maps` and `rs/as-unqualified-arrays` if you want unqualified `:<column>` column names.
|
||||
* `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`.
|
||||
|
||||
In addition, there are API functions to create `PreparedStatement`s (`prepare`) from `Connection`s, which can be passed to `reducible!`, `execute!`, or `execute-one!`, and to run code inside a transaction (the `transact` function and the `with-transaction` macro).
|
||||
|
|
@ -44,7 +44,7 @@ Since `next.jdbc` uses raw Java JDBC types, you can use `with-open` directly to
|
|||
### Usage scenarios
|
||||
|
||||
There are three intended usage scenarios that may drive the API to change:
|
||||
* 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` will yield `{:next.jdbc/update-count N}`) when it can only return an update count). This usage is currently supported by `execute-one!`.
|
||||
* 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 currently supported by `execute-one!`.
|
||||
* 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`).
|
||||
* 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 `reducible!`.
|
||||
|
||||
|
|
@ -66,11 +66,25 @@ Whereas `clojure.java.jdbc` supports a wide variety of options to describe how t
|
|||
|
||||
### Clojure identifier creation
|
||||
|
||||
While the `:identifiers` option is still (currently) supported for operations that produce realized row hash maps, it defaults to `identity` rather than `clojure.java.jdbc`'s `clojure.string/lower-case`, and it is called separately for the table name string and the column name string in the qualified keys of those maps.
|
||||
The `:identifiers` option is not supported. Column names are returned "as-is", rather than `clojure.java.jdbc`'s default behavior of `clojure.string/lower-case`. You can write your own `get-column-names` and `as-maps` variants to produce whatever behavior you need:
|
||||
|
||||
```
|
||||
(defn lower-case-cols [^ResultSetMetaData rsmeta opts]
|
||||
(mapv (fn [^Integer i]
|
||||
(keyword (str/lower-case (.getColumnLabel rsmeta i))))
|
||||
(range 1 (inc (.getColumnCount rsmeta)))))
|
||||
|
||||
(defn as-lower-case [^ResultSet rs opts]
|
||||
(let [rsmeta (.getMetaData rs)
|
||||
cols (lower-case-cols rsmeta opts)]
|
||||
(next.jdbc.result-set/->MapResultSetBuilder rs rsmeta cols)))
|
||||
|
||||
(execute! con ["SELECT * FROM fruit"] {:gen-fn as-lower-case})
|
||||
```
|
||||
|
||||
### SQL entity creation
|
||||
|
||||
The `:entities` option is still (currently) supported for operations that create SQL strings from Clojure data structures. As with `clojure.java.jdbc`, it is applied to the string that will be used for the SQL entity (after converting incoming keywords to strings), and the various "quoting" strategies used in specific databases are now functions in the `next.jdbc.quoted` namespace: `ansi`, `mysql`, `postgres` (alias for `ansi`), `oracle` (also an alias for `ansi`), and `sql-server`.
|
||||
The `:entities` option has been split into two options: `:table-fn` and `:column-fn`. These are used when creating SQL strings from Clojure data structures (the "syntactic sugar" functions in `next.jdbc.sql`). As with `clojure.java.jdbc`'s `:entities` option, they are applied to the string that will be used for the SQL entity (after converting incoming keywords to strings), and the various "quoting" strategies used in specific databases are now functions in the `next.jdbc.quoted` namespace: `ansi`, `mysql`, `postgres` (alias for `ansi`), `oracle` (also an alias for `ansi`), and `sql-server`.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue