2019-01-08 07:03:20 +00:00
|
|
|
;; copyright (c) 2018-2019 Sean Corfield, all rights reserved
|
2019-01-08 04:38:58 +00:00
|
|
|
|
2019-01-08 07:03:20 +00:00
|
|
|
(ns next.jdbc
|
2019-04-01 06:17:12 +00:00
|
|
|
"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,
|
2019-04-11 04:46:38 +00:00
|
|
|
* PreparedStatement -- SQL and parameters combined, from a connection,
|
2019-04-02 04:31:38 +00:00
|
|
|
and the following two functions and a macro:
|
2019-04-01 06:17:12 +00:00
|
|
|
* 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
|
2019-04-02 04:31:38 +00:00
|
|
|
(to turn table and column names into appropriate SQL names -- see the
|
|
|
|
|
next.jdbc.quoted namespace for the most common ones you might need),
|
2019-04-01 06:17:12 +00:00
|
|
|
* :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."
|
2019-03-31 23:54:34 +00:00
|
|
|
(:require [next.jdbc.connection] ; used to extend protocols
|
|
|
|
|
[next.jdbc.prepare :as prepare] ; used to extend protocols
|
|
|
|
|
[next.jdbc.protocols :as p]
|
|
|
|
|
[next.jdbc.result-set :as rs]
|
2019-04-01 02:30:01 +00:00
|
|
|
[next.jdbc.sql :as sql]
|
2019-04-01 01:22:04 +00:00
|
|
|
[next.jdbc.transaction])) ; used to extend protocols
|
2019-01-08 07:03:20 +00:00
|
|
|
|
|
|
|
|
(set! *warn-on-reflection* true)
|
|
|
|
|
|
2019-04-01 06:17:12 +00:00
|
|
|
(defn get-datasource
|
2019-04-02 04:31:38 +00:00
|
|
|
"Given some sort of specification of a database, return a DataSource.
|
|
|
|
|
|
|
|
|
|
A specification can be a JDBC URL string (which is passed to the JDBC
|
|
|
|
|
driver as-is), or a hash map. For the hash map, these keys are required:
|
|
|
|
|
* :dbtype -- a string indicating the type of the database
|
|
|
|
|
* :dbname -- a string indicating the name of the database to be used
|
|
|
|
|
|
|
|
|
|
The following optional keys are commonly used:
|
|
|
|
|
* :user -- the username to authenticate with
|
|
|
|
|
* :password -- the password to authenticate with
|
|
|
|
|
* :host -- the hostname or IP address of the database (default: 127.0.0.1)
|
|
|
|
|
* :port -- the port for the database connection (the default is database-
|
|
|
|
|
specific -- see below)
|
|
|
|
|
* :classname -- if you need to override the default for the :dbtype
|
|
|
|
|
(or you want to use a database that next.jdbc does not know about!)
|
|
|
|
|
|
|
|
|
|
Any additional options provided will be passed to the JDBC driver's
|
|
|
|
|
.getConnection call as a java.util.Properties structure.
|
|
|
|
|
|
|
|
|
|
Database types supported, and their defaults:
|
|
|
|
|
* derby -- org.apache.derby.jdbc.EmbeddedDriver -- also pass :create true
|
|
|
|
|
if you want the database to be automatically created
|
|
|
|
|
* h2 -- org.h2.Driver -- for an on-disk database
|
|
|
|
|
* h2:mem -- org.h2.Driver -- for an in-memory database
|
|
|
|
|
* hsqldb, hsql -- org.hsqldb.jdbcDriver
|
|
|
|
|
* jtds:sqlserver, jtds -- net.sourceforge.jtds.jdbc.Driver -- 1433
|
|
|
|
|
* mysql -- com.mysql.cj.jdbc.Driver, com.mysql.jdbc.Driver -- 3306
|
|
|
|
|
* oracle:oci -- oracle.jdbc.OracleDriver -- 1521
|
|
|
|
|
* oracle:thin, oracle -- oracle.jdbc.OracleDriver -- 1521
|
|
|
|
|
* oracle:sid -- oracle.jdbc.OracleDriver -- 1521 -- uses the legacy :
|
|
|
|
|
separator for the database name but otherwise behaves like oracle:thin
|
|
|
|
|
* postgresql, postgres -- org.postgresql.Driver -- 5432
|
|
|
|
|
* pgsql -- com.impossibl.postgres.jdbc.PGDriver -- no default port
|
|
|
|
|
* redshift -- com.amazon.redshift.jdbc.Driver -- no default port
|
|
|
|
|
* sqlite -- org.sqlite.JDBC
|
|
|
|
|
* sqlserver, mssql -- com.microsoft.sqlserver.jdbc.SQLServerDriver -- 1433"
|
2019-04-01 06:17:12 +00:00
|
|
|
[spec]
|
|
|
|
|
(p/get-datasource spec))
|
2019-01-11 03:23:35 +00:00
|
|
|
|
2019-04-01 06:17:12 +00:00
|
|
|
(defn get-connection
|
|
|
|
|
"Given some sort of specification of a database, return a new Connection.
|
2019-01-10 07:30:46 +00:00
|
|
|
|
2019-04-01 06:17:12 +00:00
|
|
|
In general, this should be used via with-open:
|
|
|
|
|
|
|
|
|
|
(with-open [con (get-connection spec opts)]
|
2019-04-02 04:31:38 +00:00
|
|
|
(run-some-ops con))
|
|
|
|
|
|
|
|
|
|
If you call get-connection on a DataSource, it just calls .getConnection
|
|
|
|
|
and applies the :auto-commit and/or :read-only options, if provided.
|
|
|
|
|
|
|
|
|
|
If you call get-connection on anything else, it will call get-datasource
|
|
|
|
|
first to try to get a DataSource, and then call get-connection on that."
|
2019-04-02 06:57:12 +00:00
|
|
|
([spec]
|
|
|
|
|
(p/get-connection spec {}))
|
|
|
|
|
([spec opts]
|
|
|
|
|
(p/get-connection spec opts)))
|
2019-04-01 06:17:12 +00:00
|
|
|
|
|
|
|
|
(defn prepare
|
2019-04-02 04:31:38 +00:00
|
|
|
"Given a connection to a database, and a vector containing SQL and any
|
|
|
|
|
parameters it needs, return a new PreparedStatement.
|
2019-04-01 06:17:12 +00:00
|
|
|
|
|
|
|
|
In general, this should be used via with-open:
|
|
|
|
|
|
|
|
|
|
(with-open [stmt (prepare spec sql-params opts)]
|
2019-04-02 04:31:38 +00:00
|
|
|
(run-some-ops stmt))
|
|
|
|
|
|
|
|
|
|
See the list of options above (in the namespace docstring) for what can
|
|
|
|
|
be passed to prepare."
|
2019-04-02 06:57:12 +00:00
|
|
|
([connection sql-params]
|
|
|
|
|
(p/prepare connection sql-params {}))
|
|
|
|
|
([connection sql-params opts]
|
|
|
|
|
(p/prepare connection sql-params opts)))
|
2019-01-08 07:03:20 +00:00
|
|
|
|
2019-03-31 06:12:37 +00:00
|
|
|
(defn reducible!
|
2019-03-31 03:36:53 +00:00
|
|
|
"General SQL execution function.
|
|
|
|
|
|
2019-04-02 04:31:38 +00:00
|
|
|
Returns a reducible that, when reduced, runs the SQL and yields the result.
|
|
|
|
|
|
|
|
|
|
Can be called on a PreparedStatement, a Connection, or something that can
|
|
|
|
|
produce a Connection via a DataSource."
|
2019-04-02 06:57:12 +00:00
|
|
|
([stmt]
|
|
|
|
|
(p/-execute stmt [] {}))
|
|
|
|
|
([connectable sql-params]
|
|
|
|
|
(p/-execute connectable sql-params {}))
|
|
|
|
|
([connectable sql-params opts]
|
2019-03-31 23:54:34 +00:00
|
|
|
(p/-execute connectable sql-params opts)))
|
2019-01-26 08:21:03 +00:00
|
|
|
|
2019-03-31 06:13:01 +00:00
|
|
|
(defn execute!
|
2019-04-01 06:17:12 +00:00
|
|
|
"General SQL execution function.
|
|
|
|
|
|
2019-04-02 04:31:38 +00:00
|
|
|
Invokes 'reducible!' and then reduces that into a vector of hash maps.
|
|
|
|
|
|
|
|
|
|
Can be called on a PreparedStatement, a Connection, or something that can
|
2019-04-02 07:41:39 +00:00
|
|
|
produce a Connection via a DataSource.
|
|
|
|
|
|
|
|
|
|
If it is called on a PreparedStatement, it cannot produce a datafiable
|
|
|
|
|
result (because that requires a connectable instead)."
|
2019-03-31 23:54:34 +00:00
|
|
|
([stmt]
|
2019-04-02 07:41:39 +00:00
|
|
|
(rs/execute! stmt [] (partial into {}) {}))
|
2019-03-31 23:54:34 +00:00
|
|
|
([connectable sql-params]
|
2019-04-11 06:59:19 +00:00
|
|
|
(rs/execute! connectable sql-params #(rs/datafiable-row % connectable {}) {}))
|
2019-03-31 06:13:01 +00:00
|
|
|
([connectable sql-params opts]
|
2019-04-11 06:59:19 +00:00
|
|
|
(rs/execute! connectable sql-params #(rs/datafiable-row % connectable opts) opts))
|
2019-04-02 07:41:39 +00:00
|
|
|
([connectable sql-params f opts]
|
|
|
|
|
(rs/execute! connectable sql-params f opts)))
|
2019-01-26 08:21:03 +00:00
|
|
|
|
2019-03-31 06:13:01 +00:00
|
|
|
(defn execute-one!
|
2019-04-01 06:17:12 +00:00
|
|
|
"General SQL execution function that returns just the first row of a result.
|
|
|
|
|
|
2019-04-02 04:31:38 +00:00
|
|
|
Invokes 'reducible!' but immediately returns the first row.
|
|
|
|
|
|
|
|
|
|
Can be called on a PreparedStatement, a Connection, or something that can
|
2019-04-02 07:41:39 +00:00
|
|
|
produce a Connection via a DataSource.
|
|
|
|
|
|
|
|
|
|
If it is called on a PreparedStatement, it cannot produce a datafiable
|
|
|
|
|
result (because that requires a connectable instead)."
|
2019-03-31 23:54:34 +00:00
|
|
|
([stmt]
|
2019-04-02 07:41:39 +00:00
|
|
|
(rs/execute-one! stmt [] (partial into {}) {}))
|
2019-03-31 23:54:34 +00:00
|
|
|
([connectable sql-params]
|
2019-04-11 06:59:19 +00:00
|
|
|
(rs/execute-one! connectable sql-params #(rs/datafiable-row % connectable {}) {}))
|
2019-03-31 06:13:01 +00:00
|
|
|
([connectable sql-params opts]
|
2019-04-11 06:59:19 +00:00
|
|
|
(rs/execute-one! connectable sql-params #(rs/datafiable-row % connectable opts) opts))
|
2019-04-02 07:41:39 +00:00
|
|
|
([connectable sql-params f opts]
|
|
|
|
|
(rs/execute-one! connectable sql-params f opts)))
|
2019-03-31 23:54:34 +00:00
|
|
|
|
2019-04-02 06:43:10 +00:00
|
|
|
(defn transact
|
|
|
|
|
"Given a connectable object and a function (taking a Connection),
|
|
|
|
|
execute the function on a new connection in a transactional manner.
|
|
|
|
|
|
|
|
|
|
An options map may be provided before the function."
|
|
|
|
|
([connectable f]
|
|
|
|
|
(p/-transact connectable f {}))
|
|
|
|
|
([connectable opts f]
|
|
|
|
|
(p/-transact connectable f opts)))
|
|
|
|
|
|
2019-03-31 23:54:34 +00:00
|
|
|
(defmacro with-transaction
|
2019-04-01 06:17:12 +00:00
|
|
|
"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."
|
2019-03-31 23:54:34 +00:00
|
|
|
[[sym connectable opts] & body]
|
2019-04-02 06:43:10 +00:00
|
|
|
`(transact ~connectable ~opts (fn [~sym] ~@body)))
|
2019-04-01 02:30:01 +00:00
|
|
|
|
|
|
|
|
(defn insert!
|
2019-04-02 06:57:12 +00:00
|
|
|
"Syntactic sugar over execute! to make inserting hash maps easier.
|
|
|
|
|
|
|
|
|
|
Given a connectable object, a table name, and a data hash map, inserts the
|
2019-04-01 06:17:12 +00:00
|
|
|
data as a single row in the database and attempts to return a map of generated
|
|
|
|
|
keys."
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table key-map]
|
2019-04-02 07:41:39 +00:00
|
|
|
(insert! connectable table key-map {}))
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table key-map opts]
|
|
|
|
|
(rs/execute! connectable
|
|
|
|
|
(sql/for-insert table key-map opts)
|
2019-04-02 07:41:39 +00:00
|
|
|
(partial into {})
|
2019-04-01 02:30:01 +00:00
|
|
|
(merge {:return-keys true} opts))))
|
|
|
|
|
|
|
|
|
|
(defn insert-multi!
|
2019-04-02 06:57:12 +00:00
|
|
|
"Syntactic sugar over execute! to make inserting columns/rows easier.
|
|
|
|
|
|
|
|
|
|
Given a connectable object, a table name, a sequence of column names, and
|
2019-04-01 06:17:12 +00:00
|
|
|
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."
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table cols rows]
|
2019-04-02 07:41:39 +00:00
|
|
|
(insert-multi! connectable table cols rows {}))
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table cols rows opts]
|
|
|
|
|
(rs/execute! connectable
|
|
|
|
|
(sql/for-insert-multi table cols rows opts)
|
2019-04-02 07:41:39 +00:00
|
|
|
(partial into {})
|
2019-04-01 02:30:01 +00:00
|
|
|
(merge {:return-keys true} opts))))
|
|
|
|
|
|
2019-04-02 06:57:12 +00:00
|
|
|
(defn query
|
|
|
|
|
"Syntactic sugar over execute! to provide a query alias.
|
|
|
|
|
|
|
|
|
|
Given a connectable object, and a vector of SQL and its parameters,
|
|
|
|
|
returns a vector of hash maps of rows that match."
|
|
|
|
|
([connectable sql-params]
|
2019-04-02 07:41:39 +00:00
|
|
|
(query connectable sql-params {}))
|
2019-04-02 06:57:12 +00:00
|
|
|
([connectable sql-params opts]
|
2019-04-02 07:41:39 +00:00
|
|
|
(if-let [row-fn (:row-fn opts)]
|
|
|
|
|
(rs/execute! connectable
|
|
|
|
|
sql-params
|
|
|
|
|
row-fn
|
|
|
|
|
opts)
|
|
|
|
|
(rs/execute! connectable
|
|
|
|
|
sql-params
|
2019-04-11 06:59:19 +00:00
|
|
|
#(rs/datafiable-row % connectable opts)
|
2019-04-02 07:41:39 +00:00
|
|
|
opts))))
|
2019-04-02 06:57:12 +00:00
|
|
|
|
2019-04-01 02:30:01 +00:00
|
|
|
(defn find-by-keys
|
2019-04-02 06:57:12 +00:00
|
|
|
"Syntactic sugar over execute! to make certain common queries easier.
|
|
|
|
|
|
|
|
|
|
Given a connectable object, a table name, and a hash map of columns and
|
2019-04-01 06:17:12 +00:00
|
|
|
their values, returns a vector of hash maps of rows that match."
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table key-map]
|
2019-04-02 07:41:39 +00:00
|
|
|
(find-by-keys connectable table key-map {}))
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table key-map opts]
|
2019-04-02 07:41:39 +00:00
|
|
|
(if-let [row-fn (:row-fn opts)]
|
|
|
|
|
(rs/execute! connectable
|
|
|
|
|
(sql/for-query table key-map opts)
|
|
|
|
|
row-fn
|
|
|
|
|
opts)
|
|
|
|
|
(rs/execute! connectable
|
|
|
|
|
(sql/for-query table key-map opts)
|
2019-04-11 06:59:19 +00:00
|
|
|
#(rs/datafiable-row % connectable opts)
|
2019-04-02 07:41:39 +00:00
|
|
|
opts))))
|
2019-04-01 02:30:01 +00:00
|
|
|
|
|
|
|
|
(defn get-by-id
|
2019-04-02 06:57:12 +00:00
|
|
|
"Syntactic sugar over execute! to make certain common queries easier.
|
|
|
|
|
|
|
|
|
|
Given a connectable object, a table name, and a primary key value, returns
|
2019-04-01 06:17:12 +00:00
|
|
|
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."
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table pk]
|
2019-04-02 07:41:39 +00:00
|
|
|
(get-by-id connectable table pk :id {}))
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table pk opts]
|
2019-04-02 07:41:39 +00:00
|
|
|
(get-by-id connectable table pk :id opts))
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table pk pk-name opts]
|
2019-04-02 07:41:39 +00:00
|
|
|
(if-let [row-fn (:row-fn opts)]
|
|
|
|
|
(rs/execute-one! connectable
|
|
|
|
|
(sql/for-query table {pk-name pk} opts)
|
|
|
|
|
row-fn
|
|
|
|
|
opts)
|
|
|
|
|
(rs/execute-one! connectable
|
|
|
|
|
(sql/for-query table {pk-name pk} opts)
|
2019-04-11 06:59:19 +00:00
|
|
|
#(rs/datafiable-row % connectable opts)
|
2019-04-02 07:41:39 +00:00
|
|
|
opts))))
|
2019-04-01 02:30:01 +00:00
|
|
|
|
|
|
|
|
(defn update!
|
2019-04-02 06:57:12 +00:00
|
|
|
"Syntactic sugar over execute! to make certain common updates easier.
|
|
|
|
|
|
|
|
|
|
Given a connectable object, a table name, a hash map of columns and values
|
2019-04-01 06:17:12 +00:00
|
|
|
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."
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table key-map where-params]
|
2019-04-02 07:41:39 +00:00
|
|
|
(update! connectable table key-map where-params {}))
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table key-map where-params opts]
|
2019-04-02 07:41:39 +00:00
|
|
|
(rs/execute! connectable
|
|
|
|
|
(sql/for-update table key-map where-params opts)
|
|
|
|
|
(partial into {})
|
|
|
|
|
opts)))
|
2019-04-01 02:30:01 +00:00
|
|
|
|
|
|
|
|
(defn delete!
|
2019-04-02 06:57:12 +00:00
|
|
|
"Syntactic sugar over execute! to make certain common deletes easier.
|
|
|
|
|
|
|
|
|
|
Given a connectable object, a table name, and either a hash map of columns
|
2019-04-01 06:17:12 +00:00
|
|
|
and values to search on or a vector of a SQL where clause and parameters,
|
|
|
|
|
perform a delete on the table."
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table where-params]
|
2019-04-02 07:41:39 +00:00
|
|
|
(delete! connectable table where-params {}))
|
2019-04-01 02:30:01 +00:00
|
|
|
([connectable table where-params opts]
|
2019-04-02 07:41:39 +00:00
|
|
|
(rs/execute! connectable
|
|
|
|
|
(sql/for-delete table where-params opts)
|
|
|
|
|
(partial into {})
|
|
|
|
|
opts)))
|