(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])))))