From fd9ea23858d80cbd2d8bf242db23ccedf99c4b88 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 8 Jul 2019 20:03:41 +0200 Subject: [PATCH] Refactor. --- CHANGES.adoc | 17 +++ README.md | 11 +- deps.edn | 26 ++-- doc/content.adoc | 131 +++++------------- scripts/bench.clj | 37 ++++++ src/suricatta/core.clj | 26 ++-- src/suricatta/impl.clj | 235 ++++++++++++--------------------- src/suricatta/proto.clj | 2 +- test/suricatta/core_test.clj | 28 ++-- test/suricatta/extend_test.clj | 5 +- 10 files changed, 207 insertions(+), 311 deletions(-) create mode 100644 scripts/bench.clj diff --git a/CHANGES.adoc b/CHANGES.adoc index f92ea8b..6e8976a 100644 --- a/CHANGES.adoc +++ b/CHANGES.adoc @@ -1,5 +1,22 @@ = Changelog +== 2.0.0 + +Date: unreleased + +BREAKING CHANGES: +- `suricatta.format` namespace is removed +- `suricatta.dsl` namespace is removed +- dbspec connection format is removed (now only the simplest methods + are supported: `DataSource` instance and URL. +- `cursor->lazyseq` is renamed to `cursor->seq` +- New and simplfied method for add type extensions (see docs). + +Other changes: +- Many performance improvements on standart queries. +- Many performance improvements on lazy fetching. + + == 1.3.1 Date: 2016-12-25 diff --git a/README.md b/README.md index 6f462fe..3ccea87 100644 --- a/README.md +++ b/README.md @@ -14,23 +14,16 @@ High level sql toolkit for clojure (backed by jooq library) Put suricatta on your dependency list: ```clojure -[funcool/suricatta "1.3.1"] +[funcool/suricatta "2.0.0"] [com.h2database/h2 "1.4.191"] ;; For this example only ``` -Define a valid dbspec hashmap: - -```clojure -(def dbspec {:subprotocol "h2" - :subname "mem:"}) -``` - Connect to the database and execute a query: ```clojure (require '[suricatta.core :as sc]) -(with-open [ctx (sc/context dbspec)] +(with-open [ctx (sc/context "h2:mem:")] (sc/fetch ctx "select x from system_range(1, 2);")) ;; => [{:x 1} {:x 2}] ``` diff --git a/deps.edn b/deps.edn index 09e7c7c..3d5b17f 100644 --- a/deps.edn +++ b/deps.edn @@ -1,19 +1,19 @@ {:deps {org.clojure/clojure {:mvn/version "1.10.1"} org.jooq/jooq {:mvn/version "3.11.11"}} - :paths ["src"] - :aliases - {:dev {:extra-deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"} - org.clojure/tools.namespace {:mvn/version "0.3.0"} - org.postgresql/postgresql {:mvn/version "42.2.6"} - com.h2database/h2 {:mvn/version "1.4.199"} - cheshire/cheshire {:mvn/version "5.8.1"}} - :extra-paths ["test"]} - :repl {:main-opts ["-m" "rebel-readline.main"]} - :ancient {:main-opts ["-m" "deps-ancient.deps-ancient"] - :extra-deps {deps-ancient {:mvn/version "RELEASE"}}} - :test {:main-opts ["-m" "user"]} - }} + :aliases {:dev {:extra-deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"} + org.clojure/tools.namespace {:mvn/version "0.3.0"} + org.postgresql/postgresql {:mvn/version "42.2.6"} + com.h2database/h2 {:mvn/version "1.4.199"} + cheshire/cheshire {:mvn/version "5.8.1"}} + :extra-paths ["test"]} + :bench {:extra-deps {seancorfield/next.jdbc {:mvn/version "1.0.1"} + criterium/criterium {:mvn/version "0.4.5"}}} + :repl {:main-opts ["-m" "rebel-readline.main"]} + :ancient {:main-opts ["-m" "deps-ancient.deps-ancient"] + :extra-deps {deps-ancient {:mvn/version "RELEASE"}}} + :test {:main-opts ["-m" "user"]} + }} diff --git a/doc/content.adoc b/doc/content.adoc index e79fc73..d6dc009 100644 --- a/doc/content.adoc +++ b/doc/content.adoc @@ -1,6 +1,6 @@ = suricatta documentation Andrey Antukh, -1.3.1 +2.0.0 :toc: left :!numbered: :source-highlighter: pygments @@ -50,49 +50,21 @@ management, transaction isolation flags and sql rendering dialect. You can create a **context** from: -- a hash-map dbspec format (plain connection params). - a datasource instance (connection pool). -- a link:http://funcool.github.io/clojure.jdbc/latest/[clojure.jdbc] connection - instance. +- a valid jdbc url -.This is a default aspect of one dbspec. -[source,clojure] ----- -(def dbspec {:subprotocol "postgresql" - :subname "//localhost:5432/dbname" - :user "username" ;; Optional - :password "password"} ;; Optional ----- -==== Create Context from plain dbspec. +==== Create Context from jdbc url .Example creating context from dbspec. [source, clojure] ---- (require '[suricatta.core :as sc]) -(with-open [ctx (sc/context {:subprotocol "h2" - :subname "mem:"})] - (do-something-with ctx)) ----- - -==== Create Context from _clojure.jdbc_ connection. - -.Example creating context from _clojure.jdbc_ connection instance. -[source, clojure] ----- -(require '[jdbc.core :as jdbc]) -(require '[suricatta.core :as sc]) - -(def dbspec {:subprotocol "h2" - :subname "mem:"}) -(with-open [conn (jdbc/connection dbspec) - ctx (sc/context conn)] - (do-something ctx)) +(with-open [ctx (sc/context "h2:mem:")] + (do-something-with ctx)) ---- -NOTE: when closing the _suricatta_ context, the wrapped connection will also be closed. - ==== Create Context from DataSource. @@ -100,12 +72,11 @@ DataSource is the preferd way to connect to the database in production enviromen and is usually used to implement connection pools. In our case we will use *hikaricp* as a datasource with a connection pool. Lets -start by adding hikari's dependency entry to your _project.clj_: +start by adding hikari's dependency entry to your _deps.edn_: [source, clojure] ---- -[hikari-cp "0.13.0" :exclusions [com.zaxxer/HikariCP]] -[com.zaxxer/HikariCP-java6 "2.2.5"] +hikari-cp/hikari-cp {:mvn/version "2.7.1"} ---- Now create the datasource instance: @@ -114,19 +85,19 @@ Now create the datasource instance: ---- (require '[hikari-cp.core :as hikari]) -(def ^javax.sql.Datasource - datasource (hikari/make-datasource - {:connection-timeout 30000 - :idle-timeout 600000 - :max-lifetime 1800000 - :minimum-idle 10 - :maximum-pool-size 10 - :adapter "postgresql" - :username "username" - :password "password" - :database-name "database" - :server-name "localhost" - :port-number 5432})) +(def ^javax.sql.Datasource datasource + (hikari/make-datasource + {:connection-timeout 30000 + :idle-timeout 600000 + :max-lifetime 1800000 + :minimum-idle 10 + :maximum-pool-size 10 + :adapter "postgresql" + :username "username" + :password "password" + :database-name "database" + :server-name "localhost" + :port-number 5432})) ---- Now, having a datasource instace, you can use it like plain dbspec for creating @@ -276,7 +247,7 @@ sequence. Let's see one example: (sc/atomic ctx (with-open [cursor (sc/fetch-lazy ctx sql {:fetch-size 10})] - (doseq [item (sc/cursor->lazyseq cursor)] + (doseq [item (sc/cursor->seq cursor)] (println item)))) ;; This should print something similar to: @@ -285,8 +256,8 @@ sequence. Let's see one example: ;; ... ---- -The third parameter of `sc/fetch-lazy` function is the optional default fetch -size (currently 100.) +The third parameter of `sc/fetch-lazy` function is the optional. The +default fetch size is `128`. === Custom types @@ -357,59 +328,16 @@ contains a work in progress of the new approach. If you want play with that look tests code to see how it works. + == FAQ -=== Why I should use suricatta instead of clojure.jdbc or java.jdbc? +=== Why I should use suricatta instead of next.jdbc or java.jdbc? Unlike any jdbc library, _suricatta_ works at a slightly higher level. It hides a lot of idiosyncrasies of jdbc under a much *simpler, cleaner and less error prone api*, with better resource management. -=== Where is the async support? - -In previous version _suricatta_ it had come with asynchronous support using -core.async channels as response but since the version 0.4.0 it is removed because -core.async is not a proper abstraction for represent a promise. - -In the jvm world, the proper promise abstraction is introduced in JDK8 so using -that abstraction will force people use JDK8, something that I don't want to do at -this moment. - -The great news is that async support is stil very easy implement, so you can do -it in your own code base defining two additional functions. Here a code snippet -for it: - -[source, clojure] ----- -(require '[suricatta.core :as sc] - '[cats.monad.exception :as exc] - '[promissum.core :as p]) - -(defn execute - "Execute a query asynchronously returning a CompletableFuture." - ([ctx q] - (execute ctx q {})) - ([ctx q opts] - (let [act (.-act ctx) - fun #(% (exc/try-on (sc/execute ctx q)))] - (p/promise - (fn [deliver] - (send-off act (fn [_] (fun deliver)))))))) - -(defn fetch - "Execute a query asynchronously returning a CompletableFuture." - ([ctx q] - (fetch ctx q {})) - ([ctx q opts] - (let [act (.-act ctx) - fun #(% (exc/try-on (sc/fetch ctx q opts)))] - (p/promise - (fn [deliver] - (send-off act (fn [_] (fun deliver)))))))) ----- - - === Why another dsl? Is it just yet another dsl? First _suricatta_ is not a dsl library, it's a sql toolkit, and one part of the @@ -419,13 +347,14 @@ Secondly, _suricatta_'s dsl's don't intends to be a sql abstraction. The real purpose of _suricatta_'s dsl is make SQL composable while still allowing use all or almost all vendor specific sql constructions. + === What are some suricatta use cases? The _suricatta_ library is very flexible and it can be used in very different ways: -- You can build queries with _suricatta_ and execute them with _clojure.jdbc_. +- You can build queries with _suricatta_ and execute them with _next.jdbc_. - You can use _suricatta_ for executing queries with string-based sql. -- You can combine the _suricatta_ library with _clojure.jdbc_. +- You can combine the _suricatta_ library with _next.jdbc_. - And obviously, you can forget jdbc and use _suricatta_ for both purposes, building and/or executing queries. @@ -496,7 +425,7 @@ git clone https://github.com/funcool/suricatta [source, text] ---- -lein test +clojure -Adev:test ---- === License @@ -504,7 +433,7 @@ lein test _suricatta_ is licensed under BSD (2-Clause) license: ---- -Copyright (c) 2014-2015 Andrey Antukh +Copyright (c) 2014-2019 Andrey Antukh All rights reserved. diff --git a/scripts/bench.clj b/scripts/bench.clj new file mode 100644 index 0000000..ca764fa --- /dev/null +++ b/scripts/bench.clj @@ -0,0 +1,37 @@ +;; HOW TO RUN: clojure -J-Xmx128m -Adev:bench scripts/bench.clj + +(require '[criterium.core :as b]) +(require '[next.jdbc :as jdbc]) +(require '[next.jdbc.result-set :as jdbc-rs]) +(require '[suricatta.core :as sc]) + +(def uri "jdbc:postgresql://127.0.0.1/test") + +(def conn1 (jdbc/get-connection uri)) +(def conn2 (sc/context uri)) + +(def sql1 "SELECT x FROM generate_series(1, 1000) as x;") + +(defn test-next-jdbc1 + [] + (let [result (jdbc/execute! conn1 [sql1] {:builder-fn jdbc-rs/as-unqualified-lower-maps})] + (with-out-str + (prn result)))) + +(defn test-suricatta1 + [] + (let [result (sc/fetch conn2 sql1)] + (with-out-str + (prn result)))) + +(println "***** START: next.jdbc (1) *****") +;; (b/with-progress-reporting (b/quick-bench (test-next-jdbc1) :verbose)) +(b/quick-bench (test-next-jdbc1)) +(println "***** END: next.jdbc (1) *****") + +(println "***** START: suricatta (1) *****") +;; (b/with-progress-reporting (b/quick-bench (test-suricatta1) :verbose)) +(b/quick-bench (test-suricatta1)) +(println "***** END: suricatta (1) *****") + + diff --git a/src/suricatta/core.clj b/src/suricatta/core.clj index 302b52f..658a307 100644 --- a/src/suricatta/core.clj +++ b/src/suricatta/core.clj @@ -36,16 +36,16 @@ (defn context "Context constructor." - ([dbspec] (context dbspec {})) - ([dbspec opts] - (let [^Connection connection (impl/make-connection dbspec opts) - ^SQLDialect dialect (if (:dialect dbspec) - (impl/translate-dialect (:dialect dbspec)) + ([uri] (context uri {})) + ([uri opts] + (let [^Connection connection (impl/make-connection uri opts) + ^SQLDialect dialect (if (:dialect opts) + (impl/translate-dialect (:dialect opts)) (JDBCUtils/dialect connection)) ^Configuration conf (doto (DefaultConfiguration.) (.set dialect) (.set connection))] - (types/context conf)))) + (types/context conf)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SQL Executor @@ -85,15 +85,15 @@ You should explicitly close the cursor at the end of iteration for release resources." ([ctx q] (proto/-fetch-lazy q ctx {})) - ([ctx q opts] (proto/-fetch-lazy q ctx {}))) + ([ctx q opts] (proto/-fetch-lazy q ctx opts))) -(defn cursor->lazyseq +(defn cursor->seq "Transform a cursor in a lazyseq. The returned lazyseq will return values until a cursor is closed or all values are fetched." - ([cursor] (impl/cursor->lazyseq cursor {})) - ([cursor opts] (impl/cursor->lazyseq cursor opts))) + ([cursor] (impl/cursor->seq cursor {})) + ([cursor opts] (impl/cursor->seq cursor opts))) (defn typed-field "Get a instance of Field definitio." @@ -110,12 +110,6 @@ ;; Transactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn atomic-apply - "Deprecated alias for `apply-atomic`." - {:deprecated true} - [& args] - (apply tx/apply-atomic args)) - (defn apply-atomic "Apply a function in a transaction." [& args] diff --git a/src/suricatta/impl.clj b/src/suricatta/impl.clj index d4ad068..f775744 100644 --- a/src/suricatta/impl.clj +++ b/src/suricatta/impl.clj @@ -29,7 +29,6 @@ [clojure.walk :as walk]) (:import org.jooq.impl.DSL org.jooq.impl.DefaultConfiguration - org.jooq.conf.ParamType org.jooq.tools.jdbc.JDBCUtils org.jooq.SQLDialect org.jooq.DSLContext @@ -40,14 +39,11 @@ org.jooq.Param org.jooq.Result org.jooq.Cursor - org.jooq.RenderContext - org.jooq.BindContext org.jooq.Configuration org.jooq.util.postgres.PostgresDataType org.jooq.util.mariadb.MariaDBDataType org.jooq.util.mysql.MySQLDataType clojure.lang.PersistentVector - java.net.URI java.util.Properties java.sql.Connection java.sql.PreparedStatement @@ -55,6 +51,8 @@ javax.sql.DataSource suricatta.types.Context)) +(set! *warn-on-reflection* true) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -84,41 +82,16 @@ :repeatable-read Connection/TRANSACTION_REPEATABLE_READ :serializable Connection/TRANSACTION_SERIALIZABLE}) -(defn- render-inline? - "Return true if the current render/bind context - allow inline sql rendering. - This function should be used on third party - types/fields adapters." - {:internal true} - [^org.jooq.Context context] - (let [^ParamType ptype (.paramType context)] - (or (= ptype ParamType/INLINED) - (= ptype ParamType/NAMED_OR_INLINED)))) +;; Default implementation for avoid call `satisfies?` -(defn sql->param - [sql & parts] - (letfn [(wrap-if-need [item] - (if (instance? Param item) - item - (DSL/val item)))] - (DSL/field sql (->> (map wrap-if-need parts) - (into-array QueryPart))))) +(extend-protocol proto/IParam + Object + (-param [v _] v)) (defn wrap-if-need [ctx obj] - (if (satisfies? proto/IParam obj) - (proto/-param obj ctx) - obj)) - -(defn- querystring->map - "Given a URI instance, return its querystring as - plain map with parsed keys and values." - [^URI uri] - (let [^String query (.getQuery uri)] - (->> (for [^String kvs (.split query "&")] (into [] (.split kvs "="))) - (into {}) - (walk/keywordize-keys)))) + (proto/-param obj ctx)) (defn- map->properties "Convert hash-map to java.utils.Properties instance. This method is used @@ -129,15 +102,22 @@ (dorun (map (fn [[k v]] (.setProperty p (name k) (str v))) (seq data))) p)) +(defn sql->param + [sql & parts] + (letfn [(wrap-if-need [item] + (if (instance? Param item) + item + (DSL/val item)))] + (DSL/field sql (->> (map wrap-if-need parts) + (into-array QueryPart))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Connection management ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn make-connection - [dbspec opts] - (let [^Connection conn (proto/-connection dbspec) - opts (merge (when (map? dbspec) dbspec) opts)] - + [uri opts] + (let [^Connection conn (proto/-connection uri opts)] ;; Set readonly flag if it found on the options map (some->> (:read-only opts) (.setReadOnly conn)) @@ -154,84 +134,37 @@ conn)) -(declare uri->dbspec) -(declare dbspec->connection) +(defn- map->properties + ^java.util.Properties + [opts] + (letfn [(reduce-fn [^Properties acc k v] + (.setProperty acc (name k) (str v)) + acc)] + (reduce-kv reduce-fn (Properties.) opts))) (extend-protocol proto/IConnectionFactory java.sql.Connection - (-connection [it] it) + (-connection [it opts] it) javax.sql.DataSource - (-connection [it] + (-connection [it opts] (.getConnection it)) - clojure.lang.IPersistentMap - (-connection [dbspec] - (dbspec->connection dbspec)) - - java.net.URI - (-connection [uri] - (-> (uri->dbspec uri) - (dbspec->connection))) - java.lang.String - (-connection [uri] - (let [uri (URI. uri)] - (proto/-connection uri)))) - -(defn dbspec->connection - "Create a connection instance from dbspec." - [{:keys [subprotocol subname user password - name vendor host port datasource classname] - :as dbspec}] - (cond - (and name vendor) - (let [host (or host "127.0.0.1") - port (if port (str ":" port) "") - dbspec (-> (dissoc dbspec :name :vendor :host :port) - (assoc :subprotocol vendor - :subname (str "//" host port "/" name)))] - (dbspec->connection dbspec)) - - (and subprotocol subname) - (let [url (format "jdbc:%s:%s" subprotocol subname) - options (dissoc dbspec :subprotocol :subname)] - - (when classname - (Class/forName classname)) - - (DriverManager/getConnection url (map->properties options))) - - ;; NOTE: only for legacy dbspec format compatibility - (and datasource) - (proto/-connection datasource) - - :else - (throw (IllegalArgumentException. "Invalid dbspec format")))) - -(defn uri->dbspec - "Parses a dbspec as uri into a plain dbspec. This function - accepts `java.net.URI` or `String` as parameter." - [^URI uri] - (let [host (.getHost uri) - port (.getPort uri) - path (.getPath uri) - scheme (.getScheme uri) - userinfo (.getUserInfo uri)] - (merge - {:subname (if (pos? port) - (str "//" host ":" port path) - (str "//" host path)) - :subprotocol scheme} - (when userinfo - (let [[user password] (str/split userinfo #":")] - {:user user :password password})) - (querystring->map uri)))) + (-connection [url opts] + (let [url (if (.startsWith url "jdbc:") url (str "jdbc:" url))] + (DriverManager/getConnection url (map->properties opts))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; IExecute implementation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- make-params + ^"[Ljava.lang.Object;" + [^DSLContext context params] + (->> (map (partial wrap-if-need context) params) + (into-array Object))) + (extend-protocol proto/IExecute java.lang.String (-execute [^String sql ^Context ctx] @@ -243,13 +176,13 @@ (let [^DSLContext context (proto/-context ctx)] (.execute context query))) - clojure.lang.PersistentVector + PersistentVector (-execute [^PersistentVector sqlvec ^Context ctx] (let [^DSLContext context (proto/-context ctx) - ^Query query (->> (map (partial wrap-if-need context) (rest sqlvec)) - (into-array Object) - (.query context (first sqlvec)))] - (.execute context query))) + ^String sql (first sqlvec) + params (make-params context (rest sqlvec)) + query (.query context sql params)] + (.execute context ^Query query))) suricatta.types.Query (-execute [query ctx] @@ -263,38 +196,39 @@ ;; IFetch Implementation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Default implementation for avoid call to `satisfies?` +(extend-protocol proto/ISQLType + Object + (-convert [v] v)) + (defn- result-record->record [^org.jooq.Record record] - (into {} (for [^int i (range (.size record))] - (let [^Field field (.field record i) - value (.getValue record i)] - [(keyword (.toLowerCase (.getName field))) - (if (satisfies? proto/ISQLType value) - (proto/-convert value) - value)])))) + (letfn [(reduce-fn [acc ^Field field] + (let [value (.getValue field record) + name (.getName field)] + (assoc! acc (keyword (.toLowerCase name)) + (proto/-convert value))))] + (-> (reduce reduce-fn (transient {}) (.fields record)) + (persistent!)))) (defn- result-record->row [^org.jooq.Record record] - (into [] (for [^int i (range (.size record))] - (let [value (.getValue record i)] - (if (satisfies? proto/ISQLType value) - (proto/-convert value) - value))))) + (letfn [(reduce-fn [acc ^Field field] + (let [value (.getValue field record) + name (.getName field)] + (conj! acc (proto/-convert value))))] + (-> (reduce reduce-fn (transient []) (.fields record)) + (persistent!)))) (defn- result->vector - [^org.jooq.Result result {:keys [mapfn into format] - :or {rows false format :record}}] + [^org.jooq.Result result {:keys [mapfn format] :or {rows false format :record}}] (if mapfn (mapv mapfn result) - (condp = format + (case format :record (mapv result-record->record result) :row (mapv result-record->row result) - :json (if into - (.formatJSON result into) - (.formatJSON result)) - :csv (if into - (.formatCSV result into) - (.formatCSV result))))) + :json (.formatJSON result) + :csv (.formatCSV result)))) (extend-protocol proto/IFetch String @@ -306,9 +240,10 @@ PersistentVector (-fetch [^PersistentVector sqlvec ^Context ctx opts] (let [^DSLContext context (proto/-context ctx) - ^ResultQuery query (->> (into-array Object (map (partial wrap-if-need context) (rest sqlvec))) - (.resultQuery context (first sqlvec)))] - (-> (.fetch context query) + ^String sql (first sqlvec) + params (make-params context (rest sqlvec)) + query (.resultQuery context sql params)] + (-> (.fetch context ^ResultQuery query) (result->vector opts)))) org.jooq.ResultQuery @@ -335,33 +270,33 @@ (-fetch-lazy [^String query ^Context ctx opts] (let [^DSLContext context (proto/-context ctx) ^ResultQuery query (.resultQuery context query)] - (.fetchSize query (get opts :fetch-size 60)) - (.fetchLazy context query))) + (->> (.fetchSize query (get opts :fetch-size 128)) + (.fetchLazy context)))) - clojure.lang.PersistentVector + PersistentVector (-fetch-lazy [^PersistentVector sqlvec ^Context ctx opts] (let [^DSLContext context (proto/-context ctx) - ^ResultQuery query (->> (into-array Object (rest sqlvec)) - (.resultQuery context (first sqlvec)))] - (.fetchSize query (get opts :fetch-size 100)) - (.fetchLazy context query))) + ^String sql (first sqlvec) + params (make-params context (rest sqlvec)) + query (.resultQuery context sql params)] + (->> (.fetchSize query (get opts :fetch-size 128)) + (.fetchLazy context)))) org.jooq.ResultQuery (-fetch-lazy [^ResultQuery query ^Context ctx opts] (let [^DSLContext context (proto/-context ctx)] - (.fetchSize query (get opts :fetch-size 100)) - (.fetchLazy context query)))) + (->> (.fetchSize query (get opts :fetch-size 128)) + (.fetchLazy context))))) -(defn cursor->lazyseq +(defn cursor->seq [^Cursor cursor {:keys [format mapfn] :or {format :record}}] - (let [lseq (fn thisfn [] - (when (.hasNext cursor) - (let [item (.fetchOne cursor) - record (condp = format - :record (result-record->record item) - :row (result-record->row item))] - (cons record (lazy-seq (thisfn))))))] - (lseq))) + (letfn [(transform-fn [item] + (if mapfn + (mapfn item) + (case format + :record (result-record->record item) + :row (result-record->row item))))] + (sequence (map transform-fn) cursor))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; IQuery Implementation diff --git a/src/suricatta/proto.clj b/src/suricatta/proto.clj index 9e4299f..c9e011e 100644 --- a/src/suricatta/proto.clj +++ b/src/suricatta/proto.clj @@ -29,7 +29,7 @@ (-config [_] "Get attached configuration.")) (defprotocol IConnectionFactory - (-connection [_] "Create a jdbc connection.")) + (-connection [_ _] "Create a jdbc connection.")) (defprotocol IExecute (-execute [q ctx] "Execute a query and return a number of rows affected.")) diff --git a/test/suricatta/core_test.clj b/test/suricatta/core_test.clj index 530eb13..12d32db 100644 --- a/test/suricatta/core_test.clj +++ b/test/suricatta/core_test.clj @@ -2,17 +2,11 @@ (:require [clojure.test :refer :all] [suricatta.core :as sc])) -(def dbspec {:subprotocol "h2" - :subname "mem:"}) - -(def pgdbspec {:subprotocol "postgresql" - :subname "//127.0.0.1/test"}) - (def ^:dynamic *ctx*) (defn database-fixture [end] - (with-open [ctx (sc/context pgdbspec)] + (with-open [ctx (sc/context "jdbc:postgresql://127.0.0.1/test")] (sc/atomic ctx (binding [*ctx* ctx] (end) @@ -53,16 +47,16 @@ (deftest lazy-fetch (testing "Fetch by default vector of rows." (sc/atomic *ctx* - (with-open [cursor (sc/fetch-lazy *ctx* "select x from generate_series(1, 300) as x")] - (let [res (take 3 (sc/cursor->lazyseq cursor {:format :row}))] - (is (= (mapcat identity (vec res)) [1 2 3])))))) + (with-open [cursor (sc/fetch-lazy *ctx* "select x from generate_series(1, 300) as x")] + (let [res (take 3 (sc/cursor->seq cursor {:format :row}))] + (is (= (mapcat identity (vec res)) [1 2 3])))))) (testing "Fetch by default vector of records." (sc/atomic *ctx* - (with-open [cursor (sc/fetch-lazy *ctx* "select x from generate_series(1, 300) as x")] - (let [res (take 3 (sc/cursor->lazyseq cursor))] - (is (= (vec res) [{:x 1} {:x 2} {:x 3}])))))) -) + (with-open [cursor (sc/fetch-lazy *ctx* "select x from generate_series(1, 300) as x;")] + (let [res (take 3 (sc/cursor->seq cursor))] + (is (= (vec res) [{:x 1} {:x 2} {:x 3}])))))) + ) #_(deftest fetch-format (testing "Fetch in csv format" @@ -93,7 +87,7 @@ (deftest transactions (testing "Execute in a transaction" - (with-open [ctx (sc/context dbspec)] + (with-open [ctx (sc/context "jdbc:h2:mem:")] (sc/execute ctx "create table foo (id int)") (sc/atomic ctx (sc/execute ctx ["insert into foo (id) values (?), (?)" 1 2]) @@ -108,7 +102,7 @@ (is (= 2 (count result))))))))) (testing "Execute in a transaction with explicit rollback" - (with-open [ctx (sc/context dbspec)] + (with-open [ctx (sc/context "jdbc:h2:mem:")] (sc/execute ctx "create table foo (id int)") (sc/atomic ctx (sc/execute ctx ["insert into foo (id) values (?), (?)" 1 2]) @@ -121,7 +115,7 @@ (is (= 2 (count result))))))) (testing "Execute in a transaction with explicit rollback" - (with-open [ctx (sc/context dbspec)] + (with-open [ctx (sc/context "jdbc:h2:mem:")] (sc/execute ctx "create table foo (id int)") (sc/atomic ctx (sc/execute ctx ["insert into foo (id) values (?), (?)" 1 2]) diff --git a/test/suricatta/extend_test.clj b/test/suricatta/extend_test.clj index fe6d840..eeb14aa 100644 --- a/test/suricatta/extend_test.clj +++ b/test/suricatta/extend_test.clj @@ -10,14 +10,11 @@ ;; Connection setup ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def dbspec {:subprotocol "postgresql" - :subname "//127.0.0.1/test"}) - (def ^:dynamic *ctx*) (defn setup-connection-fixture [end] - (with-open [ctx (sc/context dbspec)] + (with-open [ctx (sc/context "jdbc:postgresql://127.0.0.1/test")] (sc/atomic ctx (binding [*ctx* ctx] (end)