Do more with protocols for performance

This commit is contained in:
Sean Corfield 2019-01-09 23:05:21 -08:00
parent 0950e356ab
commit 320f40ecca

View file

@ -35,10 +35,11 @@
(set! *warn-on-reflection* true)
(defprotocol Sourceable
(get-datasource ^DataSource [this]))
(defprotocol Connectable
(get-connection ^AutoCloseable [this]))
(defprotocol Preparable
(->jdbc-connection ^Connection [this])
(prepare ^PreparedStatement [this sql-params opts]))
(defprotocol Transactable
(transact [this f opts]))
@ -54,11 +55,11 @@
(def ^:private isolation-levels
"Transaction isolation levels."
{:none java.sql.Connection/TRANSACTION_NONE
:read-committed java.sql.Connection/TRANSACTION_READ_COMMITTED
:read-uncommitted java.sql.Connection/TRANSACTION_READ_UNCOMMITTED
:repeatable-read java.sql.Connection/TRANSACTION_REPEATABLE_READ
:serializable java.sql.Connection/TRANSACTION_SERIALIZABLE})
{:none Connection/TRANSACTION_NONE
:read-committed Connection/TRANSACTION_READ_COMMITTED
:read-uncommitted Connection/TRANSACTION_READ_UNCOMMITTED
:repeatable-read Connection/TRANSACTION_REPEATABLE_READ
:serializable Connection/TRANSACTION_SERIALIZABLE})
(def ^:private isolation-kws
"Map transaction isolation constants to our keywords."
@ -86,7 +87,7 @@
(f con)
(with-open [^AutoCloseable t-con (assoc (get-connection con)
:transacted (atom committable?))]
(let [^Connection jdbc (->jdbc-connection t-con)
(let [^Connection jdbc t-con
old-autocommit (.getAutoCommit jdbc)
old-isolation (.getTransactionIsolation jdbc)
old-readonly (.isReadOnly jdbc)]
@ -131,28 +132,6 @@
(.setReadOnly jdbc old-readonly)
(catch Exception _)))))))))))
(defrecord NestedConnection [con transacted]
Connectable
(get-connection [this] (->NestedConnection con transacted))
Preparable
(->jdbc-connection [this] con)
(prepare [this sql-params opts] (prepare* con sql-params opts))
AutoCloseable
(close [this])
Transactable
(transact [this f opts] (transact* con transacted f opts)))
(defrecord Connected [con transacted]
Connectable
(get-connection [this] (->NestedConnection con transacted))
Preparable
(->jdbc-connection [this] con)
(prepare [this sql-params opts] (prepare* con sql-params opts))
AutoCloseable
(close [this] (.close ^Connection con))
Transactable
(transact [this f opts] (transact* con transacted f opts)))
(defmacro in-transaction
[[sym con opts] & body]
`(transact ~con (fn [~sym] ~@body) ~opts))
@ -214,8 +193,8 @@
(defn- modify-connection
"Given a database connection and a map of options, update the connection
as specified by the options."
^java.sql.Connection
[^java.sql.Connection connection opts]
^Connection
[^Connection connection opts]
(when (and connection (contains? opts :auto-commit?))
(.setAutoCommit connection (boolean (:auto-commit? opts))))
(when (and connection (contains? opts :read-only?))
@ -234,29 +213,13 @@
(defn- get-driver-connection
"Common logic for loading the DriverManager and the designed JDBC driver
class and obtaining the appropriate Connection object."
[classname subprotocol db-spec url etc error-msg]
(if-let [class-name (or classname (classnames subprotocol))]
(do
;; force DriverManager to be loaded
(DriverManager/getLoginTimeout)
(if (string? class-name)
(clojure.lang.RT/loadClassForName class-name)
(loop [[clazz & more] class-name]
(when-let [load-failure
(try
(clojure.lang.RT/loadClassForName clazz)
nil
(catch Exception e
e))]
(if (seq more)
(recur more)
(throw load-failure))))))
(throw (ex-info error-msg db-spec)))
[url etc]
(-> (DriverManager/getConnection url (as-properties etc))
(modify-connection etc)))
(defn- spec->connection
(defn- spec->datasource
""
^DataSource
[{:keys [dbtype dbname host port classname] :as db-spec}]
(let [;; allow aliases for dbtype
subprotocol (aliases dbtype dbtype)
@ -274,29 +237,74 @@
(when port (str ":" port))
db-sep dbname))
etc (dissoc db-spec :dbtype :dbname)]
(get-driver-connection classname subprotocol db-spec
url etc
(str "Unknown dbtype: " dbtype))))
;; verify the datasource is loadable
(if-let [class-name (or classname (classnames subprotocol))]
(do
;; force DriverManager to be loaded
(DriverManager/getLoginTimeout)
(if (string? class-name)
(clojure.lang.RT/loadClassForName class-name)
(loop [[clazz & more] class-name]
(when-let [load-failure
(try
(clojure.lang.RT/loadClassForName clazz)
nil
(catch Exception e
e))]
(if (seq more)
(recur more)
(throw load-failure))))))
(throw (ex-info (str "Unknown dbtype: " dbtype) db-spec)))
;; return a DataSource
(reify DataSource
(getConnection [this]
(get-driver-connection url etc))
(getConnection [this username password]
(get-driver-connection url
(assoc etc
:username username
:password password))))))
(defn- string->spec
""
[s]
{})
(extend-protocol
Sourceable
clojure.lang.Associative
(get-datasource [this] (spec->datasource this))
DataSource
(get-datasource [this] this)
String
(get-datasource [this] (get-datasource (string->spec this))))
(extend-protocol
Connectable
clojure.lang.Associative
(get-connection [this]
(->Connected (spec->connection this) nil))
(get-connection [this] (get-connection (get-datasource this)))
Connection
(get-connection [this]
(->Connected this nil))
(get-connection [this] (reify
AutoCloseable
(close [this'])
Connectable
(get-connection [this'] this')
Preparable
(prepare [this' sql-params opts]
(prepare this sql-params opts))))
DataSource
(get-connection [this]
(->Connected (.getConnection this) nil))
String
(get-connection [this]
(get-connection (string->spec this))))
(get-connection [this] (.getConnection this))
Object
(get-connection [this] (get-connection (get-datasource this))))
(extend-protocol
Preparable
Connection
(prepare [this sql-params opts] (prepare* this sql-params opts))
DataSource
(prepare [this sql-params opts] (prepare (get-connection this) sql-params opts))
Object
(prepare [this sql-params opts] (prepare (get-datasource this) sql-params opts)))
(comment
(get-connection {:dbtype "derby" :dbname "clojure_test" :create true} {})
@ -386,11 +394,15 @@
(def db-spec {:dbtype "mysql" :dbname "worldsingles" :user "root" :password "visual" :useSSL false})
(def db-spec {:dbtype "h2:mem" :dbname "perf"})
(def con (get-connection db-spec))
(println con)
(def ds (get-datasource db-spec))
(def con (get-connection ds))
(reduce + 0 (execute! con ["DROP TABLE fruit"]))
(reduce + 0 (execute! con ["CREATE TABLE fruit (id int default 0, name varchar(32) primary key, appearance varchar(32), cost int, grade real)"]))
(reduce + 0 (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)"]))
(close con)
(require '[criterium.core :refer [bench quick-bench]])
;; calibrate
(quick-bench (reduce + (take 10e6 (range))))
(quick-bench
(reduce (fn [_ row] (reduced (:name row)))