Merge branch 'develop' into multi-rs

This commit is contained in:
Sean Corfield 2020-06-24 12:01:43 -07:00
commit 2a8571bb14
11 changed files with 118 additions and 14 deletions

View file

@ -7,6 +7,9 @@ Changes made on master since 1.0.476:
## Stable Builds ## Stable Builds
* 2020-06-24 -- 1.0.478
* Address #123 by adding `next.jdbc.types` namespace, full of auto-generated `as-xxx` functions, one for each of the `java.sql.Types` values.
* 2020-06-22 -- 1.0.476 * 2020-06-22 -- 1.0.476
* Extend default options behavior to `next.jdbc.sql` functions. * Extend default options behavior to `next.jdbc.sql` functions.

View file

@ -6,7 +6,7 @@ The next generation of `clojure.java.jdbc`: a new low-level Clojure wrapper for
The latest versions on Clojars and on cljdoc: The latest versions on Clojars and on cljdoc:
[![Clojars Project](https://clojars.org/seancorfield/next.jdbc/latest-version.svg)](https://clojars.org/seancorfield/next.jdbc) [![cljdoc badge](https://cljdoc.org/badge/seancorfield/next.jdbc?1.0.476)](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT) [![Clojars Project](https://clojars.org/seancorfield/next.jdbc/latest-version.svg)](https://clojars.org/seancorfield/next.jdbc) [![cljdoc badge](https://cljdoc.org/badge/seancorfield/next.jdbc?1.0.478)](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT)
The documentation on [cljdoc.org](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT) is for the current version of `next.jdbc`: The documentation on [cljdoc.org](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT) is for the current version of `next.jdbc`:
@ -14,7 +14,7 @@ The documentation on [cljdoc.org](https://cljdoc.org/d/seancorfield/next.jdbc/CU
* [Migrating from `clojure.java.jdbc`](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/doc/migration-from-clojure-java-jdbc) * [Migrating from `clojure.java.jdbc`](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/doc/migration-from-clojure-java-jdbc)
* Feedback via [issues](https://github.com/seancorfield/next-jdbc/issues) or in the [`#sql` channel on the Clojurians Slack](https://clojurians.slack.com/messages/C1Q164V29/details/) or the [`#sql` stream on the Clojurians Zulip](https://clojurians.zulipchat.com/#narrow/stream/152063-sql). * Feedback via [issues](https://github.com/seancorfield/next-jdbc/issues) or in the [`#sql` channel on the Clojurians Slack](https://clojurians.slack.com/messages/C1Q164V29/details/) or the [`#sql` stream on the Clojurians Zulip](https://clojurians.zulipchat.com/#narrow/stream/152063-sql).
The documentation on GitHub is for **develop** since the 1.0.476 release -- [see the CHANGELOG](https://github.com/seancorfield/next-jdbc/blob/develop/CHANGELOG.md) and then read the [corresponding updated documentation](https://github.com/seancorfield/next-jdbc/tree/develop/doc) on GitHub if you want. The documentation on GitHub is for **develop** since the 1.0.478 release -- [see the CHANGELOG](https://github.com/seancorfield/next-jdbc/blob/develop/CHANGELOG.md) and then read the [corresponding updated documentation](https://github.com/seancorfield/next-jdbc/tree/develop/doc) on GitHub if you want.
This project follows the version scheme MAJOR.MINOR.COMMITS where MAJOR and MINOR provide some relative indication of the size of the change, but do not follow semantic versioning. In general, all changes endeavor to be non-breaking (by moving to new names rather than by breaking existing names). COMMITS is an ever-increasing counter of commits since the beginning of this repository. This project follows the version scheme MAJOR.MINOR.COMMITS where MAJOR and MINOR provide some relative indication of the size of the change, but do not follow semantic versioning. In general, all changes endeavor to be non-breaking (by moving to new names rather than by breaking existing names). COMMITS is an ever-increasing counter of commits since the beginning of this repository.

View file

@ -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: You can add `next.jdbc` to your project with either:
```clojure ```clojure
seancorfield/next.jdbc {:mvn/version "1.0.476"} seancorfield/next.jdbc {:mvn/version "1.0.478"}
``` ```
for `deps.edn` or: for `deps.edn` or:
```clojure ```clojure
[seancorfield/next.jdbc "1.0.476"] [seancorfield/next.jdbc "1.0.478"]
``` ```
for `project.clj` or `build.boot`. 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 ```clojure
;; deps.edn ;; deps.edn
{:deps {org.clojure/clojure {:mvn/version "1.10.1"} {:deps {org.clojure/clojure {:mvn/version "1.10.1"}
seancorfield/next.jdbc {:mvn/version "1.0.476"} seancorfield/next.jdbc {:mvn/version "1.0.478"}
com.h2database/h2 {:mvn/version "1.4.199"}}} com.h2database/h2 {:mvn/version "1.4.199"}}}
``` ```
@ -398,12 +398,14 @@ If you are using [Component](https://github.com/stuartsierra/component), a conne
By default, `next.jdbc` relies on the JDBC driver to handle all data type conversions when reading from a result set (to produce Clojure values from SQL values) or setting parameters (to produce SQL values from Clojure values). Sometimes that means that you will get back a database-specific Java object that would need to be manually converted to a Clojure data structure, or that certain database column types require you to manually construct the appropriate database-specific Java object to pass into a SQL operation. You can usually automate those conversions using either the [`ReadableColumn` protocol](/doc/result-set-builders.md#readablecolumn) (for converting database-specific types to Clojure values) or the [`SettableParameter` protocol](/doc/prepared-statements.md#prepared-statement-parameters) (for converting Clojure values to database-specific types). By default, `next.jdbc` relies on the JDBC driver to handle all data type conversions when reading from a result set (to produce Clojure values from SQL values) or setting parameters (to produce SQL values from Clojure values). Sometimes that means that you will get back a database-specific Java object that would need to be manually converted to a Clojure data structure, or that certain database column types require you to manually construct the appropriate database-specific Java object to pass into a SQL operation. You can usually automate those conversions using either the [`ReadableColumn` protocol](/doc/result-set-builders.md#readablecolumn) (for converting database-specific types to Clojure values) or the [`SettableParameter` protocol](/doc/prepared-statements.md#prepared-statement-parameters) (for converting Clojure values to database-specific types).
In particular, PostgreSQL does not seem to perform a conversion from `java.util.Date` to a SQL data type automatically. You must `require` the [`next.jdbc.date-time` namespace](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/api/next.jdbc.date-time) to enable that conversion. In particular, PostgreSQL does not seem to perform a conversion from `java.util.Date` to a SQL data type automatically. You can `require` the [`next.jdbc.date-time` namespace](https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/api/next.jdbc.date-time) to enable that conversion.
If you are working with Java Time, some JDBC drivers will automatically convert `java.time.Instant` (and `java.time.LocalDate` and `java.time.LocalDateTime`) to a SQL data type automatically, but others will not. Requiring `next.jdbc.date-time` will enable those automatic conversions for all databases. If you are working with Java Time, some JDBC drivers will automatically convert `java.time.Instant` (and `java.time.LocalDate` and `java.time.LocalDateTime`) to a SQL data type automatically, but others will not. Requiring `next.jdbc.date-time` will enable those automatic conversions for all databases.
> Note: `next.jdbc.date-time` also provides functions you can call to enable automatic conversion of SQL date/timestamp types to Clojure data types when reading result sets. If you need specific conversions beyond that to happen automatically, consider extending the `ReadableColumn` protocol, mentioned above. > Note: `next.jdbc.date-time` also provides functions you can call to enable automatic conversion of SQL date/timestamp types to Clojure data types when reading result sets. If you need specific conversions beyond that to happen automatically, consider extending the `ReadableColumn` protocol, mentioned above.
The `next.jdbc.types` namespace provides over three dozen convenience functions for "type hinting" values so that the JDBC driver might automatically handle some conversions that the default parameter setting function does not. Each function is named for the corresponding SQL type, prefixed by `as-`: `as-bigint`, `as-other`, `as-real`, etc. An example of where this helps is when dealing with PostgreSQL enumerated types: the default behavior, when passed a string that should correspond to an enumerated type, is to throw an exception that `column "..." is of type ... but expression is of type character varying`. You can wrap such strings with `(as-other "...")` which tells PostgreSQL to treat this as `java.sql.Types/OTHER` when setting the parameter.
## Processing Database Metadata ## Processing Database Metadata
JDBC provides several features that let you introspect the database to obtain lists of tables, views, and so on. `next.jdbc` does not provide any specific functions for this but you can easily get this metadata from a `java.sql.Connection` and turn it into Clojure data as follows: JDBC provides several features that let you introspect the database to obtain lists of tables, views, and so on. `next.jdbc` does not provide any specific functions for this but you can easily get this metadata from a `java.sql.Connection` and turn it into Clojure data as follows:

View file

@ -54,6 +54,8 @@ You can also extend this protocol via metadata so you can do it on a per-object
(with-meta obj {'next.jdbc.prepare/set-parameter (fn [v ps i]...)}) (with-meta obj {'next.jdbc.prepare/set-parameter (fn [v ps i]...)})
``` ```
The `next.jdbc.types` namespace provides functions to wrap values with per-object implementations of `set-parameter` for every standard `java.sql.Types` value. Each is named `as-xxx` corresponding to `java.sql.Types/XXX`.
The converse, converting database-specific types to Clojure values is handled by the `ReadableColumn` protocol, discussed in the previous section ([Result Set Builders](/doc/result-set-builders.md#readablecolumn)). The converse, converting database-specific types to Clojure values is handled by the `ReadableColumn` protocol, discussed in the previous section ([Result Set Builders](/doc/result-set-builders.md#readablecolumn)).
As noted above, `next.jdbc.prepare/set-parameters` is available for you to call on any existing `PreparedStatement` to set or update the parameters that will be used when the statement is executed: As noted above, `next.jdbc.prepare/set-parameters` is available for you to call on any existing `PreparedStatement` to set or update the parameters that will be used when the statement is executed:

View file

@ -109,6 +109,27 @@ If you have a query where you want to select where a column is `IN` a sequence o
What does this mean for your use of `next.jdbc`? In `plan`, `execute!`, and `execute-one!`, you can use `col = ANY(?)` in the SQL string and a single primitive array parameter, such as `(int-array [1 2 3 4])`. That means that in `next.jdbc.sql`'s functions that take a where clause (`find-by-keys`, `update!`, and `delete!`) you can specify `["col = ANY(?)" (int-array data)]` for what would be a `col IN (?,?,?,,,?)` where clause for other databases and require multiple values. What does this mean for your use of `next.jdbc`? In `plan`, `execute!`, and `execute-one!`, you can use `col = ANY(?)` in the SQL string and a single primitive array parameter, such as `(int-array [1 2 3 4])`. That means that in `next.jdbc.sql`'s functions that take a where clause (`find-by-keys`, `update!`, and `delete!`) you can specify `["col = ANY(?)" (int-array data)]` for what would be a `col IN (?,?,?,,,?)` where clause for other databases and require multiple values.
PostgreSQL has a SQL extension for defining enumerated types and the default `set-parameter` implementation will not work for those. You can use `next.jdbc.types/as-other` to wrap string values in a way that the JDBC driver will convert them to enumerated type values:
```sql
CREATE TYPE language AS ENUM('en','fr','de');
CREATE TABLE person (
...
speaks language NOT NULL,
...
);
```
```clojure
(require '[next.jdbc.sql :as sql]
'[next.jdbc.types :refer [as-other]])
(sql/insert! ds :person {:speaks (as-other "fr")})
```
That call produces a vector `["fr"]` with metadata that implements `set-parameter` such that `.setObject()` is called with `java.sql.Types/OTHER` which allows PostgreSQL to "convert" the string `"fr"` to the corresponding `language` enumerated type value.
### Streaming Result Sets ### Streaming Result Sets
You can get PostgreSQL to stream very large result sets (when you are reducing over `plan`) by setting the following options: You can get PostgreSQL to stream very large result sets (when you are reducing over `plan`) by setting the following options:
@ -150,7 +171,7 @@ create table example(
;; => #:example{:tags ["tag1" "tag2"]} ;; => #:example{:tags ["tag1" "tag2"]}
``` ```
Note: PostgreSQL JDBC driver supports only 7 primitive array types, but not such as `UUID[]` - > Note: PostgreSQL JDBC driver supports only 7 primitive array types, but not array types like `UUID[]` -
[PostgreSQL™ Extensions to the JDBC API](https://jdbc.postgresql.org/documentation/head/arrays.html). [PostgreSQL™ Extensions to the JDBC API](https://jdbc.postgresql.org/documentation/head/arrays.html).
### Working with Date and Time ### Working with Date and Time

View file

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>seancorfield</groupId> <groupId>seancorfield</groupId>
<artifactId>next.jdbc</artifactId> <artifactId>next.jdbc</artifactId>
<version>1.0.476</version> <version>1.0.478</version>
<name>next.jdbc</name> <name>next.jdbc</name>
<description>The next generation of clojure.java.jdbc: a new low-level Clojure wrapper for JDBC-based access to databases.</description> <description>The next generation of clojure.java.jdbc: a new low-level Clojure wrapper for JDBC-based access to databases.</description>
<url>https://github.com/seancorfield/next-jdbc</url> <url>https://github.com/seancorfield/next-jdbc</url>
@ -22,7 +22,7 @@
<url>https://github.com/seancorfield/next-jdbc</url> <url>https://github.com/seancorfield/next-jdbc</url>
<connection>scm:git:git://github.com/seancorfield/next-jdbc.git</connection> <connection>scm:git:git://github.com/seancorfield/next-jdbc.git</connection>
<developerConnection>scm:git:ssh://git@github.com/seancorfield/next-jdbc.git</developerConnection> <developerConnection>scm:git:ssh://git@github.com/seancorfield/next-jdbc.git</developerConnection>
<tag>v1.0.476</tag> <tag>v1.0.478</tag>
</scm> </scm>
<dependencies> <dependencies>
<dependency> <dependency>

View file

@ -15,7 +15,11 @@
See also https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/api/next.jdbc.date-time See also https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/api/next.jdbc.date-time
for implementations of `SettableParameter` that provide automatic for implementations of `SettableParameter` that provide automatic
conversion of Java Time objects to SQL data types." conversion of Java Time objects to SQL data types.
See also https://cljdoc.org/d/seancorfield/next.jdbc/CURRENT/api/next.jdbc.types
for `as-xxx` functions that provide per-instance implementations of
`SettableParameter` for each of the standard `java.sql.Types` values."
(:require [clojure.java.data :as j] (:require [clojure.java.data :as j]
[next.jdbc.protocols :as p]) [next.jdbc.protocols :as p])
(:import (java.sql Connection (:import (java.sql Connection

39
src/next/jdbc/types.clj Normal file
View file

@ -0,0 +1,39 @@
;; copyright (c) 2018-2020 Sean Corfield, all rights reserved
(ns next.jdbc.types
"Provides convenience functions for wrapping values you pass into SQL
operations that have per-instance implementations of `SettableParameter`
so that `.setObject()` is called with the appropriate `java.sql.Types` value."
(:require [clojure.string :as str]
[next.jdbc.prepare :as prep])
(:import (java.lang.reflect Field Modifier)
(java.sql PreparedStatement)))
(set! *warn-on-reflection* true)
(defmacro ^:private all-types
[]
(let [names
(into []
(comp (filter #(Modifier/isStatic (.getModifiers ^Field %)))
(map #(.getName ^Field %)))
(.getDeclaredFields java.sql.Types))]
`(do
~@(for [n names]
(let [as-n (symbol (str "as-"
(-> n
(str/lower-case)
(str/replace "_" "-"))))]
`(defn ~as-n
~(str "Wrap a Clojure value in a vector with metadata to implement `set-parameter`
so that `.setObject()` is called with the `java.sql.Types/" n "` SQL type.")
[~'obj]
(with-meta [~'obj]
{'next.jdbc.prepare/set-parameter
(fn [[v#] ^PreparedStatement s# ^long i#]
(.setObject s# i# v# ~(symbol "java.sql.Types" n)))})))))))
(all-types)
(comment
(macroexpand '(all-types)))

View file

@ -8,7 +8,8 @@
[next.jdbc.sql :as sql] [next.jdbc.sql :as sql]
[next.jdbc.test-fixtures [next.jdbc.test-fixtures
:refer [with-test-db ds column default-options :refer [with-test-db ds column default-options
derby? jtds? maria? mssql? mysql? postgres? sqlite?]])) derby? jtds? maria? mssql? mysql? postgres? sqlite?]]
[next.jdbc.types :refer [as-other as-real as-varchar]]))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
@ -79,8 +80,9 @@
:else :FRUIT/ID)] :else :FRUIT/ID)]
(testing "single insert/delete" (testing "single insert/delete"
(is (== 5 (new-key (sql/insert! (ds) :fruit (is (== 5 (new-key (sql/insert! (ds) :fruit
{:name "Kiwi" :appearance "green & fuzzy" {:name (as-varchar "Kiwi")
:cost 100 :grade 99.9})))) :appearance "green & fuzzy"
:cost 100 :grade (as-real 99.9)}))))
(is (= 5 (count (sql/query (ds) ["select * from fruit"])))) (is (= 5 (count (sql/query (ds) ["select * from fruit"]))))
(is (= {:next.jdbc/update-count 1} (is (= {:next.jdbc/update-count 1}
(sql/delete! (ds) :fruit {:id 5}))) (sql/delete! (ds) :fruit {:id 5})))
@ -156,3 +158,8 @@
(when (postgres?) (when (postgres?)
(let [data (sql/find-by-keys (ds) :fruit ["id = any(?)" (int-array [1 2 3 4])])] (let [data (sql/find-by-keys (ds) :fruit ["id = any(?)" (int-array [1 2 3 4])])]
(is (= 4 (count data)))))) (is (= 4 (count data))))))
(deftest enum-pg
(when (postgres?)
(let [r (sql/insert! (ds) :lang_test {:lang (as-other "fr")})]
(is (= {:lang_test/lang "fr"} r)))))

View file

@ -140,7 +140,19 @@
(catch Throwable _))) (catch Throwable _)))
(try (try
(do-commands con [(str "DROP TABLE " fruit)]) (do-commands con [(str "DROP TABLE " fruit)])
(catch Throwable _)) (catch Exception _))
(when (postgres?)
(try
(do-commands con ["DROP TABLE LANG_TEST"])
(catch Exception _))
(try
(do-commands con ["DROP TYPE LANGUAGE"])
(catch Exception _))
(do-commands con ["CREATE TYPE LANGUAGE AS ENUM('en','fr','de')"])
(do-commands con ["
CREATE TABLE LANG_TEST (
LANG LANGUAGE NOT NULL
)"]))
(do-commands con [(str " (do-commands con [(str "
CREATE TABLE " fruit " ( CREATE TABLE " fruit " (
ID INTEGER " auto-inc-pk ", ID INTEGER " auto-inc-pk ",

View file

@ -0,0 +1,14 @@
;; copyright (c) 2020 Sean Corfield, all rights reserved
(ns next.jdbc.types-test
"Some tests for the type-assist functions."
(:require [clojure.test :refer [deftest is testing]]
[next.jdbc.types :refer [as-varchar]]))
(set! *warn-on-reflection* true)
(deftest as-varchar-test
(let [v (as-varchar "Hello")]
(is (= ["Hello"] v))
(is (contains? (meta v) 'next.jdbc.prepare/set-parameter))
(is (fn? (get (meta v) 'next.jdbc.prepare/set-parameter)))))