diff options
Diffstat (limited to 'src/clojure_sql/compiler.clj')
-rw-r--r-- | src/clojure_sql/compiler.clj | 216 |
1 files changed, 127 insertions, 89 deletions
diff --git a/src/clojure_sql/compiler.clj b/src/clojure_sql/compiler.clj index ca8d2d9..7ded112 100644 --- a/src/clojure_sql/compiler.clj +++ b/src/clojure_sql/compiler.clj @@ -1,7 +1,9 @@ (ns clojure-sql.compiler - (:require [clojure.string :as string])) - - + (:refer-clojure :exclude [compile sequence]) + (:require [clojure.string :as string] + [clojure-sql.query :refer [query?]] + [clojure-sql.util :as u] + [clojure-sql.writer :as w :refer [return lift p-lift sequence do-m tell >>]])) (defn add-parentheses [s] (str \( s \))) @@ -26,121 +28,129 @@ +;; we use the $ prefix to denote a lifted function +(def $add-parentheses (lift add-parentheses)) +(def $str (lift str)) + + + ;; ============================================================== ;; Utility functions for the compile-* functions ;; ============================================================== -;; compile-* multimethods are of the signature: -;; (db, expr) -> [SQL & replacements] - -(def is-unary? (comp boolean '#{"not"} name)) -(def is-binary? (comp boolean '#{"=" "<" ">" "<=" ">=" "is" "in" "+" "-" "/" "*"} name)) -(def is-operator? (comp boolean '#{"and" "or"} name)) -(defn c-return [val] [val]) -(defn c-lift [f & orig-args] - (fn [& c-args] - (apply vector - (apply f (concat orig-args - (map first c-args))) - (apply concat (map rest c-args))))) -(def c-str (c-lift str)) -(def c-join (fn [sep args] - (apply vector - (string/join sep (map first args)) - (apply concat (map rest args))))) -(def c-add-parentheses (c-lift add-parentheses)) +(def ^:private named? (some-fn string? symbol? keyword?)) +(def quote? (comp boolean '#{"quote"} name)) +(def unary? (comp boolean '#{"not" "exists"} name)) +(def binary? (comp boolean '#{"=" "<" ">" "<=" ">=" "is" "in"} name)) +(def operator? (comp boolean '#{"and" "or" "+" "-" "/" "*"} name)) ;; ============================================================== ;; compile-* functions (turning a map into a query string) ;; ============================================================== +;; compile-* multimethods are of the signature: +;; (db, expr) -> (fn [s] [sql]) + +(declare compile-query compile-expression) + +(defmulti compile-expression-sequential (fn [db ex])) +(defmethod compile-expression-sequential :default [db ex] + (let [compile-exprs #(map (partial compile-expression db) %) + op (name (first ex)) + num-args (dec (count ex))] + (-> (condp u/funcall (first ex) + quote? (do (assert (= num-args 1) "`quote` must only take one argument") + (>> (tell (second ex)) (return "?"))) + unary? (do (assert (= num-args 1) (str "Unary operator `" op "` must take one argument")) + (do-m :let [exprs (compile-exprs ex)] + vals <- (apply sequence exprs) + (return (string/join "" vals)))) + binary? (do (assert (= num-args 2) (str "Binary operator `" op "` must take two arguments")) + (do-m :let [[op left right] (compile-exprs ex)] + vals <- (sequence left op right) + (return (string/join " " vals)))) + operator? (do-m :let [[op & exprs] (compile-exprs ex)] + vals <- (apply sequence (interpose op exprs)) + (return (string/join " " vals))) + (do-m :let [fn-name (function-name db (first ex)) + exprs (compile-exprs (rest ex))] + vals <- (apply sequence exprs) + (return (str fn-name + (add-parentheses (string/join "," vals)))))) + $add-parentheses))) + (defmulti compile-expression (fn [db _] db)) (defmethod compile-expression :default [db ex] - (condp #(%1 %2) ex - nil? (c-return "NULL") - vector? (c-return (str (table-name db (first ex)) \. (field-name db (second ex)))) - keyword? (c-return (field-name db ex)) - string? ["?" ex] ;;(sql-string db ex) - symbol? (c-return (string/upper-case (name ex))) - sequential? (-> (condp #(%1 %2) (first ex) - is-unary? (if (= (count ex) 2) - (->> ex - (map (partial compile-expression db)) - (c-join " ")) - (throw (ex-info "Unary operators can only take one argument." - {:operator (first ex) - :arguments (rest ex)}))) - is-binary? (if (= (count ex) 3) - (->> (rest ex) - (map (partial compile-expression db)) - (interpose (compile-expression db (first ex))) - (c-join " ")) - (throw (ex-info "Binary operators must take two arguments." - {:operator (first ex) - :arguments (rest ex)}))) - is-operator? (->> (rest ex) - (map (partial compile-expression db)) - (interpose (compile-expression db (first ex))) - (c-join " ")) - (->> (rest ex) - (map (partial compile-expression db)) - (c-join ", ") - c-add-parentheses - (c-str (c-return (function-name db (first ex)))))) - c-add-parentheses) - (c-return ex))) + (condp u/funcall ex + query? ($add-parentheses (compile-query db ex)) + nil? (return "NULL") + vector? (return (str (table-name db (first ex)) \. (field-name db (second ex)))) + keyword? (return (field-name db ex)) + string? (>> (tell ex) (return "?")) ;;(sql-string db ex) + symbol? (return (string/upper-case (name ex))) + sequential? (compile-expression-sequential db ex) + (return ex))) (defn ^:private make-table-name [db table & [alias]] (if (or (= table alias) (nil? alias)) - (table-name db table) - (str (table-name db table) " AS " (field-name db alias))) ) - -(defn ^:private make-field-name [db table field & [alias]] - (if (or (= field alias) (nil? alias)) - (str (table-name db table) \. (field-name db field)) - (str (table-name db table) \. (field-name db field) " AS " (field-name db alias)))) + (return (table-name db table)) + ($str (condp #(%1 %2) table + query? ($add-parentheses (compile-query db table)) + named? (return (table-name db table)) + (compile-expression db table)) + (return " AS ") + (return (table-name db alias))))) + +(defn ^:private make-field-name [db field & [alias]] + (if (and (vector? field) (or (= field alias) (nil? alias))) + (compile-expression db field) + ($str (compile-expression db field) + (return " AS ") + (return (field-name db alias))))) (defmulti compile-fields (fn [db _] db)) (defmethod compile-fields :default [db fields-map] (if (seq fields-map) - (->> (for [[[table field] alias] fields-map] - (make-field-name db table field alias)) - (string/join ", ") - c-return) - (c-return "*"))) + (->> (for [[field alias] fields-map] + (make-field-name db field alias)) + (apply sequence) + ((p-lift string/join ", "))) + (return "*"))) (defmulti compile-tables (fn [db _] db)) (defmethod compile-tables :default [db tables-map] (->> (for [[table alias] tables-map] (make-table-name db table alias)) - (string/join ", ") - c-return)) + (apply sequence) + ((p-lift string/join ", ")))) (defmulti compile-join (fn [db _] db)) (defmethod compile-join :default [db [type table-map on]] - (c-str (c-return (case type - :left " LEFT OUTER" - :right " RIGHT OUTER" - " INNER")) - (c-return " JOIN ") - (compile-tables db table-map) - (c-return " ON ") - (compile-expression db on))) + ($str (return (case type + :left " LEFT OUTER" + :right " RIGHT OUTER" + " INNER")) + (return " JOIN ") + (compile-tables db table-map) + (return " ON ") + (compile-expression db on))) (defmulti compile-joins (fn [db _] db)) (defmethod compile-joins :default [db joins] (->> joins (map (partial compile-join db)) - (c-join ""))) + (apply sequence) + ((p-lift string/join "")))) (defmulti compile-where (fn [db _] db)) (defmethod compile-where :default [db expr] (if expr - (c-str (c-return " WHERE ") (compile-expression db expr)))) + ($str (return " WHERE ") (compile-expression db expr)) + (return nil))) (defmulti compile-sort-by (fn [db _] db)) (defmethod compile-sort-by :default [db fields] @@ -148,22 +158,29 @@ (->> (for [[[table field] dir] fields] (str (make-field-name db table field) \space (string/upper-case (name dir)))) (string/join ",") - (apply str " ORDER BY ") - c-return))) + (apply $str (return " ORDER BY ")) + return) + (return nil))) (defmulti compile-query (fn [db _] db)) (defmethod compile-query :default [db {:keys [table fields joins where sort-by]}] - (c-str (c-return "SELECT ") - (compile-fields db fields) - (if table - (c-return " FROM ")) - (compile-tables db table) - (compile-joins db joins) - (compile-where db where) - (compile-sort-by db sort-by))) + ($str (return "SELECT ") + (compile-fields db fields) + (if table + (return " FROM ")) + (compile-tables db table) + (compile-joins db joins) + (compile-where db where) + (compile-sort-by db sort-by))) + +(defn compile [db query] + (let [[sql vars] ((compile-query db query) [])] + (vec (cons sql vars)))) + + ;; ============================================================== ;; A few DB specific overrides ;; ============================================================== @@ -184,4 +201,25 @@ (str \` (name table) \`)) +;;(compile nil {:table {:u :u}, :fields {[:v :x] :w}}) + + + + +;; Utility functions + +(defn insert! [db query & records] + {:pre [(empty? (:joins query))]} + ;; some code here + ($str (return "Carlo")) + ) + +(defn update! [db query & partial-records] + {:pre [(empty? (:joins query))]} + ;; some code here + ) +(defn delete! [db query] + {:pre [(empty? (:joins query))]} + ;; some code here + ) |