From abc65cec79f2e30e27b0de2391b9a51f3e92337f Mon Sep 17 00:00:00 2001 From: "Alexander K. Hudek" Date: Tue, 21 Apr 2015 01:27:02 -0400 Subject: [PATCH] Initial commit. --- .gitignore | 13 +++++++ project.clj | 13 +++++-- src/views/honeysql/core.clj | 45 +++++++++++++++++++++ src/views/honeysql/util.clj | 78 +++++++++++++++++++++++++++++++++++++ src/views/honeysql/view.clj | 27 +++++++++++++ src/views_honeysql/core.clj | 6 --- 6 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 src/views/honeysql/core.clj create mode 100644 src/views/honeysql/util.clj create mode 100644 src/views/honeysql/view.clj delete mode 100644 src/views_honeysql/core.clj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d23decf --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/target +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +/.lein-* +/.nrepl-port +.hgignore +.hg/ +.idea +*.iml diff --git a/project.clj b/project.clj index f1f60de..04a71cd 100644 --- a/project.clj +++ b/project.clj @@ -1,6 +1,11 @@ -(defproject views-honeysql "0.1.0-SNAPSHOT" +(defproject views/honeysql "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" - :license {:name "Eclipse Public License" - :url "http://www.eclipse.org/legal/epl-v10.html"} - :dependencies [[org.clojure/clojure "1.6.0"]]) + :license {:name "MIT License" + :url "http://opensource.org/licenses/MIT"} + :dependencies [[views "1.4.0-SNAPSHOT"] + [honeysql "0.5.2"] + [org.clojure/java.jdbc "0.3.6"] + [org.clojure/tools.logging "0.3.1"]] + :profiles {:dev {:dependencies + [[org.clojure/clojure "1.6.0"]]}}) diff --git a/src/views/honeysql/core.clj b/src/views/honeysql/core.clj new file mode 100644 index 0000000..247fe81 --- /dev/null +++ b/src/views/honeysql/core.clj @@ -0,0 +1,45 @@ +(ns views.honeysql.core + (:require + [views.core :refer [hint]] + [views.honeysql.util :refer [query-tables]] + [honeysql.core :as hsql] + [clojure.tools.logging :refer [error]] + [clojure.java.jdbc :as j])) + +(def send-hints! (atom (fn [hints] (error "send-hints! not configured")))) + +(defmacro with-view-transaction + "Like with-db-transaction, but sends view hints at end of transaction." + [binding & forms] + (let [tvar (first binding), db (second binding), args (drop 2 binding)] + `(if (:hints ~db) ;; check if we are in a nested transaction + (let [~tvar ~db] ~@forms) + (let [hints# (atom []) + result# (j/with-db-transaction [t# ~db ~@args] + (let [~tvar (assoc ~db :hints hints#)] + ~@forms))] + (@send-hints! @hints#) + result#)))) + +(defn execute-honeysql! + "Always return keys for inserts." + [db hsql-map] + (if-let [table (:insert-into hsql-map)] + (if (vector? table) + (j/execute! db (hsql/format hsql-map)) + (apply j/insert! db table (:values hsql-map))) + (j/execute! db (hsql/format hsql-map)))) + +(defn vexec! + "Used to perform arbitrary insert/update/delete actions on the database, + while ensuring that view hints are sent to the view system. + Arguments are: + - db: a clojure.java.jdbc database with fid field + - action-map: the HoneySQL map for the insert/update/delete action" + [db action-map] + (let [results (execute-honeysql! db action-map) + hsql-hint (hint :views/honeysql (query-tables action-map))] + (if-let [hints (:hints db)] + (swap! hints conj hsql-hint) + (send-hints! [hsql-hint])) + results)) diff --git a/src/views/honeysql/util.clj b/src/views/honeysql/util.clj new file mode 100644 index 0000000..fb85c31 --- /dev/null +++ b/src/views/honeysql/util.clj @@ -0,0 +1,78 @@ +(ns views.honeysql.util + (:require + [honeysql.core :as hsql] + [honeysql.helpers :as hh] + [clojure.string :refer [split]])) +;; The following is used for full refresh views where we can have CTEs and +;; subselects in play. + +(declare query-tables) + +(defn- first-leaf + "Retrieves the first leaf in a collection of collections + + (first-leaf :table) -> :table + (first-leaf [[:table] [& values]]) -> :table" + [v] + (if (coll? v) (recur (first v)) v)) + +(defn cte-tables + [query] + (mapcat #(query-tables (second %)) (:with query))) + +(defn isolate-tables + "Isolates tables from table definitions in from and join clauses." + [c] + (if (keyword? c) [c] (let [v (first c)] (if (map? v) (query-tables v) [v])))) + +(defn from-tables + [query] + (mapcat isolate-tables (:from query))) + +(defn every-second + [coll] + (map first (partition 2 coll))) + +(defn join-tables + [query k] + (mapcat isolate-tables (every-second (k query)))) + +(defn collect-maps + [wc] + (cond + (coll? wc) (let [maps (filterv map? wc) + colls (filter #(and (coll? %) (not (map? %))) wc)] + (into maps (mapcat collect-maps colls))) + (map? wc) [wc] + :else [])) + +(defn where-tables + "This search for subqueries in the where clause." + [query] + (mapcat query-tables (collect-maps (:where query)))) + +(defn insert-tables + [query] + (some->> query :insert-into first-leaf vector)) + +(defn update-tables + [query] + (if-let [v (:update query)] [v] [])) + +(defn delete-tables + [query] + (if-let [v (:delete-from query)] [v] [])) + +(defn query-tables + "Return all the tables in an sql statement." + [query] + (set (concat + (cte-tables query) + (from-tables query) + (join-tables query :join) + (join-tables query :left-join) + (join-tables query :right-join) + (where-tables query) + (insert-tables query) + (update-tables query) + (delete-tables query)))) diff --git a/src/views/honeysql/view.clj b/src/views/honeysql/view.clj new file mode 100644 index 0000000..af59ba2 --- /dev/null +++ b/src/views/honeysql/view.clj @@ -0,0 +1,27 @@ +(ns views.honeysql.view + (:require + [views.protocols :refer :all] + [views.honeysql.util :refer [query-tables]] + [honeysql.core :as hsql] + [clojure.set :refer [intersection]] + [clojure.java.jdbc :as j] + [clojure.tools.logging :refer [warn]])) + +(defrecord HSQLView [id db query-fn post-fn] + IView + (id [_] id) + (data [_ namespace parameters] + (let [start (System/currentTimeMillis) + data (j/query db (hsql/format (apply query-fn parameters)) :row-fn post-fn) + time (- (System/currentTimeMillis) start)] + (when (>= time 1000) (warn id "took" time "msecs")) + data)) + (relevant? [_ namespace parameters hints] + (let [tables (query-tables (apply query-fn parameters)) + nhints (filter #(= :views/honeysql (:namespace %)) hints)] + (boolean (some #(not-empty (intersection (:hint %) tables)) nhints))))) + +(defn view + "Creates a Honey SQL view that uses a jdbc database configuration" + ([id db hsql-fn post-fn] (HSQLView. id db hsql-fn post-fn)) + ([id db hsql-fn] (view id db hsql-fn identity))) diff --git a/src/views_honeysql/core.clj b/src/views_honeysql/core.clj deleted file mode 100644 index 1338f11..0000000 --- a/src/views_honeysql/core.clj +++ /dev/null @@ -1,6 +0,0 @@ -(ns views-honeysql.core) - -(defn foo - "I don't do a whole lot." - [x] - (println x "Hello, World!"))