From fe513564989d9151a79d5494f2958ae190c20d02 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Tue, 12 Nov 2013 16:58:44 +1100 Subject: Change the query executor model: now it's query local and the interface is managed by a protocol. --- README.md | 16 +++++++++++++++- src/clojure_sql/compiler.clj | 2 +- src/clojure_sql/core.clj | 39 +++++++++++---------------------------- src/clojure_sql/dsl.clj | 18 ++++++++++++++---- src/clojure_sql/jdbc.clj | 37 ++++++++++++++++++++++++++----------- src/clojure_sql/query.clj | 24 +++++++++++++++++------- 6 files changed, 84 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 97f1afb..650c9fe 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,21 @@ be the return value of the associated query function call (`deref`, (q/group [:age] {:name :name})) ;=> Exception! Expr is not a function application - could not possible be an aggregate -* **Breaking:** Remove `having`, use `select` instead now +* **Breaking:** remove `having`, use `select` instead now + +* **Breaking:** make query executors query local, rather than + global. This changed two things in particular: + + * `table` now takes a second, optional, argument representing a + query executor + + * `set-query-executor!` is no longer present, use + `set-default-query-executor!` instead (which will be used + whenever `table`'s optional argument is omitted) + + As a result of this, the `use-jdbc!` function has been + removed. To connect to a database via jdbc use this: + `(set-default-query-executor (clojure-sql.jdbc/jdbc-executor "connection-string"))` * `sort` can now sort on arbitrary expressions, not just fields diff --git a/src/clojure_sql/compiler.clj b/src/clojure_sql/compiler.clj index 87899f9..50c14af 100644 --- a/src/clojure_sql/compiler.clj +++ b/src/clojure_sql/compiler.clj @@ -254,7 +254,7 @@ -(defn compile-insert [db {:keys [fields tables joins]} & records] +(defn compile-insert [db {:keys [fields tables joins]} records] (assert (= (count tables) 1) "Cannot insert into a multiple-table query") (let [fields-order (map key fields) wrap #(str "INSERT INTO " diff --git a/src/clojure_sql/core.clj b/src/clojure_sql/core.clj index 2a9e77f..415c7f4 100644 --- a/src/clojure_sql/core.clj +++ b/src/clojure_sql/core.clj @@ -22,40 +22,27 @@ select group sort take drop - union intersection) - - -(def ^:private ^:dynamic *database-type* nil) -(defn set-database-type! [new-type] - (alter-var-root #'*database-type* (constantly new-type)) - nil) - -(def ^:private ^:dynamic *query-executor* (comp second vector)) -(defn set-query-executor! [exec-fn] - (alter-var-root #'*query-executor* (constantly exec-fn)) - nil) - - + union intersection + set-default-query-executor!) (defmethod print-method clojure_sql.query.Query [query writer] (binding [*out* writer] - (pr (c/compile-select *database-type* query)))) + (pr (c/compile-select nil query)))) (defn run-query "Run a select query. Return value is determined by query executor." [query] - (assert *query-executor* "Cannot execute a query without a query executor") - (*query-executor* :select (c/compile-select *database-type* query))) + (assert (:executor query) "Cannot execute a query without a query executor") + (q/query (:executor query) query)) (defn insert! "Insert a number of records into a table, setting each column to the corresponding value from the record. Return value is determined by query executor." [query & records] - (assert *query-executor* "Cannot execute a query without a query executor") - (let [compiled (apply c/compile-insert *database-type* query records)] - (*query-executor* :insert compiled))) + (assert (:executor query) "Cannot execute a query without a query executor") + (q/insert! (:executor query) query records)) (defn update! "Update everything which would have been selected by the query, @@ -67,16 +54,12 @@ with `project` before calling `update!`." [query partial-record] - (assert *query-executor* "Cannot execute a query without a query executor") - (let [compiled (c/compile-update *database-type* query partial-record)] - (*query-executor* :update compiled))) + (assert (:executor query) "Cannot execute a query without a query executor") + (q/update! (:executor query) query partial-record)) (defn delete! "Delete everything which would have been selected by the query. Return value is determined by query executor." [query] - (assert *query-executor* "Cannot execute a query without a query executor") - (let [compiled (c/compile-delete *database-type* query)] - (*query-executor* :delete compiled))) - -(q/set-query-deref-behaviour! run-query) + (assert (:executor query) "Cannot execute a query without a query executor") + (q/delete! (:executor query) query)) diff --git a/src/clojure_sql/dsl.clj b/src/clojure_sql/dsl.clj index 413b9c0..2a22eea 100644 --- a/src/clojure_sql/dsl.clj +++ b/src/clojure_sql/dsl.clj @@ -85,14 +85,21 @@ (keyword? expression) (resolve-field tables aliases expression) :else expression)) +(def ^:dynamic *default-executor* nil) +(defn set-default-query-executor! [executor] + (alter-var-root #'*default-executor* (constantly executor))) +(defmacro with-default-query-executor [executor & body] + `(binding [*default-executor* ~executor] ~@body)) + (defn table "Create a query on a database table. If `table` is itself a query it will be wrapped, otherwise `table` will be used as the table name." - [table] + [table & [executor]] (q/map->Query (let [table-name (if (u/named? table) (name table) "table") table-keyword (keyword (gensym table-name))] {:tables {table-keyword table} - :joins [table-keyword]}))) + :joins [table-keyword] + :executor (or executor *default-executor*)}))) (defn ^:private into-map-duplicate-error [coll error-fn] (reduce (fn [acc [k v]] @@ -229,6 +236,7 @@ outer join is considered a LEFT outer join. To achieve a right outer join reverse the query arguments." [left right & {:keys [on type]}] + (assert (= (:executor left) (:executor right)) "Cannot join queries with different executors.") (let [left (make-join-subquery left) right (rename-all-tables (make-join-subquery right)) merged-tables (merge (:tables left) (:tables right)) @@ -387,7 +395,8 @@ (defn ^:private union-compatible? [& queries] (and (every? (comp seq keys :fields) queries) - (apply = (map (comp set keys :fields) queries)))) + (apply = (map (comp set keys :fields) queries)) + (apply = (map :executor queries)))) (defn union "Combine the results of two queries. @@ -407,4 +416,5 @@ {:pre [(apply union-compatible? queries)]} (convert-to-subquery (q/map->Query {:set-operation :intersect :queries queries - :fields (zipmap (keys (:fields (first queries))) (repeat nil))}))) + :fields (zipmap (keys (:fields (first queries))) (repeat nil)) + :executor (:executor (first queries))}))) diff --git a/src/clojure_sql/jdbc.clj b/src/clojure_sql/jdbc.clj index 44ed55e..5a3a55d 100644 --- a/src/clojure_sql/jdbc.clj +++ b/src/clojure_sql/jdbc.clj @@ -1,6 +1,8 @@ (ns clojure-sql.jdbc (:require [clojure.java.jdbc :as jdbc] - [clojure-sql.core :refer [set-query-executor!]] + [clojure-sql.query :refer [QueryExecutor fn->QueryExecutor]] + [clojure-sql.compiler :as compiler] + ;;[clojure-sql.core :refer [set-query-executor!]] [clojure.string :as string])) (defn ^:private dotted-to-nested-map-one [obj] @@ -16,13 +18,26 @@ (defn ^:private dotted-to-nested-maps [objs] (mapv dotted-to-nested-map-one objs)) -(defn use-jdbc! [connection-string] - (set-query-executor! (fn [type query] - (jdbc/with-connection connection-string - (case type - :select (jdbc/with-query-results results query - (dotted-to-nested-maps results)) - :insert (jdbc/do-prepared-return-keys (first query) (next query)) - :update (jdbc/do-prepared-return-keys (first query) (next query)) - :delete (first (jdbc/do-prepared (first query) (next query))) - (assert false (str "Unknown query type: " type))))))) +(defn jdbc-executor [connection-string] + (let [[_ db-type] (re-find #"^jdbc:([^:]+)" connection-string)] + (reify QueryExecutor + (query [_ query] + (let [compiled (compiler/compile-select db-type query)] + (jdbc/with-connection connection-string + (jdbc/with-query-results results compiled + (dotted-to-nested-maps results))))) + + (insert! [_ query records] + (let [compiled (compiler/compile-insert db-type query records)] + (jdbc/with-connection connection-string + (jdbc/do-prepared-return-keys (first query) (next query))))) + + (update! [_ query partial-record] + (let [compiled (compiler/compile-update db-type query partial-record)] + (jdbc/with-connection connection-string + (jdbc/do-prepared-return-keys (first query) (next query))))) + + (delete! [_ query] + (let [compiled (compiler/compile-delete db-type query)] + (jdbc/with-connection connection-string + (first (jdbc/do-prepared (first query) (next query))))))))) diff --git a/src/clojure_sql/query.clj b/src/clojure_sql/query.clj index 218ae7a..113e748 100644 --- a/src/clojure_sql/query.clj +++ b/src/clojure_sql/query.clj @@ -1,13 +1,23 @@ (ns clojure-sql.query) -(def ^:private ^:dynamic *query-deref-behaviour* identity) -(defn set-query-deref-behaviour! [f] - (alter-var-root #'*query-deref-behaviour* (constantly f)) - nil) +(defprotocol QueryExecutor + (query [_ query] + "Retrieve information from the database.") + (insert! [_ query records] + "Insert a number of records into a table, setting each column to the corresponding value in its record.") + (update! [_ query partial-record] + "Update every row which this query would select by setting each field to the corresponding value in partial-record.") + (delete! [_ query] + "Delete every row which this query would select.")) -(defrecord ^:private Query [] +(defn fn->QueryExecutor [f] + (reify QueryExecutor + (query [_ query] ()))) + +(defrecord ^:private Query [executor] clojure.lang.IDeref - (deref [this] (*query-deref-behaviour* this))) + (deref [this] + (assert executor "Cannot deref a query without a query executor") + (query executor this))) (def query? (partial instance? Query)) - -- cgit v1.2.3