Alpha 5; fixes #12 by adding :order-by option to for-query
This commit is contained in:
parent
cc0e33413d
commit
03b09f6e03
6 changed files with 89 additions and 38 deletions
|
|
@ -54,5 +54,4 @@ These are mostly drawn from Issue #5 although most of the bullets in that issue
|
||||||
* Keyword options no longer end in `?` -- to reflect the latest best practice on predicates vs. attributes,
|
* Keyword options no longer end in `?` -- to reflect the latest best practice on predicates vs. attributes,
|
||||||
* `with-db-connection` has been replaced by just `with-open` containing a call to `get-connection`,
|
* `with-db-connection` has been replaced by just `with-open` containing a call to `get-connection`,
|
||||||
* `with-transaction` can take a `:rollback-only` option, but there is no way to change a transaction to rollback _dynamically_; throw an exception instead (all transactions roll back on an exception)
|
* `with-transaction` can take a `:rollback-only` option, but there is no way to change a transaction to rollback _dynamically_; throw an exception instead (all transactions roll back on an exception)
|
||||||
* `find-by-keys` no longer supports `:order-by` (but this may come back),
|
|
||||||
* The extension points for setting parameters and reading columns are now `SettableParameter` and `ReadableColumn` protocols.
|
* The extension points for setting parameters and reading columns are now `SettableParameter` and `ReadableColumn` protocols.
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,17 @@ Given a table name (as a keyword) and either a hash map of column names and valu
|
||||||
"Stella" "stella@artois.beer"])
|
"Stella" "stella@artois.beer"])
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`find-by-keys` supports an `:order-by` option which can specify a vector of column names to sort the results by. Elements may be column names or pairs of a column name and the direction to sort: `:asc` or `:desc`:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(sql/find-by-keys ds :address
|
||||||
|
{:name "Stella" :email "stella@artois.beer"}
|
||||||
|
{:order-by [[:id :desc]]})
|
||||||
|
;; equivalent to
|
||||||
|
(jdbc/execute! ds ["SELECT * FROM address WHERE name = ? AND email = ? ORDER BY id DESC"
|
||||||
|
"Stella" "stella@artois.beer"])
|
||||||
|
```
|
||||||
|
|
||||||
## `get-by-id`
|
## `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:
|
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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{next.jdbc {:mvn/version "1.0.0-alpha4"}}
|
{next.jdbc {:mvn/version "1.0.0-alpha5"}}
|
||||||
```
|
```
|
||||||
for `deps.edn` or:
|
for `deps.edn` or:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
[next.jdbc "1.0.0-alpha4"]
|
[next.jdbc "1.0.0-alpha5"]
|
||||||
```
|
```
|
||||||
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.0"}
|
{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
|
||||||
next.jdbc {:mvn/version "1.0.0-alpha4"}
|
next.jdbc {:mvn/version "1.0.0-alpha5"}
|
||||||
com.h2database/h2 {:mvn/version "1.4.197"}}}
|
com.h2database/h2 {:mvn/version "1.4.197"}}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
2
pom.xml
2
pom.xml
|
|
@ -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.0-alpha4</version>
|
<version>1.0.0-alpha5</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>
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,29 @@
|
||||||
"Some utility functions that make common operations easier by
|
"Some utility functions that make common operations easier by
|
||||||
providing some syntactic sugar over `execute!`/`execute-one!`.
|
providing some syntactic sugar over `execute!`/`execute-one!`.
|
||||||
|
|
||||||
This is intended to provide a minimal level of parity with clojure.java.jdbc
|
This is intended to provide a minimal level of parity with
|
||||||
(insert!, update!, delete!, etc). For anything more complex, use a library
|
`clojure.java.jdbc` (`insert!`, `update!`, `delete!`, etc).
|
||||||
like HoneySQL https://github.com/jkk/honeysql to generate SQL + parameters.
|
For anything more complex, use a library like HoneySQL
|
||||||
|
https://github.com/jkk/honeysql to generate SQL + parameters.
|
||||||
|
|
||||||
The following options are supported:
|
The following options are supported:
|
||||||
* :table-fn -- specify a function used to convert table names (strings)
|
* `:table-fn` -- specify a function used to convert table names (strings)
|
||||||
to SQL entity names -- see the next.jdbc.quoted namespace for the
|
to SQL entity names -- see the `next.jdbc.quoted` namespace for the
|
||||||
most common quoting strategy functions,
|
most common quoting strategy functions,
|
||||||
* :column-fn -- specify a function used to convert column names (strings)
|
* `:column-fn` -- specify a function used to convert column names (strings)
|
||||||
to SQL entity names -- see the next.jdbc.quoted namespace for the
|
to SQL entity names -- see the `next.jdbc.quoted` namespace for the
|
||||||
most common quoting strategy functions."
|
most common quoting strategy functions.
|
||||||
|
|
||||||
|
In addition, `find-by-keys` supports `:order-by` to add an `ORDER BY`
|
||||||
|
clause to the generated SQL."
|
||||||
(:require [clojure.string :as str]
|
(:require [clojure.string :as str]
|
||||||
[next.jdbc :refer [execute! execute-one!]]))
|
[next.jdbc :refer [execute! execute-one!]]))
|
||||||
|
|
||||||
(defn- by-keys
|
(defn- by-keys
|
||||||
"Given a hash map of column names and values and a clause type (:set, :where),
|
"Given a hash map of column names and values and a clause type
|
||||||
return a vector of a SQL clause and its parameters.
|
(`:set`, `:where`), return a vector of a SQL clause and its parameters.
|
||||||
|
|
||||||
Applies any :column-fn supplied in the options."
|
Applies any `:column-fn` supplied in the options."
|
||||||
[key-map clause opts]
|
[key-map clause opts]
|
||||||
(let [entity-fn (:column-fn opts identity)
|
(let [entity-fn (:column-fn opts identity)
|
||||||
[where params] (reduce-kv (fn [[conds params] k v]
|
[where params] (reduce-kv (fn [[conds params] k v]
|
||||||
|
|
@ -40,22 +44,50 @@
|
||||||
"Given a hash map of column names and values, return a string of all the
|
"Given a hash map of column names and values, return a string of all the
|
||||||
column names.
|
column names.
|
||||||
|
|
||||||
Applies any :column-fn supplied in the options."
|
Applies any `:column-fn` supplied in the options."
|
||||||
[key-map opts]
|
[key-map opts]
|
||||||
(str/join ", " (map (comp (:column-fn opts identity) name) (keys key-map))))
|
(str/join ", " (map (comp (:column-fn opts identity) name) (keys key-map))))
|
||||||
|
|
||||||
(defn- as-?
|
(defn- as-?
|
||||||
"Given a hash map of column names and values, or a vector of column names,
|
"Given a hash map of column names and values, or a vector of column names,
|
||||||
return a string of ? placeholders for them."
|
return a string of `?` placeholders for them."
|
||||||
[key-map opts]
|
[key-map opts]
|
||||||
(str/join ", " (repeat (count key-map) "?")))
|
(str/join ", " (repeat (count key-map) "?")))
|
||||||
|
|
||||||
|
(defn- for-order-col
|
||||||
|
"Given a column name, or a pair of column name and direction,
|
||||||
|
return the sub-clause for addition to `ORDER BY`."
|
||||||
|
[col opts]
|
||||||
|
(let [entity-fn (:column-fn opts identity)]
|
||||||
|
(cond (keyword? col)
|
||||||
|
(entity-fn (name col))
|
||||||
|
|
||||||
|
(and (vector? col) (= 2 (count col)) (keyword? (first col)))
|
||||||
|
(str (entity-fn (name (first col)))
|
||||||
|
" "
|
||||||
|
(or (get {:asc "ASC" :desc "DESC"} (second col))
|
||||||
|
(throw (IllegalArgumentException.
|
||||||
|
(str ":order-by " col
|
||||||
|
" expected :asc or :desc")))))
|
||||||
|
:else
|
||||||
|
(throw (IllegalArgumentException.
|
||||||
|
(str ":order-by expected keyword or keyword pair,"
|
||||||
|
" found: " col))))))
|
||||||
|
|
||||||
|
(defn- for-order
|
||||||
|
"Given an `:order-by` vector, return an `ORDER BY` clause."
|
||||||
|
[order-by opts]
|
||||||
|
(if (vector? order-by)
|
||||||
|
(str "ORDER BY "
|
||||||
|
(str/join ", " (map #(for-order-col % opts) order-by)))
|
||||||
|
(throw (IllegalArgumentException. ":order-by must be a vector"))))
|
||||||
|
|
||||||
(defn- for-query
|
(defn- for-query
|
||||||
"Given a table name and either a hash map of column names and values or a
|
"Given a table name and either a hash map of column names and values or a
|
||||||
vector of SQL (where clause) and its parameters, return a vector of the
|
vector of SQL (where clause) and its parameters, return a vector of the
|
||||||
full SELECT SQL string and its parameters.
|
full `SELECT` SQL string and its parameters.
|
||||||
|
|
||||||
Applies any :table-fn / :column-fn supplied in the options."
|
Applies any `:table-fn` / `:column-fn` supplied in the options."
|
||||||
[table where-params opts]
|
[table where-params opts]
|
||||||
(let [entity-fn (:table-fn opts identity)
|
(let [entity-fn (:table-fn opts identity)
|
||||||
where-params (if (map? where-params)
|
where-params (if (map? where-params)
|
||||||
|
|
@ -63,15 +95,17 @@
|
||||||
(into [(str "WHERE " (first where-params))]
|
(into [(str "WHERE " (first where-params))]
|
||||||
(rest where-params)))]
|
(rest where-params)))]
|
||||||
(into [(str "SELECT * FROM " (entity-fn (name table))
|
(into [(str "SELECT * FROM " (entity-fn (name table))
|
||||||
" " (first where-params))]
|
" " (first where-params)
|
||||||
|
(when-let [order-by (:order-by opts)]
|
||||||
|
(str " " (for-order order-by opts))))]
|
||||||
(rest where-params))))
|
(rest where-params))))
|
||||||
|
|
||||||
(defn- for-delete
|
(defn- for-delete
|
||||||
"Given a table name and either a hash map of column names and values or a
|
"Given a table name and either a hash map of column names and values or a
|
||||||
vector of SQL (where clause) and its parameters, return a vector of the
|
vector of SQL (where clause) and its parameters, return a vector of the
|
||||||
full DELETE SQL string and its parameters.
|
full `DELETE` SQL string and its parameters.
|
||||||
|
|
||||||
Applies any :table-fn / :column-fn supplied in the options."
|
Applies any `:table-fn` / `:column-fn` supplied in the options."
|
||||||
[table where-params opts]
|
[table where-params opts]
|
||||||
(let [entity-fn (:table-fn opts identity)
|
(let [entity-fn (:table-fn opts identity)
|
||||||
where-params (if (map? where-params)
|
where-params (if (map? where-params)
|
||||||
|
|
@ -85,10 +119,10 @@
|
||||||
(defn- for-update
|
(defn- for-update
|
||||||
"Given a table name, a vector of column names to set and their values, and
|
"Given a table name, a vector of column names to set and their values, and
|
||||||
either a hash map of column names and values or a vector of SQL (where clause)
|
either a hash map of column names and values or a vector of SQL (where clause)
|
||||||
and its parameters, return a vector of the full UPDATE SQL string and its
|
and its parameters, return a vector of the full `UPDATE` SQL string and its
|
||||||
parameters.
|
parameters.
|
||||||
|
|
||||||
Applies any :table-fn / :column-fn supplied in the options."
|
Applies any `:table-fn` / `:column-fn` supplied in the options."
|
||||||
[table key-map where-params opts]
|
[table key-map where-params opts]
|
||||||
(let [entity-fn (:table-fn opts identity)
|
(let [entity-fn (:table-fn opts identity)
|
||||||
set-params (by-keys key-map :set opts)
|
set-params (by-keys key-map :set opts)
|
||||||
|
|
@ -104,9 +138,9 @@
|
||||||
|
|
||||||
(defn- for-insert
|
(defn- for-insert
|
||||||
"Given a table name and a hash map of column names and their values,
|
"Given a table name and a hash map of column names and their values,
|
||||||
return a vector of the full INSERT SQL string and its parameters.
|
return a vector of the full `INSERT` SQL string and its parameters.
|
||||||
|
|
||||||
Applies any :table-fn / :column-fn supplied in the options."
|
Applies any `:table-fn` / `:column-fn` supplied in the options."
|
||||||
[table key-map opts]
|
[table key-map opts]
|
||||||
(let [entity-fn (:table-fn opts identity)
|
(let [entity-fn (:table-fn opts identity)
|
||||||
params (as-keys key-map opts)
|
params (as-keys key-map opts)
|
||||||
|
|
@ -118,10 +152,10 @@
|
||||||
|
|
||||||
(defn- for-insert-multi
|
(defn- for-insert-multi
|
||||||
"Given a table name, a vector of column names, and a vector of row values
|
"Given a table name, a vector of column names, and a vector of row values
|
||||||
(each row is a vector of its values), return a vector of the full INSERT
|
(each row is a vector of its values), return a vector of the full `INSERT`
|
||||||
SQL string and its parameters.
|
SQL string and its parameters.
|
||||||
|
|
||||||
Applies any :table-fn / :column-fn supplied in the options."
|
Applies any `:table-fn` / `:column-fn` supplied in the options."
|
||||||
[table cols rows opts]
|
[table cols rows opts]
|
||||||
(assert (apply = (count cols) (map count rows)))
|
(assert (apply = (count cols) (map count rows)))
|
||||||
(let [table-fn (:table-fn opts identity)
|
(let [table-fn (:table-fn opts identity)
|
||||||
|
|
@ -136,7 +170,7 @@
|
||||||
rows)))
|
rows)))
|
||||||
|
|
||||||
(defn insert!
|
(defn insert!
|
||||||
"Syntactic sugar over execute-one! to make inserting hash maps easier.
|
"Syntactic sugar over `execute-one!` to make inserting hash maps easier.
|
||||||
|
|
||||||
Given a connectable object, a table name, and a data hash map, inserts the
|
Given a connectable object, a table name, and a data hash map, inserts the
|
||||||
data as a single row in the database and attempts to return a map of generated
|
data as a single row in the database and attempts to return a map of generated
|
||||||
|
|
@ -149,7 +183,7 @@
|
||||||
(merge {:return-keys true} opts))))
|
(merge {:return-keys true} opts))))
|
||||||
|
|
||||||
(defn insert-multi!
|
(defn insert-multi!
|
||||||
"Syntactic sugar over execute! to make inserting columns/rows easier.
|
"Syntactic sugar over `execute!` to make inserting columns/rows easier.
|
||||||
|
|
||||||
Given a connectable object, a table name, a sequence of column names, and
|
Given a connectable object, a table name, a sequence of column names, and
|
||||||
a vector of rows of data (vectors of column values), inserts the data as
|
a vector of rows of data (vectors of column values), inserts the data as
|
||||||
|
|
@ -163,7 +197,7 @@
|
||||||
(merge {:return-keys true} opts))))
|
(merge {:return-keys true} opts))))
|
||||||
|
|
||||||
(defn query
|
(defn query
|
||||||
"Syntactic sugar over execute! to provide a query alias.
|
"Syntactic sugar over `execute!` to provide a query alias.
|
||||||
|
|
||||||
Given a connectable object, and a vector of SQL and its parameters,
|
Given a connectable object, and a vector of SQL and its parameters,
|
||||||
returns a vector of hash maps of rows that match."
|
returns a vector of hash maps of rows that match."
|
||||||
|
|
@ -173,10 +207,14 @@
|
||||||
(execute! connectable sql-params opts)))
|
(execute! connectable sql-params opts)))
|
||||||
|
|
||||||
(defn find-by-keys
|
(defn find-by-keys
|
||||||
"Syntactic sugar over execute! to make certain common queries easier.
|
"Syntactic sugar over `execute!` to make certain common queries easier.
|
||||||
|
|
||||||
Given a connectable object, a table name, and a hash map of columns and
|
Given a connectable object, a table name, and a hash map of columns and
|
||||||
their values, returns a vector of hash maps of rows that match."
|
their values, returns a vector of hash maps of rows that match.
|
||||||
|
|
||||||
|
If the `:order-by` option is present, add `ORDER BY` clause. `:order-by`
|
||||||
|
should be a vector of column names or pairs of column name / direction,
|
||||||
|
which can be `:asc` or `:desc`."
|
||||||
([connectable table key-map]
|
([connectable table key-map]
|
||||||
(find-by-keys connectable table key-map {}))
|
(find-by-keys connectable table key-map {}))
|
||||||
([connectable table key-map opts]
|
([connectable table key-map opts]
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,16 @@
|
||||||
|
|
||||||
(deftest test-for-query
|
(deftest test-for-query
|
||||||
(testing "by example"
|
(testing "by example"
|
||||||
(is (= (#'sql/for-query :user {:id 9} {:table-fn sql-server :column-fn mysql})
|
(is (= (#'sql/for-query :user {:id 9}
|
||||||
["SELECT * FROM [user] WHERE `id` = ?" 9]))
|
{:table-fn sql-server :column-fn mysql :order-by [:a [:b :desc]]})
|
||||||
|
["SELECT * FROM [user] WHERE `id` = ? ORDER BY `a`, `b` DESC" 9]))
|
||||||
(is (= (#'sql/for-query :user {:id nil} {:table-fn sql-server :column-fn mysql})
|
(is (= (#'sql/for-query :user {:id nil} {:table-fn sql-server :column-fn mysql})
|
||||||
["SELECT * FROM [user] WHERE `id` IS NULL"])))
|
["SELECT * FROM [user] WHERE `id` IS NULL"])))
|
||||||
(testing "by where clause"
|
(testing "by where clause"
|
||||||
(is (= (#'sql/for-query :user ["id = ? and opt is null" 9] {:table-fn sql-server :column-fn mysql})
|
(is (= (#'sql/for-query :user ["id = ? and opt is null" 9]
|
||||||
["SELECT * FROM [user] WHERE id = ? and opt is null" 9]))))
|
{:table-fn sql-server :column-fn mysql :order-by [:a [:b :desc]]})
|
||||||
|
[(str "SELECT * FROM [user] WHERE id = ? and opt is null"
|
||||||
|
" ORDER BY `a`, `b` DESC") 9]))))
|
||||||
|
|
||||||
(deftest test-for-delete
|
(deftest test-for-delete
|
||||||
(testing "by example"
|
(testing "by example"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue