diff --git a/project.clj b/project.clj index 474d938..ac37a04 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject views "0.5.0" +(defproject views "0.5.1" :description "You underestimate the power of the SQL side" :url "https://github.com/diligenceengine/views" diff --git a/src/views/db/checks.clj b/src/views/db/checks.clj index c6f2902..9a76fe6 100644 --- a/src/views/db/checks.clj +++ b/src/views/db/checks.clj @@ -38,9 +38,8 @@ (defn have-overlapping-tables? "Takes two Honeysql hash-maps, one for action, one for view, and returns boolean value representing whether or not their set of tables intersect." - [action view] - (->> [action view] - (map (comp set #(map first %) vh/extract-tables)) - (apply intersection) - seq - boolean)) + [action view refresh?] + (let [a (set (map first (vh/extract-tables action)))] + (if refresh? + (boolean (seq (intersection a (vh/query-tables view)))) + (boolean (seq (intersection a (set (map first (vh/extract-tables view))))))))) diff --git a/src/views/db/deltas.clj b/src/views/db/deltas.clj index 34be129..8bd62d9 100644 --- a/src/views/db/deltas.clj +++ b/src/views/db/deltas.clj @@ -310,7 +310,7 @@ as the original :view-sig the deltas apply to." [persistence namespace schema db all-views action templates] (j/with-db-transaction [t db :isolation :serializable] - (let [filtered-views (filterv #(vc/have-overlapping-tables? action (:view %)) all-views) + (let [filtered-views (filterv #(vc/have-overlapping-tables? action (:view %) (:refresh-only? %)) all-views) {full-refresh-views true normal-views nil} (group-by :refresh-only? filtered-views) need-deltas (map #(generate-view-delta-map % action) normal-views) table (-> action vh/extract-tables ffirst) diff --git a/src/views/db/honeysql.clj b/src/views/db/honeysql.clj index 686572e..bf2619c 100644 --- a/src/views/db/honeysql.clj +++ b/src/views/db/honeysql.clj @@ -15,13 +15,13 @@ (defn process-complex-clause [tables clause] (reduce - #(if (coll? %2) + #(if (coll? %2) (if (some pred-ops [(first %2)]) %1 (conj %1 %2)) (conj %1 [%2])) - tables - clause)) + tables + clause)) (defn extract-tables* [tables clause] @@ -31,12 +31,6 @@ (conj tables [clause])) tables)) -(defn with-op - "Takes a collection of things and returns either an nary op of them, or - the item in the collection if there is only one." - [op coll] - (if (> (count coll) 1) (into [op] coll) (first coll))) - (defn extract-tables "Extracts a set of table vector from a HoneySQL spec hash-map. Each vector either contains a single table keyword, or the @@ -44,6 +38,74 @@ ([hh-spec] (extract-tables hh-spec table-clauses)) ([hh-spec clauses] (reduce #(extract-tables* %1 (%2 hh-spec)) #{} clauses))) +;; The following is used for full refresh views where we can have CTEs and +;; subselects in play. + +(declare query-tables) + +(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 + [coll] + (let[maps (filterv map? coll) + colls (filter #(and (coll? %) (not (map? %))) coll)] + (into maps (mapcat collect-maps colls)))) + +(defn where-tables + "This search for subqueries in the where clause." + [query] + (mapcat query-tables (collect-maps (:where query)))) + +(defn insert-tables + [query] + (if-let [v (:insert-into query)] [v] [])) + +(defn update-tables + [query] + (if-let [v (:update query)] [v] [])) + +(defn delete-tables + [query] + (if-let [v (:delete-from query)] [v] [])) + +(defn query-tables + [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)))) + +(defn with-op + "Takes a collection of things and returns either an nary op of them, or + the item in the collection if there is only one." + [op coll] + (if (> (count coll) 1) (into [op] coll) (first coll))) + (defn find-table-aliases "Returns the table alias for the supplied table." [action-table tables] diff --git a/test/views/db/honeysql_test.clj b/test/views/db/honeysql_test.clj index 917c541..95d4596 100644 --- a/test/views/db/honeysql_test.clj +++ b/test/views/db/honeysql_test.clj @@ -34,6 +34,30 @@ (is (= (vh/extract-tables join-with-alias-test) #{[:foo] [:bar :b]})) (is (= (vh/extract-tables join-and-from-with-alias-test) #{[:foo :f] [:bar :b]}))) +(def cte-test + {:with [[:a {:select [:*] :from [:bar]}]] + :select [:*] :from [:foo]}) + +(def from-subselect-test + {:select [:*] :from [[{:select [:*] :from [:foo]} :a]]}) + +(def where-subselect-test + {:select [:*] :from [:foo] :where [:in :a {:select [:*] :from [:bar]}]}) + +(def nested-where-subselect-test + {:select [:*] :from [:foo] :where [:and [:in :a {:select [:*] :from [:bar]}] [:in :a {:select [:*] :from [:baz]}]]}) + +(deftest extracts-tables-from-full-refresh-specs + (is (= (vh/query-tables simple-test) #{:foo})) + (is (= (vh/query-tables insert-test) #{:foo})) + (is (= (vh/query-tables join-test) #{:foo :bar})) + (is (= (vh/query-tables join-with-alias-test) #{:foo :bar})) + (is (= (vh/query-tables join-and-from-with-alias-test) #{:foo :bar})) + (is (= (vh/query-tables cte-test) #{:foo :bar})) + (is (= (vh/query-tables from-subselect-test) #{:foo})) + (is (= (vh/query-tables where-subselect-test) #{:foo :bar})) + (is (= (vh/query-tables nested-where-subselect-test) #{:foo :bar :baz}))) + ;; Do we really need to test the new version? (deftest merges-where-clauses (is (= (vh/merge-where-clauses [:= :foo 1] [:= :bar 2])