next-jdbc/src/next/jdbc.clj

314 lines
13 KiB
Clojure
Raw Normal View History

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,
* 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]
[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-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."
([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."
([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."
([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-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
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]
(rs/execute! stmt [] (partial into {}) {}))
2019-03-31 23:54:34 +00:00
([connectable sql-params]
(rs/execute! connectable sql-params #(rs/datafiable-row % connectable {}) {}))
2019-03-31 06:13:01 +00:00
([connectable sql-params opts]
(rs/execute! connectable sql-params #(rs/datafiable-row % connectable opts) opts))
([connectable sql-params f opts]
(rs/execute! connectable sql-params f opts)))
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
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]
(rs/execute-one! stmt [] (partial into {}) {}))
2019-03-31 23:54:34 +00:00
([connectable sql-params]
(rs/execute-one! connectable sql-params #(rs/datafiable-row % connectable {}) {}))
2019-03-31 06:13:01 +00:00
([connectable sql-params opts]
(rs/execute-one! connectable sql-params #(rs/datafiable-row % connectable opts) opts))
([connectable sql-params f opts]
(rs/execute-one! connectable sql-params f opts)))
2019-03-31 23:54:34 +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]
`(transact ~connectable ~opts (fn [~sym] ~@body)))
(defn insert!
"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."
([connectable table key-map]
(insert! connectable table key-map {}))
([connectable table key-map opts]
(rs/execute! connectable
(sql/for-insert table key-map opts)
(partial into {})
(merge {:return-keys true} opts))))
(defn insert-multi!
"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."
([connectable table cols rows]
(insert-multi! connectable table cols rows {}))
([connectable table cols rows opts]
(rs/execute! connectable
(sql/for-insert-multi table cols rows opts)
(partial into {})
(merge {:return-keys true} opts))))
(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]
(query connectable sql-params {}))
([connectable sql-params opts]
(if-let [row-fn (:row-fn opts)]
(rs/execute! connectable
sql-params
row-fn
opts)
(rs/execute! connectable
sql-params
#(rs/datafiable-row % connectable opts)
opts))))
(defn find-by-keys
"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."
([connectable table key-map]
(find-by-keys connectable table key-map {}))
([connectable table key-map opts]
(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)
#(rs/datafiable-row % connectable opts)
opts))))
(defn get-by-id
"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."
([connectable table pk]
(get-by-id connectable table pk :id {}))
([connectable table pk opts]
(get-by-id connectable table pk :id opts))
([connectable table pk pk-name opts]
(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)
#(rs/datafiable-row % connectable opts)
opts))))
(defn update!
"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."
([connectable table key-map where-params]
(update! connectable table key-map where-params {}))
([connectable table key-map where-params opts]
(rs/execute! connectable
(sql/for-update table key-map where-params opts)
(partial into {})
opts)))
(defn delete!
"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."
([connectable table where-params]
(delete! connectable table where-params {}))
([connectable table where-params opts]
(rs/execute! connectable
(sql/for-delete table where-params opts)
(partial into {})
opts)))