summaryrefslogtreecommitdiff
path: root/src/clojure_sql/dsl.clj
blob: 68446d1ae6659be8be12e3e8fd12a74955c115cc (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
(ns clojure-sql.dsl
  (:refer-clojure :exclude [sort-by])
  (:require [clojure.set :as set]
            [clojure.walk]
            [clojure-sql.query :as q]
            [clojure-sql.util :as u]))


;; ==============================================================
;; The DSL for making query maps
;; ==============================================================

;; important sections:
;;   -PROJECTION-
;;   -TABLES-
;;   -FILTERS-
;;   GROUPING
;;   GROUPED FILTERS
;;   -SORTING-

;; {
;;  :tables => tablename -> table_alias
;;  :fields => (table_alias, fieldname) -> field_alias
;;  :joins => [left-table type right-table on?]
;;  :where => expression
;;  :sort-by => [(field, direction)]
;; }

(defn table [arg]
  (into (q/->Query)
        (if (map? arg)
          {:tables (u/flip-map arg)}
          {:tables {arg arg}})))

(defn ^:private ambiguous-error [field & [query]]
  (throw (ex-info (str "Ambiguous field " field " in query with more than one table")
                  {:field field
                   :query query})))

(defn ^:private resolve-field [table aliases field]
  (let [field-alias-lookup aliases]
    (or (field-alias-lookup field)
        (if table
          [table field]
          (ambiguous-error field)))))

(defn ^:private resolve-fields [table aliases expression]
  (cond (list? expression) (map (partial resolve-fields table aliases) expression)
        (vector? expression) (mapv (partial resolve-fields table aliases) expression)
        (keyword? expression) (resolve-field table aliases expression)
        :else expression))

(defn project [query fields]
  (let [table (if (= (count (:tables query)) 1)
                (-> query :tables first key))
        alias-lookup (or (:fields query) {})
        original-name #(resolve-fields table alias-lookup %)]
    (assoc query
      :fields (->> (for [[key val] (if (map? fields)
                                     fields
                                     (zipmap fields fields))]
                     [val (original-name key)])
                   (into {})))))

(defn rename [query field-renames]
  {:pre [(map? field-renames)
         ;; the intersection of the new aliases with the old aliases NOT renamed by this operation
         (empty? (set/intersection (set (vals field-renames))
                                   (set/difference (set (keys (:fields query)))
                                                   (set (keys field-renames)))))]}
  (let [fields (:fields query)
        alias-lookup (or (:fields query) {}) 
        fields-to-remove (set/difference (set (keys field-renames))
                                         (set (vals field-renames)))
        original-name (fn [field]
                        (cond (vector? field) field
                              (contains? alias-lookup field) (get alias-lookup field)
                              :else (throw (ex-info (str "Cannot rename field " (pr-str field) ". Field does not exist in query.")
                                                    {:field field
                                                     :query query
                                                     :renames field-renames})))
                        (get alias-lookup field))
        ;; do the renaming we want here:
        fields (->> (for [[key val] field-renames]
                      [val (original-name key)]) 
                    (into fields))
        ;; remove the things we no longer have here:
        fields (->> (remove (comp fields-to-remove key) fields)
                    (into {}))]
    (assoc query :fields fields)))

;; (defn ^:private combine-wheres [& wheres]
;;   (reduce (fn [acc where]
;;             (cond (nil? acc) where
;;                   (nil? where) acc
;;                   :else (or (if (and (sequential? where)
;;                                      (= (name (first where)) "and"))
;;                               `(and ~acc ~@(next where)))
;;                             (if (and (sequential? acc)
;;                                      (= (name (first acc)) "and"))
;;                               `(and ~@(next acc) ~where))
;;                             `(and ~acc ~where))))
;;           nil wheres))

;; (defn join [left right & {:keys [on type]}]
;;   (let [joins-vector (or (:join left) []) 
;;         common-fields (set/intersection (set (vals (:fields left)))
;;                                         (set (vals (:fields right))))
;;         joined-fields (if (= type :right) 
;;                         (merge (->> (:fields left)
;;                                     (filter (comp not common-fields val))
;;                                     (into {}))
;;                                (:fields right))
;;                         (merge (:fields left)
;;                                (->> (:fields right)
;;                                     (filter (comp not common-fields val))
;;                                     (into {}))))
;;         implicit-on (if (seq common-fields)
;;                       (map (fn [field]
;;                              `(= ~(resolve-field (:table left) (:fields left) field)
;;                                  ~(resolve-field (:table right) (:fields right) field)))
;;                            common-fields))
;;         on (if on
;;              [(resolve-fields nil joined-fields on)])
;;         join-condition (if-let [condition (seq (concat implicit-on on))]
;;                          `(and ~@condition)
;;                          true)]
;;     (-> left
;;         (assoc :fields joined-fields)
;;         (assoc :joins (into (conj joins-vector
;;                                   [(or type :inner) (:table right) join-condition])
;;                             (:joins left)))
;;         (assoc :where (combine-wheres (:where left) (:where right))))))

;; (defn select [query expression]
;;   (let [table-name (if-not (:joins query)
;;                      (-> query :table first val))
;;         old-where (:where query)
;;         resolved-expression (resolve-fields table-name (:fields query) expression)
;;         new-where (combine-wheres old-where resolved-expression)]
;;     (assoc query :where new-where)))

;; (defn sort-by [query fields]
;;   (let [table-name (if-not (:joins query)
;;                      (-> query :table first val))
;;         fields-seq (if (sequential? fields)
;;                      fields
;;                      [fields])]
;;     (assoc query
;;       :sort-by (for [field fields-seq]
;;                  (if (vector? field)
;;                    (resolve-field table-name (:fields query) field)
;;                    [(resolve-field table-name (:fields query) field) :asc])))))