diff --git a/doc/intro.md b/doc/intro.md deleted file mode 100644 index 85177b0..0000000 --- a/doc/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -# Introduction to next.jdbc - -TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) diff --git a/src/next/jdbc.clj b/src/next/jdbc.clj index 96613a3..5e7c4d0 100644 --- a/src/next/jdbc.clj +++ b/src/next/jdbc.clj @@ -1,7 +1,41 @@ ;; copyright (c) 2018-2019 Sean Corfield, all rights reserved (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 [next.jdbc.prepare :as prepare] ; used to extend protocols [next.jdbc.protocols :as p] @@ -11,11 +45,31 @@ (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! "General SQL execution function. @@ -26,7 +80,9 @@ (p/-execute connectable sql-params opts))) (defn execute! - "" + "General SQL execution function. + + Invokes 'reducible!' and then reduces that into a vector of hash maps." ([stmt] (rs/execute! stmt [] {})) ([connectable sql-params] @@ -35,7 +91,9 @@ (rs/execute! connectable sql-params opts))) (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] (rs/execute-one! stmt [] {})) ([connectable sql-params] @@ -44,11 +102,22 @@ (rs/execute-one! connectable sql-params opts))) (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] `(p/-transact ~connectable (fn [~sym] ~@body) ~opts)) (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] (rs/execute! connectable (sql/for-insert table key-map {}) @@ -59,7 +128,10 @@ (merge {:return-keys true} opts)))) (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] (rs/execute! connectable (sql/for-insert-multi table cols rows {}) @@ -70,14 +142,19 @@ (merge {:return-keys true} opts)))) (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] (rs/execute! connectable (sql/for-query table key-map {}) {})) ([connectable table key-map opts] (rs/execute! connectable (sql/for-query table key-map opts) opts))) (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] (rs/execute-one! connectable (sql/for-query table {:id pk} {}) {})) ([connectable table pk opts] @@ -86,14 +163,18 @@ (rs/execute-one! connectable (sql/for-query table {pk-name pk} opts) opts))) (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] (rs/execute! connectable (sql/for-update table key-map where-params {}) {})) ([connectable table key-map where-params opts] (rs/execute! connectable (sql/for-update table key-map where-params opts) opts))) (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] (rs/execute! connectable (sql/for-delete table where-params {}) {})) ([connectable table where-params opts] diff --git a/src/next/jdbc/connection.clj b/src/next/jdbc/connection.clj index ea3f8c3..d73dbb4 100644 --- a/src/next/jdbc/connection.clj +++ b/src/next/jdbc/connection.clj @@ -1,7 +1,7 @@ ;; copyright (c) 2018-2019 Sean Corfield, all rights reserved (ns next.jdbc.connection - "" + "Standard implementations of get-datasource and get-connection." (:require [next.jdbc.protocols :as p]) (:import (java.sql Connection DriverManager) (javax.sql DataSource) @@ -81,7 +81,7 @@ (DriverManager/getConnection url (as-properties 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}] (let [;; allow aliases for dbtype subprotocol (aliases dbtype dbtype) @@ -118,12 +118,13 @@ [url etc])) (defn- string->url+etc - "" + "Given a JDBC URL, return it with an empty set of options with no parsing." [s] [s {}]) (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]] (reify DataSource (getConnection [_] @@ -136,14 +137,19 @@ (defn- make-connection "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 [^DataSource datasource opts] (let [^Connection connection (.getConnection datasource)] - (when (contains? opts :auto-commit?) - (.setAutoCommit connection (boolean (:auto-commit? opts)))) - (when (contains? opts :read-only?) - (.setReadOnly connection (boolean (:read-only? opts)))) + (when (contains? opts :auto-commit) + (.setAutoCommit connection (boolean (:auto-commit opts)))) + (when (contains? opts :read-only) + (.setReadOnly connection (boolean (:read-only opts)))) connection)) (extend-protocol p/Sourceable diff --git a/src/next/jdbc/prepare.clj b/src/next/jdbc/prepare.clj index c5f24df..57df43d 100644 --- a/src/next/jdbc/prepare.clj +++ b/src/next/jdbc/prepare.clj @@ -1,7 +1,11 @@ ;; copyright (c) 2018-2019 Sean Corfield, all rights reserved (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]) (:import (java.sql Connection PreparedStatement @@ -11,7 +15,10 @@ (set! *warn-on-reflection* true) (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 [^PreparedStatement ps params] (when (seq params) diff --git a/src/next/jdbc/protocols.clj b/src/next/jdbc/protocols.clj index 63bbaa9..7640937 100644 --- a/src/next/jdbc/protocols.clj +++ b/src/next/jdbc/protocols.clj @@ -1,7 +1,31 @@ ;; copyright (c) 2018-2019 Sean Corfield, all rights reserved (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) diff --git a/src/next/jdbc/result_set.clj b/src/next/jdbc/result_set.clj index 7e204de..6f23d1f 100644 --- a/src/next/jdbc/result_set.clj +++ b/src/next/jdbc/result_set.clj @@ -1,7 +1,7 @@ ;; copyright (c) 2018-2019 Sean Corfield, all rights reserved (ns next.jdbc.result-set - "" + "An implementation of ResultSet handling functions." (:require [clojure.core.protocols :as core-p] [next.jdbc.prepare :as prepare] [next.jdbc.protocols :as p]) @@ -12,7 +12,11 @@ (set! *warn-on-reflection* true) (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] (let [^ResultSetMetaData rsmeta (.getMetaData rs) idxs (range 1 (inc (.getColumnCount rsmeta)))] @@ -75,7 +79,13 @@ (range 1 (inc (count @cols))))))))) (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] (if-let [^ResultSet rs (if (.execute stmt) (.getResultSet stmt) @@ -130,19 +140,27 @@ (declare navize-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] (fn [row] (into (with-meta {} {`core-p/datafy (navize-row connectable opts)}) row))) (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] (into [] (map (or (:row-fn opts) (datafiable-row connectable opts))) (p/-execute connectable sql-params opts))) (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] (let [row-fn (or (:row-fn opts) (datafiable-row connectable opts))] (reduce (fn [_ row] @@ -161,7 +179,22 @@ [(keyword table) :id]))) (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