Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote branch 'trampoline/fix_aliased_table_join_order'

re-bumped version to snapshot
Conflicts:
	project.clj
  • Loading branch information...
commit de90ab6b7e295fef6665f9df9ceb3ca19431a40e 2 parents cdf5bed + 027548b
@bendlas bendlas authored
View
2  project.clj
@@ -1,4 +1,4 @@
-(defproject clojureql "1.0.2"
+(defproject clojureql "1.1.0-SNAPSHOT"
:description "Superior SQL integration for Clojure"
:dependencies [[org.clojure/clojure "1.2.1"]
[org.clojure/core.incubator "0.1.0"]
View
20 src/clojureql/core.clj
@@ -255,25 +255,7 @@
(outer-join this table2 nil join-on))
(outer-join [this table2 type join-on]
- (let [sort-joins (fn sort-joins [joins]
- (let [to-tbl-name (fn to-tbl-name [{[table-name join-on] :data :as join}]
- (->> join-on :cols
- (map #(-> % name (.replaceAll "\\..*" "")))
- (filter #(not= % table-name))
- first))
- to-graph-el (fn to-graph-el [m {[table-name join-on] :data :as join}]
- (let [required-table (to-tbl-name join)]
- (assoc m table-name required-table)))
- map-of-joins (reduce #(let [{[table-name join-on] :data :as join} %2
- k table-name]
- (assoc %1 k (conj (%1 k) join))) {} joins)
- edges (reduce to-graph-el {} joins)
- set-of-root-nodes (clojure.set/difference (into #{} (vals edges)) (into #{} (keys edges)))
- add-deps (fn add-deps [tbl]
- (into [(map-of-joins tbl)] (map add-deps (filter #(= tbl (edges %)) (keys edges)))))
- sorted-joins (filter #(not (nil? %)) (flatten (map add-deps set-of-root-nodes)))]
- sorted-joins))
- j (into (or joins []) (-> table2 :joins (or [])))]
+ (let [j (into (or joins []) (-> table2 :joins (or [])))]
(if (requires-subselect? table2)
(assoc this
:tcols (into (or tcols [])
View
110 src/clojureql/internal.clj
@@ -3,7 +3,7 @@
(:require
[clojure.java.jdbc.internal :as jdbcint]
[clojure.java.jdbc :as jdbc])
- (:use [clojure.string :only [join upper-case] :rename {join join-str}]
+ (:use [clojure.string :only [join split upper-case replace] :rename {join join-str replace replace-str}]
[clojure.core.incubator :only [-?> -?>>]]))
(defn upper-name [kw]
@@ -445,3 +445,111 @@
(if (zero? (first result))
(conj-rows table (keys record) (vals record))
result))))
+
+(defn table-alias
+ "given an RTable tname, which is either
+ - a simple table-name string
+ - a {:table-name :alias} map
+ return
+ in the case of the simple string, the string itself
+ in the case of the alias map, the value of the explicit alias"
+ [tname]
+ (if (map? tname)
+ (str (nskeyword (-> tname vals last)))
+ (nskeyword tname)))
+
+(defn subselect-table-alias
+ "given an RTable tname, which is either
+ - a simple table-name string
+ - a {:table-name :alias} map
+ return
+ in the case of the simple string, the string with _subselect appended
+ in the case of the alias map, the value of the explicit alias"
+ [tname]
+ (if (map? tname)
+ (str (nskeyword (-> tname vals last)))
+ (str (nskeyword tname) "_subselect")))
+
+(defn join-table-alias
+ "given tna from the first position of the :data value of a join definition,
+ which is one of
+ - an RTable (for subselects)
+ - a simple table-name
+ - a space separated \"table-name alias\" pair
+ return
+ - if the object was an RTable, return the subselect table
+ alias, which is either an explicit alias, or
+ (str (nskeyword (:tname tna)) \"_subselect\")
+ - if the object was a simple table-name, return it
+ - if the object was an aliased table-name, return
+ just the alias"
+ [tna]
+ (if (map? tna)
+ (subselect-table-alias (:tname tna))
+ (let [[table-name alias] (split tna #"\s+")]
+ (or alias table-name))))
+
+(defn joins-by-table-alias
+ "given a list of joins return a map of joins
+ keyed by the join table-alias or,
+ in the case of subselects, the subselect table alias"
+ [joins]
+ (reduce #(let [{[table-name-alias join-on] :data :as join} %2
+ k (join-table-alias table-name-alias)]
+ (assoc %1 k join))
+ {}
+ joins))
+
+(defn join-column-names
+ "given table-name-alias from the first position of the :data value of a join def,
+ if table-name-alias is an RTable, representing a subselect, then return the renamed
+ join cols according the the subselect alias. if table-name-alias is not an
+ RTable then return cols unmodified"
+ [table-name-alias cols]
+ (if (map? table-name-alias)
+ (let [otn (table-alias (:tname table-name-alias))
+ sstn (subselect-table-alias (:tname table-name-alias))
+ otn-patt (re-pattern (str otn "\\..*"))
+ col-strs (map nskeyword cols)
+ subselect-col-strs (filter #(re-matches otn-patt %) col-strs)
+ other-col-strs (filter #(not (re-matches otn-patt %)) col-strs)
+ renamed-subselect-col-strs (map #(replace-str % (re-pattern (str otn "\\.")) (str sstn "."))
+ subselect-col-strs)]
+ (concat renamed-subselect-col-strs other-col-strs))
+ cols))
+
+(defn to-tbl-name
+ "given a join definition, return a table alias
+ that the join depends on"
+ [{[table-name-alias join-on] :data :as join}]
+ (->> join-on :cols
+ (join-column-names table-name-alias)
+ (map #(-> % name (.replaceAll "\\..*" "")))
+ (filter #(not= % (join-table-alias table-name-alias)))
+ first))
+
+(defn to-graph-el
+ "given a join definition return an edge in the table
+ alias dependency graph"
+ [m {[table-name-alias join-on] :data :as join}]
+ (let [required-table (to-tbl-name join)]
+ (assoc m (join-table-alias table-name-alias) required-table)))
+
+(defn add-deps
+ "recursively add dependencies of tbl into a list"
+ [map-of-joins edges tbl]
+ (into [(map-of-joins tbl)] (map #(add-deps map-of-joins edges %)
+ (filter #(= tbl (edges %))
+ (keys edges)))))
+
+(defn flatten-deps
+ [map-of-joins edges set-of-root-nodes]
+ (filter #(not (nil? %)) (flatten (map #(add-deps map-of-joins edges %) set-of-root-nodes))))
+
+(defn sort-joins
+ "sort a list of joins into dependency order"
+ [joins]
+ (let [map-of-joins (joins-by-table-alias joins)
+ edges (reduce to-graph-el {} joins)
+ set-of-root-nodes (clojure.set/difference (into #{} (vals edges)) (into #{} (keys edges)))]
+ (flatten-deps map-of-joins edges set-of-root-nodes)))
View
163 test/clojureql/test/internal.clj
@@ -30,3 +30,166 @@
"user" "user"
{:user :developer} "developer"
{"user" "developer"} "developer"))
+
+(deftest test-table-alias
+ (are [tname expected]
+ (is (= expected (table-alias tname)))
+ :user "user"
+ "user" "user"
+ {:user :u} "u"))
+
+(deftest test-subselect-table-alias
+ (are [tname expected]
+ (is (= expected (subselect-table-alias tname)))
+ :user "user_subselect"
+ "user" "user_subselect"
+ {:user :u} "u"))
+
+(deftest test-join-table-alias
+ (are [table-name-alias expected]
+ (is (= expected (join-table-alias table-name-alias)))
+ "user" "user"
+ "user u" "u"
+ {:tname :user} "user_subselect"))
+
+(deftest test-joins-by-table-alias
+ (are [joins expected]
+ (is (= expected (joins-by-table-alias joins)))
+
+ ;; simple case : join to an unaliased table
+ [{:data ["user" {:cols ["user.company_id" "companies.id"]}]}]
+ {"user" {:data ["user" {:cols ["user.company_id" "companies.id"]}]}}
+
+ ;; join to an aliased table
+ [{:data ["user u" {:cols ["u.company_id" "companies.id"]}]}]
+ {"u" {:data ["user u" {:cols ["u.company_id" "companies.id"]}]}}
+
+ ;; join to a subselect
+ [{:data [{:tname :user} {:cols ["user.company_id" "companies.id"]}]}]
+ {"user_subselect" {:data [{:tname :user} {:cols ["user.company_id" "companies.id"]}]}}
+
+ ;; join to a subselect with explicit alias
+ [{:data [{:tname {:user :u}} {:cols ["u.company_id" "companies.id"]}]}]
+ {"u" {:data [{:tname {:user :u}} {:cols ["u.company_id" "companies.id"]}]}}
+
+ ;; multiple joins
+ [{:data ["user" {:cols ["user.company_id" "companies.id"]}]}
+ {:data ["hats" {:cols ["hats.id" "user.hat_id"]}]}]
+ {"user" {:data ["user" {:cols ["user.company_id" "companies.id"]}]}
+ "hats" {:data ["hats" {:cols ["hats.id" "user.hat_id"]}]}}))
+
+(deftest test-join-column-names
+ (are [table-name-alias cols expected]
+ (is (= expected (join-column-names table-name-alias cols)))
+
+ ;; simple case : unaliased table
+ "user" ["user.company_id" "companies.id"] ["user.company_id" "companies.id"]
+
+ ;; simple case : aliased table
+ "user u" ["u.company_id" "companies.id"] ["u.company_id" "companies.id"]
+
+ ;; subselect
+ {:tname :user} ["user.company_id" "companies.id"] ["user_subselect.company_id" "companies.id"]
+
+ ;; subselect with expicit alias
+ {:tname {:user :u}} ["u.company_id" "companies.id"] ["u.company_id" "companies.id"]))
+
+(deftest test-to-tbl-name
+ (are [join expected]
+ (is (= expected (to-tbl-name join)))
+
+ ;; join to an unaliased table
+ {:data ["user" {:cols ["user.company_id" "companies.id"]}]} "companies"
+
+ ;; join to an aliased table
+ {:data ["user u" {:cols ["u.company_id" "companies.id"]}]} "companies"
+
+ ;; join to a subselect
+ {:data [{:tname :user} {:cols ["user.company_id" "companies.id"]}]} "companies"
+
+ ;; join to an explicitly aliased subselect
+ {:data [{:tname {:user :u}} {:cols ["u.company_id" "companies.id"]}]} "companies"))
+
+(deftest test-to-graph-el
+ (are [m join expected]
+ (is (= expected (to-graph-el m join)))
+
+ ;; join to an unaliased table
+ {} {:data ["user" {:cols ["user.company_id" "companies.id"]}]} {"user" "companies"}
+
+ ;; join to an aliased table
+ {} {:data ["user u" {:cols ["u.company_id" "companies.id"]}]} {"u" "companies"}
+
+ ;; join to a subselect
+ {} {:data [{:tname :user} {:cols ["user.company_id" "companies.id"]}]} {"user_subselect" "companies"}
+
+ ;; join to an explicitly aliased subselect
+ {} {:data [{:tname {:user :u}} {:cols ["u.company_id" "companies.id"]}]} {"u" "companies"}))
+
+(deftest test-add-deps
+ (are [map-of-joins edges tbl expected]
+ (is (= expected (add-deps map-of-joins edges tbl)))
+
+ ;; deps for a from a join b join c
+ {"b" :b-join "c" :c-join}
+ {"b" "a" "c" "b"}
+ "a"
+ [nil [:b-join [:c-join]]]
+
+ ;; deps for b from a join b join c
+ {"b" :b-join "c" :c-join}
+ {"b" "a" "c" "b"}
+ "b"
+ [:b-join [:c-join]]
+
+ ;; deps for c from a join b join c
+ {"b" :b-join "c" :c-join}
+ {"b" "a" "c" "b"}
+ "c"
+ [:c-join]
+
+ ;; deps for a from a (join b join c) (join d join e)
+ {"b" :b-join "c" :c-join "d" :d-join "e" :e-join}
+ {"b" "a" "c" "b" "d" "a" "e" "d"}
+ "a"
+ [nil [:b-join [:c-join]] [:d-join [:e-join]]]
+
+ ;; deps for a from a (join b (join c) (join d)) (join e (join f) (join g))
+ {"b" :b-join "c" :c-join "d" :d-join "e" :e-join "f" :f-join "g" :g-join}
+ {"b" "a" "c" "b" "d" "b" "e" "a" "f" "e" "g" "e"}
+ "a"
+ [nil [:b-join [:c-join] [:d-join]] [:e-join [:f-join] [:g-join]]]
+
+ ;; deps for a from (a (join b join c join d) (e (join f) (join g))
+ {"b" :b-join "c" :c-join "d" :d-join "e" :e-join "f" :f-join "g" :g-join}
+ {"b" "a" "c" "b" "d" "c" "f" "e" "g" "e"}
+ "a"
+ [nil [:b-join [:c-join [:d-join]]]]
+
+ ;; deps for e from (a (join b join c join d) (e (join f (join g) (join h)))
+ {"b" :b-join "c" :c-join "d" :d-join "f" :f-join "g" :g-join "h" :h-join}
+ {"b" "a" "c" "b" "d" "c" "f" "e" "g" "f" "h" "f"}
+ "e"
+ [nil [:f-join [:g-join] [:h-join]]]))
+
+(deftest test-flatten-deps
+ (are [map-of-joins edges set-of-root-nodes expected]
+ (is (= expected (flatten-deps map-of-joins edges set-of-root-nodes)))
+
+ ;; deps for a from a (join b join c) (join d join e)
+ {"b" :b-join "c" :c-join "d" :d-join "e" :e-join}
+ {"b" "a" "c" "b" "d" "a" "e" "d"}
+ #{"a"}
+ [:b-join :c-join :d-join :e-join]
+
+ ;; deps for a from a (join b (join c) (join d)) (join e (join f) (join g))
+ {"b" :b-join "c" :c-join "d" :d-join "e" :e-join "f" :f-join "g" :g-join}
+ {"b" "a" "c" "b" "d" "b" "e" "a" "f" "e" "g" "e"}
+ #{"a"}
+ [:b-join :c-join :d-join :e-join :f-join :g-join]
+
+ ;; deps for e from (a (join b join c join d) (e (join f (join g) (join h)))
+ {"b" :b-join "c" :c-join "d" :d-join "f" :f-join "g" :g-join "h" :h-join}
+ {"b" "a" "c" "b" "d" "c" "f" "e" "g" "f" "h" "f"}
+ #{"a" "e"}
+ [:b-join :c-join :d-join :f-join :g-join :h-join]))
Please sign in to comment.
Something went wrong with that request. Please try again.