Add docstrings to everything
This commit is contained in:
parent
8646472e79
commit
6e08557d92
9 changed files with 233 additions and 56 deletions
|
|
@ -1,3 +0,0 @@
|
||||||
# Introduction to next.jdbc
|
|
||||||
|
|
||||||
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
|
|
||||||
|
|
@ -1,7 +1,41 @@
|
||||||
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
||||||
|
|
||||||
(ns next.jdbc
|
(ns next.jdbc
|
||||||
""
|
"The public API of the next generation java.jdbc library.
|
||||||
|
|
||||||
|
The basic building blocks are the java.sql/javax.sql classes:
|
||||||
|
* DataSource -- something to get connections from,
|
||||||
|
* Connection -- an active connection to the database,
|
||||||
|
* PreparedStatement -- SQL and parameters combined, from a connection,
|
||||||
|
* reducible! -- given a connectable and SQL + parameters or a statement,
|
||||||
|
return a reducible that, when reduced will execute the SQL and consume
|
||||||
|
the ResultSet produced,
|
||||||
|
* execute! -- given a connectable and SQL + parameters or a statement,
|
||||||
|
execute the SQL, consume the ResultSet produced, and return a vector
|
||||||
|
of hash maps representing the rows; this can be datafied to allow
|
||||||
|
navigation of foreign keys into other tables (either by convention or
|
||||||
|
via a schema definition).
|
||||||
|
* with-transaction -- execute a series of SQL operations within a transaction.
|
||||||
|
|
||||||
|
In addition, there are some utility functions that make common operations
|
||||||
|
easier by providing some syntactic sugar over 'execute!'.
|
||||||
|
|
||||||
|
The following options are supported generally:
|
||||||
|
* :entities -- specify a function used to convert strings to SQL entity names
|
||||||
|
(to turn table and column names into appropriate SQL names),
|
||||||
|
* :identifiers -- specify a function used to convert SQL entity (column)
|
||||||
|
names to Clojure names (that are then turned into keywords),
|
||||||
|
* :row-fn -- when consuming a ResultSet, apply this function to each row of
|
||||||
|
data; defaults to a function that produces a datafiable hash map.
|
||||||
|
|
||||||
|
The following options are supported where a PreparedStatement is created:
|
||||||
|
* :concurrency -- :read-only, :updatable,
|
||||||
|
* :cursors -- :close, :hold
|
||||||
|
* :fetch-size -- the fetch size value,
|
||||||
|
* :max-rows -- the maximum number of rows to return,
|
||||||
|
* :result-type -- :forward-only, :scroll-insensitive, :scroll-sensitive,
|
||||||
|
* :return-keys -- either true or a vector of key names to return,
|
||||||
|
* :timeout -- the query timeout."
|
||||||
(:require [next.jdbc.connection] ; used to extend protocols
|
(:require [next.jdbc.connection] ; used to extend protocols
|
||||||
[next.jdbc.prepare :as prepare] ; used to extend protocols
|
[next.jdbc.prepare :as prepare] ; used to extend protocols
|
||||||
[next.jdbc.protocols :as p]
|
[next.jdbc.protocols :as p]
|
||||||
|
|
@ -11,11 +45,31 @@
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
(defn get-datasource [spec] (p/get-datasource spec))
|
(defn get-datasource
|
||||||
|
"Given some sort of specification of a database, return a DataSource."
|
||||||
|
[spec]
|
||||||
|
(p/get-datasource spec))
|
||||||
|
|
||||||
(defn get-connection [spec opts] (p/get-connection spec opts))
|
(defn get-connection
|
||||||
|
"Given some sort of specification of a database, return a new Connection.
|
||||||
|
|
||||||
(defn prepare [spec sql-params opts] (p/prepare spec sql-params opts))
|
In general, this should be used via with-open:
|
||||||
|
|
||||||
|
(with-open [con (get-connection spec opts)]
|
||||||
|
(run-some-ops con))"
|
||||||
|
[spec opts]
|
||||||
|
(p/get-connection spec opts))
|
||||||
|
|
||||||
|
(defn prepare
|
||||||
|
"Given some sort of specification of a database, and a vector containing
|
||||||
|
SQL and any parameters it needs, return a new PreparedStatement.
|
||||||
|
|
||||||
|
In general, this should be used via with-open:
|
||||||
|
|
||||||
|
(with-open [stmt (prepare spec sql-params opts)]
|
||||||
|
(run-some-ops stmt))"
|
||||||
|
[spec sql-params opts]
|
||||||
|
(p/prepare spec sql-params opts))
|
||||||
|
|
||||||
(defn reducible!
|
(defn reducible!
|
||||||
"General SQL execution function.
|
"General SQL execution function.
|
||||||
|
|
@ -26,7 +80,9 @@
|
||||||
(p/-execute connectable sql-params opts)))
|
(p/-execute connectable sql-params opts)))
|
||||||
|
|
||||||
(defn execute!
|
(defn execute!
|
||||||
""
|
"General SQL execution function.
|
||||||
|
|
||||||
|
Invokes 'reducible!' and then reduces that into a vector of hash maps."
|
||||||
([stmt]
|
([stmt]
|
||||||
(rs/execute! stmt [] {}))
|
(rs/execute! stmt [] {}))
|
||||||
([connectable sql-params]
|
([connectable sql-params]
|
||||||
|
|
@ -35,7 +91,9 @@
|
||||||
(rs/execute! connectable sql-params opts)))
|
(rs/execute! connectable sql-params opts)))
|
||||||
|
|
||||||
(defn execute-one!
|
(defn execute-one!
|
||||||
""
|
"General SQL execution function that returns just the first row of a result.
|
||||||
|
|
||||||
|
Invokes 'reducible!' but immediately returns the first row."
|
||||||
([stmt]
|
([stmt]
|
||||||
(rs/execute-one! stmt [] {}))
|
(rs/execute-one! stmt [] {}))
|
||||||
([connectable sql-params]
|
([connectable sql-params]
|
||||||
|
|
@ -44,11 +102,22 @@
|
||||||
(rs/execute-one! connectable sql-params opts)))
|
(rs/execute-one! connectable sql-params opts)))
|
||||||
|
|
||||||
(defmacro with-transaction
|
(defmacro with-transaction
|
||||||
|
"Given a connectable object, gets a new connection and binds it to 'sym',
|
||||||
|
then executes the 'body' in that context, committing any changes if the body
|
||||||
|
completes successfully, otherwise rolling back any changes made.
|
||||||
|
|
||||||
|
The options map supports:
|
||||||
|
* isolation -- :none, :read-committed, :read-uncommitted, :repeatable-read,
|
||||||
|
:serializable,
|
||||||
|
* :read-only -- true / false,
|
||||||
|
* :rollback-only -- true / false."
|
||||||
[[sym connectable opts] & body]
|
[[sym connectable opts] & body]
|
||||||
`(p/-transact ~connectable (fn [~sym] ~@body) ~opts))
|
`(p/-transact ~connectable (fn [~sym] ~@body) ~opts))
|
||||||
|
|
||||||
(defn insert!
|
(defn insert!
|
||||||
""
|
"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
|
||||||
|
keys."
|
||||||
([connectable table key-map]
|
([connectable table key-map]
|
||||||
(rs/execute! connectable
|
(rs/execute! connectable
|
||||||
(sql/for-insert table key-map {})
|
(sql/for-insert table key-map {})
|
||||||
|
|
@ -59,7 +128,10 @@
|
||||||
(merge {:return-keys true} opts))))
|
(merge {:return-keys true} opts))))
|
||||||
|
|
||||||
(defn insert-multi!
|
(defn insert-multi!
|
||||||
""
|
"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
|
||||||
|
multiple rows in the database and attempts to return a vector of maps of
|
||||||
|
generated keys."
|
||||||
([connectable table cols rows]
|
([connectable table cols rows]
|
||||||
(rs/execute! connectable
|
(rs/execute! connectable
|
||||||
(sql/for-insert-multi table cols rows {})
|
(sql/for-insert-multi table cols rows {})
|
||||||
|
|
@ -70,14 +142,19 @@
|
||||||
(merge {:return-keys true} opts))))
|
(merge {:return-keys true} opts))))
|
||||||
|
|
||||||
(defn find-by-keys
|
(defn find-by-keys
|
||||||
""
|
"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."
|
||||||
([connectable table key-map]
|
([connectable table key-map]
|
||||||
(rs/execute! connectable (sql/for-query table key-map {}) {}))
|
(rs/execute! connectable (sql/for-query table key-map {}) {}))
|
||||||
([connectable table key-map opts]
|
([connectable table key-map opts]
|
||||||
(rs/execute! connectable (sql/for-query table key-map opts) opts)))
|
(rs/execute! connectable (sql/for-query table key-map opts) opts)))
|
||||||
|
|
||||||
(defn get-by-id
|
(defn get-by-id
|
||||||
""
|
"Given a connectable object, a table name, and a primary key value, returns
|
||||||
|
a hash map of the first row that matches.
|
||||||
|
|
||||||
|
By default, the primary key is assumed to be 'id' but that can be overridden
|
||||||
|
in the five-argument call."
|
||||||
([connectable table pk]
|
([connectable table pk]
|
||||||
(rs/execute-one! connectable (sql/for-query table {:id pk} {}) {}))
|
(rs/execute-one! connectable (sql/for-query table {:id pk} {}) {}))
|
||||||
([connectable table pk opts]
|
([connectable table pk opts]
|
||||||
|
|
@ -86,14 +163,18 @@
|
||||||
(rs/execute-one! connectable (sql/for-query table {pk-name pk} opts) opts)))
|
(rs/execute-one! connectable (sql/for-query table {pk-name pk} opts) opts)))
|
||||||
|
|
||||||
(defn update!
|
(defn update!
|
||||||
""
|
"Given a connectable object, a table name, a hash map of columns and values
|
||||||
|
to set, and either a hash map of columns and values to search on or a vector
|
||||||
|
of a SQL where clause and parameters, perform an update on the table."
|
||||||
([connectable table key-map where-params]
|
([connectable table key-map where-params]
|
||||||
(rs/execute! connectable (sql/for-update table key-map where-params {}) {}))
|
(rs/execute! connectable (sql/for-update table key-map where-params {}) {}))
|
||||||
([connectable table key-map where-params opts]
|
([connectable table key-map where-params opts]
|
||||||
(rs/execute! connectable (sql/for-update table key-map where-params opts) opts)))
|
(rs/execute! connectable (sql/for-update table key-map where-params opts) opts)))
|
||||||
|
|
||||||
(defn delete!
|
(defn delete!
|
||||||
""
|
"Given a connectable object, a table name, and either a hash map of columns
|
||||||
|
and values to search on or a vector of a SQL where clause and parameters,
|
||||||
|
perform a delete on the table."
|
||||||
([connectable table where-params]
|
([connectable table where-params]
|
||||||
(rs/execute! connectable (sql/for-delete table where-params {}) {}))
|
(rs/execute! connectable (sql/for-delete table where-params {}) {}))
|
||||||
([connectable table where-params opts]
|
([connectable table where-params opts]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
||||||
|
|
||||||
(ns next.jdbc.connection
|
(ns next.jdbc.connection
|
||||||
""
|
"Standard implementations of get-datasource and get-connection."
|
||||||
(:require [next.jdbc.protocols :as p])
|
(:require [next.jdbc.protocols :as p])
|
||||||
(:import (java.sql Connection DriverManager)
|
(:import (java.sql Connection DriverManager)
|
||||||
(javax.sql DataSource)
|
(javax.sql DataSource)
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
(DriverManager/getConnection url (as-properties etc)))
|
(DriverManager/getConnection url (as-properties etc)))
|
||||||
|
|
||||||
(defn- spec->url+etc
|
(defn- spec->url+etc
|
||||||
""
|
"Given a database spec, return a JDBC URL and a map of any additional options."
|
||||||
[{:keys [dbtype dbname host port classname] :as db-spec}]
|
[{:keys [dbtype dbname host port classname] :as db-spec}]
|
||||||
(let [;; allow aliases for dbtype
|
(let [;; allow aliases for dbtype
|
||||||
subprotocol (aliases dbtype dbtype)
|
subprotocol (aliases dbtype dbtype)
|
||||||
|
|
@ -118,12 +118,13 @@
|
||||||
[url etc]))
|
[url etc]))
|
||||||
|
|
||||||
(defn- string->url+etc
|
(defn- string->url+etc
|
||||||
""
|
"Given a JDBC URL, return it with an empty set of options with no parsing."
|
||||||
[s]
|
[s]
|
||||||
[s {}])
|
[s {}])
|
||||||
|
|
||||||
(defn- url+etc->datasource
|
(defn- url+etc->datasource
|
||||||
""
|
"Given a JDBC URL and a map of options, return a DataSource that can be
|
||||||
|
used to obtain a new database connection."
|
||||||
[[url etc]]
|
[[url etc]]
|
||||||
(reify DataSource
|
(reify DataSource
|
||||||
(getConnection [_]
|
(getConnection [_]
|
||||||
|
|
@ -136,14 +137,19 @@
|
||||||
|
|
||||||
(defn- make-connection
|
(defn- make-connection
|
||||||
"Given a DataSource and a map of options, get a connection and update it
|
"Given a DataSource and a map of options, get a connection and update it
|
||||||
as specified by the options."
|
as specified by the options.
|
||||||
|
|
||||||
|
The options supported are:
|
||||||
|
* :auto-commit -- whether the connection should be set to auto-commit or not;
|
||||||
|
without this option, the defaut is true -- connections will auto-commit,
|
||||||
|
* :read-only -- whether the connection should be set to read-only mode."
|
||||||
^Connection
|
^Connection
|
||||||
[^DataSource datasource opts]
|
[^DataSource datasource opts]
|
||||||
(let [^Connection connection (.getConnection datasource)]
|
(let [^Connection connection (.getConnection datasource)]
|
||||||
(when (contains? opts :auto-commit?)
|
(when (contains? opts :auto-commit)
|
||||||
(.setAutoCommit connection (boolean (:auto-commit? opts))))
|
(.setAutoCommit connection (boolean (:auto-commit opts))))
|
||||||
(when (contains? opts :read-only?)
|
(when (contains? opts :read-only)
|
||||||
(.setReadOnly connection (boolean (:read-only? opts))))
|
(.setReadOnly connection (boolean (:read-only opts))))
|
||||||
connection))
|
connection))
|
||||||
|
|
||||||
(extend-protocol p/Sourceable
|
(extend-protocol p/Sourceable
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
||||||
|
|
||||||
(ns next.jdbc.prepare
|
(ns next.jdbc.prepare
|
||||||
""
|
"Mostly an implementation namespace for how PreparedStatement objects are
|
||||||
|
created by the next generation java.jdbc library.
|
||||||
|
|
||||||
|
set-parameters is public and may be useful if you have a PreparedStatement
|
||||||
|
that you wish to reuse and (re)set the parameters on it."
|
||||||
(:require [next.jdbc.protocols :as p])
|
(:require [next.jdbc.protocols :as p])
|
||||||
(:import (java.sql Connection
|
(:import (java.sql Connection
|
||||||
PreparedStatement
|
PreparedStatement
|
||||||
|
|
@ -11,7 +15,10 @@
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
(defn set-parameters
|
(defn set-parameters
|
||||||
""
|
"Given a PreparedStatement and a vector of parameter values, update the
|
||||||
|
PreparedStatement with those parameters and return it.
|
||||||
|
|
||||||
|
Currently uses .setObject with no possibility of an override."
|
||||||
^java.sql.PreparedStatement
|
^java.sql.PreparedStatement
|
||||||
[^PreparedStatement ps params]
|
[^PreparedStatement ps params]
|
||||||
(when (seq params)
|
(when (seq params)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,31 @@
|
||||||
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
||||||
|
|
||||||
(ns next.jdbc.protocols
|
(ns next.jdbc.protocols
|
||||||
"")
|
"This is the extensible core of the next generation java.jdbc library.
|
||||||
|
|
||||||
|
get-datasource -- turn something into a javax.sql.DataSource; implementations
|
||||||
|
are provided for strings, hash maps (db-spec structures), and also a
|
||||||
|
DataSource (which just returns itself).
|
||||||
|
|
||||||
|
get-connection -- create a new JDBC connection that should be closed when you
|
||||||
|
are finished with it; implementations are provided for DataSource and
|
||||||
|
Object, on the assumption that an Object can possibly be turned into a
|
||||||
|
DataSource.
|
||||||
|
|
||||||
|
-execute -- given SQL and parameters, produce a 'reducible' that, when
|
||||||
|
reduced, executes the SQL and produces a ResultSet that can be processed;
|
||||||
|
implementations are provided for Connection, DataSource,
|
||||||
|
PreparedStatement, and Object (on the assumption that an Object can be
|
||||||
|
turned into a DataSource and therefore used to get a Connection).
|
||||||
|
|
||||||
|
prepare -- given SQL and parameters, produce a PreparedStatement that can
|
||||||
|
be executed (by -execute above); implementation is provided for
|
||||||
|
Connection.
|
||||||
|
|
||||||
|
-transact -- given a function (presumably containing SQL operations),
|
||||||
|
run the function inside a SQL transaction; implementations are provided
|
||||||
|
for Connection, DataSource, and Object (on the assumption that an Object
|
||||||
|
can be turned into a DataSource).")
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
||||||
|
|
||||||
(ns next.jdbc.result-set
|
(ns next.jdbc.result-set
|
||||||
""
|
"An implementation of ResultSet handling functions."
|
||||||
(:require [clojure.core.protocols :as core-p]
|
(:require [clojure.core.protocols :as core-p]
|
||||||
[next.jdbc.prepare :as prepare]
|
[next.jdbc.prepare :as prepare]
|
||||||
[next.jdbc.protocols :as p])
|
[next.jdbc.protocols :as p])
|
||||||
|
|
@ -12,7 +12,11 @@
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
(defn- get-column-names
|
(defn- get-column-names
|
||||||
""
|
"Given a ResultSet, return a vector of columns names, each qualified by
|
||||||
|
the table from which it came.
|
||||||
|
|
||||||
|
If :identifiers was specified, apply that to both the table qualifier
|
||||||
|
and the column name."
|
||||||
[^ResultSet rs opts]
|
[^ResultSet rs opts]
|
||||||
(let [^ResultSetMetaData rsmeta (.getMetaData rs)
|
(let [^ResultSetMetaData rsmeta (.getMetaData rs)
|
||||||
idxs (range 1 (inc (.getColumnCount rsmeta)))]
|
idxs (range 1 (inc (.getColumnCount rsmeta)))]
|
||||||
|
|
@ -75,7 +79,13 @@
|
||||||
(range 1 (inc (count @cols)))))))))
|
(range 1 (inc (count @cols)))))))))
|
||||||
|
|
||||||
(defn- reduce-stmt
|
(defn- reduce-stmt
|
||||||
""
|
"Execute the PreparedStatement, attempt to get either its ResultSet or
|
||||||
|
its generated keys (as a ResultSet), and reduce that using the supplied
|
||||||
|
function and initial value.
|
||||||
|
|
||||||
|
If the statement yields neither a ResultSet nor generated keys, return
|
||||||
|
a hash map containing ::update-count and the number of rows updated,
|
||||||
|
with the supplied function and initial value applied."
|
||||||
[^PreparedStatement stmt f init opts]
|
[^PreparedStatement stmt f init opts]
|
||||||
(if-let [^ResultSet rs (if (.execute stmt)
|
(if-let [^ResultSet rs (if (.execute stmt)
|
||||||
(.getResultSet stmt)
|
(.getResultSet stmt)
|
||||||
|
|
@ -130,19 +140,27 @@
|
||||||
(declare navize-row)
|
(declare navize-row)
|
||||||
|
|
||||||
(defn datafiable-row
|
(defn datafiable-row
|
||||||
|
"Given a connectable object, return a function that knows how to turn a row
|
||||||
|
into a datafiable object that can be 'nav'igated."
|
||||||
[connectable opts]
|
[connectable opts]
|
||||||
(fn [row]
|
(fn [row]
|
||||||
(into (with-meta {} {`core-p/datafy (navize-row connectable opts)}) row)))
|
(into (with-meta {} {`core-p/datafy (navize-row connectable opts)}) row)))
|
||||||
|
|
||||||
(defn execute!
|
(defn execute!
|
||||||
""
|
"Given a connectable object and SQL and parameters, execute it and reduce it
|
||||||
|
into a vector of processed hash maps (rows).
|
||||||
|
|
||||||
|
By default, this will create datafiable rows but :row-fn can override that."
|
||||||
[connectable sql-params opts]
|
[connectable sql-params opts]
|
||||||
(into []
|
(into []
|
||||||
(map (or (:row-fn opts) (datafiable-row connectable opts)))
|
(map (or (:row-fn opts) (datafiable-row connectable opts)))
|
||||||
(p/-execute connectable sql-params opts)))
|
(p/-execute connectable sql-params opts)))
|
||||||
|
|
||||||
(defn execute-one!
|
(defn execute-one!
|
||||||
""
|
"Given a connectable object and SQL and parameters, execute it and return
|
||||||
|
just the first processed hash map (row).
|
||||||
|
|
||||||
|
By default, this will create a datafiable row but :row-fn can override that."
|
||||||
[connectable sql-params opts]
|
[connectable sql-params opts]
|
||||||
(let [row-fn (or (:row-fn opts) (datafiable-row connectable opts))]
|
(let [row-fn (or (:row-fn opts) (datafiable-row connectable opts))]
|
||||||
(reduce (fn [_ row]
|
(reduce (fn [_ row]
|
||||||
|
|
@ -161,7 +179,22 @@
|
||||||
[(keyword table) :id])))
|
[(keyword table) :id])))
|
||||||
|
|
||||||
(defn- navize-row
|
(defn- navize-row
|
||||||
""
|
"Given a connectable object, return a function that knows how to turn a row
|
||||||
|
into a navigable object.
|
||||||
|
|
||||||
|
A :schema option can provide a map of qualified column names (:table/column)
|
||||||
|
to tuples that indicate which table they are a foreign key for, the name of
|
||||||
|
the key within that table, and (optionality) the cardinality of that
|
||||||
|
relationship (:many, :one).
|
||||||
|
|
||||||
|
If no :schema item is provided for a column, the convention of <table>id or
|
||||||
|
<table>_id is used, and the assumption is that such columns are foreign keys
|
||||||
|
in the <table> portion of their name, the key is called 'id', and the
|
||||||
|
cardinality is :one.
|
||||||
|
|
||||||
|
Rows are looked up using 'execute!' or 'execute-one!' and the :entities
|
||||||
|
function, if provided, is applied to both the assumed table name and the
|
||||||
|
assumed foreign key column name."
|
||||||
[connectable opts]
|
[connectable opts]
|
||||||
(fn [row]
|
(fn [row]
|
||||||
(with-meta row
|
(with-meta row
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,16 @@
|
||||||
|
|
||||||
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 clojure.java.jdbc
|
||||||
(insert!, update!, delete!, etc). For anything more complex, use a library
|
(insert!, update!, delete!, etc). For anything more complex, use a library
|
||||||
like HoneySQL https://github.com/jkk/honeysql to generate SQL + parameters."
|
like HoneySQL https://github.com/jkk/honeysql to generate SQL + parameters.
|
||||||
|
|
||||||
|
This is primarily intended to be an implementation detail."
|
||||||
(:require [clojure.string :as str]))
|
(:require [clojure.string :as str]))
|
||||||
|
|
||||||
(defn by-keys
|
(defn by-keys
|
||||||
""
|
"Given a hash map of column names and values and a clause type (:set, :where),
|
||||||
|
return a vector of a SQL clause and its parameters.
|
||||||
|
|
||||||
|
Applies any :entities function supplied in the options."
|
||||||
[key-map clause opts]
|
[key-map clause opts]
|
||||||
(let [entity-fn (:entities opts identity)
|
(let [entity-fn (:entities opts identity)
|
||||||
[where params] (reduce-kv (fn [[conds params] k v]
|
[where params] (reduce-kv (fn [[conds params] k v]
|
||||||
|
|
@ -25,17 +30,25 @@
|
||||||
params)))
|
params)))
|
||||||
|
|
||||||
(defn as-keys
|
(defn as-keys
|
||||||
""
|
"Given a hash map of column names and values, return a string of all the
|
||||||
|
column names.
|
||||||
|
|
||||||
|
Applies any :entities function supplied in the options."
|
||||||
[key-map opts]
|
[key-map opts]
|
||||||
(str/join ", " (map (comp (:entities opts identity) name) (keys key-map))))
|
(str/join ", " (map (comp (:entities 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,
|
||||||
|
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-query
|
(defn for-query
|
||||||
""
|
"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
|
||||||
|
full SELECT SQL string and its parameters.
|
||||||
|
|
||||||
|
Applies any :entities function supplied in the options."
|
||||||
[table where-params opts]
|
[table where-params opts]
|
||||||
(let [entity-fn (:entities opts identity)
|
(let [entity-fn (:entities opts identity)
|
||||||
where-params (if (map? where-params)
|
where-params (if (map? where-params)
|
||||||
|
|
@ -47,7 +60,11 @@
|
||||||
(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
|
||||||
|
vector of SQL (where clause) and its parameters, return a vector of the
|
||||||
|
full DELETE SQL string and its parameters.
|
||||||
|
|
||||||
|
Applies any :entities function supplied in the options."
|
||||||
[table where-params opts]
|
[table where-params opts]
|
||||||
(let [entity-fn (:entities opts identity)
|
(let [entity-fn (:entities opts identity)
|
||||||
where-params (if (map? where-params)
|
where-params (if (map? where-params)
|
||||||
|
|
@ -59,7 +76,12 @@
|
||||||
(rest where-params))))
|
(rest where-params))))
|
||||||
|
|
||||||
(defn for-update
|
(defn for-update
|
||||||
""
|
"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)
|
||||||
|
and its parameters, return a vector of the full UPDATE SQL string and its
|
||||||
|
parameters.
|
||||||
|
|
||||||
|
Applies any :entities function supplied in the options."
|
||||||
[table key-map where-params opts]
|
[table key-map where-params opts]
|
||||||
(let [entity-fn (:entities opts identity)
|
(let [entity-fn (:entities opts identity)
|
||||||
set-params (by-keys key-map :set opts)
|
set-params (by-keys key-map :set opts)
|
||||||
|
|
@ -74,7 +96,10 @@
|
||||||
(into (rest where-params)))))
|
(into (rest where-params)))))
|
||||||
|
|
||||||
(defn for-insert
|
(defn for-insert
|
||||||
""
|
"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.
|
||||||
|
|
||||||
|
Applies any :entities function supplied in the options."
|
||||||
[table key-map opts]
|
[table key-map opts]
|
||||||
(let [entity-fn (:entities opts identity)
|
(let [entity-fn (:entities opts identity)
|
||||||
params (as-keys key-map opts)
|
params (as-keys key-map opts)
|
||||||
|
|
@ -85,7 +110,11 @@
|
||||||
(vals key-map))))
|
(vals key-map))))
|
||||||
|
|
||||||
(defn for-insert-multi
|
(defn for-insert-multi
|
||||||
""
|
"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
|
||||||
|
SQL string and its parameters.
|
||||||
|
|
||||||
|
Applies any :entities function 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 [entity-fn (:entities opts identity)
|
(let [entity-fn (:entities opts identity)
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,19 @@
|
||||||
(defn- transact*
|
(defn- transact*
|
||||||
""
|
""
|
||||||
[^Connection con f opts]
|
[^Connection con f opts]
|
||||||
(let [{:keys [isolation read-only? rollback-only?]} opts
|
(let [{:keys [isolation read-only rollback-only]} opts
|
||||||
old-autocommit (.getAutoCommit con)
|
old-autocommit (.getAutoCommit con)
|
||||||
old-isolation (.getTransactionIsolation con)
|
old-isolation (.getTransactionIsolation con)
|
||||||
old-readonly (.isReadOnly con)]
|
old-readonly (.isReadOnly con)]
|
||||||
(io!
|
(io!
|
||||||
(when isolation
|
(when isolation
|
||||||
(.setTransactionIsolation con (isolation isolation-levels)))
|
(.setTransactionIsolation con (isolation isolation-levels)))
|
||||||
(when read-only?
|
(when read-only
|
||||||
(.setReadOnly con true))
|
(.setReadOnly con true))
|
||||||
(.setAutoCommit con false)
|
(.setAutoCommit con false)
|
||||||
(try
|
(try
|
||||||
(let [result (f con)]
|
(let [result (f con)]
|
||||||
(if rollback-only?
|
(if rollback-only
|
||||||
(.rollback con)
|
(.rollback con)
|
||||||
(.commit con))
|
(.commit con))
|
||||||
result)
|
result)
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
(try
|
(try
|
||||||
(.setTransactionIsolation con old-isolation)
|
(.setTransactionIsolation con old-isolation)
|
||||||
(catch Exception _)))
|
(catch Exception _)))
|
||||||
(when read-only?
|
(when read-only
|
||||||
(try
|
(try
|
||||||
(.setReadOnly con old-readonly)
|
(.setReadOnly con old-readonly)
|
||||||
(catch Exception _))))))))
|
(catch Exception _))))))))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
(ns next.jdbc-test
|
(ns next.jdbc-test
|
||||||
|
"Not exactly a test suite -- more a series of examples."
|
||||||
(:require [clojure.test :refer [deftest is testing]]
|
(:require [clojure.test :refer [deftest is testing]]
|
||||||
[next.jdbc :refer :all]
|
[next.jdbc :refer :all]
|
||||||
[next.jdbc.result-set :as rs]))
|
[next.jdbc.result-set :as rs]))
|
||||||
|
|
@ -9,17 +10,15 @@
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(def db-spec {:dbtype "h2:mem" :dbname "perf"})
|
(def db-spec {:dbtype "h2:mem" :dbname "perf"})
|
||||||
(def db-spec {:dbtype "derby" :dbname "perf" :create true})
|
;; these should be equivalent
|
||||||
(def db-spec {:dbtype "mysql" :dbname "worldsingles" :user "root" :password "visual"})
|
|
||||||
(def con db-spec)
|
|
||||||
(def con (get-datasource db-spec))
|
|
||||||
(get-connection con {})
|
|
||||||
(def con (get-connection (get-datasource db-spec) {}))
|
(def con (get-connection (get-datasource db-spec) {}))
|
||||||
(def con (get-connection db-spec {}))
|
(def con (get-connection db-spec {}))
|
||||||
(execute! con ["DROP TABLE fruit"])
|
(execute! con ["DROP TABLE fruit"])
|
||||||
;; h2
|
;; h2
|
||||||
(execute! con ["CREATE TABLE fruit (id int default 0, name varchar(32) primary key, appearance varchar(32), cost int, grade real)"])
|
(execute! con ["CREATE TABLE fruit (id int default 0, name varchar(32) primary key, appearance varchar(32), cost int, grade real)"])
|
||||||
|
;; either this...
|
||||||
(execute! con ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (1,'Apple','red',59,87), (2,'Banana','yellow',29,92.2), (3,'Peach','fuzzy',139,90.0), (4,'Orange','juicy',89,88.6)"])
|
(execute! con ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (1,'Apple','red',59,87), (2,'Banana','yellow',29,92.2), (3,'Peach','fuzzy',139,90.0), (4,'Orange','juicy',89,88.6)"])
|
||||||
|
;; ...or this
|
||||||
(insert-multi! con :fruit [:id :name :appearance :cost :grade]
|
(insert-multi! con :fruit [:id :name :appearance :cost :grade]
|
||||||
[[1 "Apple" "red" 59 87]
|
[[1 "Apple" "red" 59 87]
|
||||||
[2,"Banana","yellow",29,92.2]
|
[2,"Banana","yellow",29,92.2]
|
||||||
|
|
@ -30,7 +29,7 @@
|
||||||
(execute! con ["CREATE TABLE fruit (id int auto_increment, name varchar(32), appearance varchar(32), cost int, grade real, primary key (id))"])
|
(execute! con ["CREATE TABLE fruit (id int auto_increment, name varchar(32), appearance varchar(32), cost int, grade real, primary key (id))"])
|
||||||
(execute! con ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (1,'Apple','red',59,87), (2,'Banana','yellow',29,92.2), (3,'Peach','fuzzy',139,90.0), (4,'Orange','juicy',89,88.6)"]
|
(execute! con ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (1,'Apple','red',59,87), (2,'Banana','yellow',29,92.2), (3,'Peach','fuzzy',139,90.0), (4,'Orange','juicy',89,88.6)"]
|
||||||
{:return-keys true})
|
{:return-keys true})
|
||||||
|
;; when you're done
|
||||||
(.close con)
|
(.close con)
|
||||||
|
|
||||||
(require '[criterium.core :refer [bench quick-bench]])
|
(require '[criterium.core :refer [bench quick-bench]])
|
||||||
|
|
@ -64,7 +63,9 @@
|
||||||
(execute! con ["select * from fruit where appearance = ?" "red"]))
|
(execute! con ["select * from fruit where appearance = ?" "red"]))
|
||||||
|
|
||||||
(execute! con ["select * from fruit"])
|
(execute! con ["select * from fruit"])
|
||||||
|
;; this is not quite equivalent
|
||||||
(into [] (map (partial into {})) (reducible! con ["select * from fruit"]))
|
(into [] (map (partial into {})) (reducible! con ["select * from fruit"]))
|
||||||
|
;; but this is (equivalent to execute!)
|
||||||
(into [] (map (rs/datafiable-row con {})) (reducible! con ["select * from fruit"]))
|
(into [] (map (rs/datafiable-row con {})) (reducible! con ["select * from fruit"]))
|
||||||
|
|
||||||
;; with a prepopulated prepared statement
|
;; with a prepopulated prepared statement
|
||||||
|
|
@ -105,11 +106,10 @@
|
||||||
["select * from fruit where appearance = ?" "red"]
|
["select * from fruit where appearance = ?" "red"]
|
||||||
{:row-fn #(assoc % :test :value)})
|
{:row-fn #(assoc % :test :value)})
|
||||||
|
|
||||||
(with-transaction [t con {:rollback-only? true}]
|
(with-transaction [t con {:rollback-only true}]
|
||||||
(execute! t ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (5,'Pear','green',49,47)"])
|
(execute! t ["INSERT INTO fruit (id,name,appearance,cost,grade) VALUES (5,'Pear','green',49,47)"])
|
||||||
(execute! t ["select * from fruit where name = ?" "Pear"]))
|
(execute! t ["select * from fruit where name = ?" "Pear"]))
|
||||||
(execute! con ["select * from fruit where name = ?" "Pear"])
|
(execute! con ["select * from fruit where name = ?" "Pear"])
|
||||||
(delete! con :fruit {:id 1})
|
|
||||||
|
|
||||||
(update! con :fruit {:appearance "Brown"} {:name "Banana"})
|
(delete! con :fruit {:id 1})
|
||||||
(execute! con ["select * from membership"]))
|
(update! con :fruit {:appearance "Brown"} {:name "Banana"}))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue