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