diff --git a/README.md b/README.md index 313fec3..7a6b347 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ SQL as Clojure data structures. ## Usage ```clj -(require '[honeysql.core :refer [format-sql sql merge-sql sql-fn sql-raw]]) +(require '[honeysql.core :as sql :refer [select from where limit merge-select + merge-where]]) ;; Everything is centered around maps representing SQL queries (def sqlmap {:select [:a :b :c] @@ -15,43 +16,55 @@ SQL as Clojure data structures. :where [:= :f.a "baz"]}) ;; format-sql turns maps into clojure.java.jdbc-compatible, parameterized SQL -(format-sql sqlmap) +(sql/format sqlmap) => ["SELECT a, b, c FROM foo WHERE (f.a = ?)" ["baz"]] ;; The sql function is a helper for building query maps (= sqlmap - (sql :select [:a :b :c] - :from :foo - :where [:= :f.a "baz"])) + (sql/sql :select [:a :b :c] + :from :foo + :where [:= :f.a "baz"])) => true -;; Providing a map as the first argument to sql will use that map as a base, -;; with the new clauses replacing old ones -(format-sql (sql sqlmap :select :* :limit 10)) +;; You can also use clause-specific helper functions, if you prefer. They +;; compose together nicely. +(= sqlmap + (-> (select :a :b :c) + (from :foo) + (where [:= :f.a "baz"]))) +=> true + +;; Providing a map as the first argument to sql or clause helper functions will +;; use that map as a base, with the new clauses replacing old ones +(sql/format (sql/sql sqlmap :select :* :limit 10)) +=> ["SELECT * FROM foo WHERE (f.a = ?) LIMIT 10" ["baz"]] +(sql/format (-> sqlmap (select :*) (limit 10))) => ["SELECT * FROM foo WHERE (f.a = ?) LIMIT 10" ["baz"]] -;; To add to clauses instead of replacing them, use merge-sql -(format-sql - (merge-sql sqlmap :select [:d :e] :where [:> :b 10])) +;; To add to clauses instead of replacing them, use merge-sql, or merge-select, +;; merge-from, etc. +(sql/format + (sql/merge-sql sqlmap :select [:d :e] :where [:> :b 10])) +=> ["SELECT a, b, c, d, e FROM foo WHERE ((f.a = ?) AND (b > 10))" ["baz"]] +(sql/format + (-> sqlmap (merge-select :d :e) (merge-where [:> :b 10]))) => ["SELECT a, b, c, d, e FROM foo WHERE ((f.a = ?) AND (b > 10))" ["baz"]] ;; Queries can be nested -(format-sql - (sql :select :* - :from :foo - :where [:in :foo.a (sql :select :a - :from :bar)])) +(sql/format + (sql/sql :select :* + :from :foo + :where [:in :foo.a (sql/sql :select :a :from :bar)])) => ["SELECT * FROM foo WHERE (foo.a IN (SELECT a FROM bar))"] ;; There are helper functions and data literals for handling SQL function ;; calls and raw SQL fragments -(sql :select [(sql-fn :count :*) (sql-raw "@var := foo.bar")] - :from :foo) -=> {:from (:foo), :select (#sql/fn [:count :*] #sql/raw "@var := foo.bar")} +(sql/sql :select [(sql/call :count :*) (sql/raw "@var := foo.bar")] + :from :foo) +=> {:from (:foo), :select (#sql/call [:count :*] #sql/raw "@var := foo.bar")} -(format-sql *1) +(sql/format *1) => ["SELECT COUNT(*), @var := foo.bar FROM foo"] - ``` ## License diff --git a/resources/data_readers.clj b/resources/data_readers.clj index 3a76467..cd55371 100644 --- a/resources/data_readers.clj +++ b/resources/data_readers.clj @@ -1,2 +1,2 @@ -{sql/fn honeysql.format/read-sql-fn +{sql/call honeysql.format/read-sql-call sql/raw honeysql.format/read-sql-raw} \ No newline at end of file diff --git a/src/honeysql/core.clj b/src/honeysql/core.clj index fba08c2..e718558 100644 --- a/src/honeysql/core.clj +++ b/src/honeysql/core.clj @@ -1,10 +1,11 @@ (ns honeysql.core - (:refer-clojure :exclude [group-by]) - (:require [honeysql.format :as format])) + (:refer-clojure :exclude [group-by format]) + (:require [honeysql.format :as format] + [honeysql.util :refer [defalias]])) -(def sql-fn format/sql-fn) -(def sql-raw format/sql-raw) -(def format-sql format/format-sql) +(defalias call format/call) +(defalias raw format/raw) +(defalias format format/format) (defn select [& fields] (let [[m fields] (if (map? (first fields)) diff --git a/src/honeysql/format.clj b/src/honeysql/format.clj index 2b32588..fd4c8ca 100644 --- a/src/honeysql/format.clj +++ b/src/honeysql/format.clj @@ -1,33 +1,34 @@ (ns honeysql.format + (:refer-clojure :exclude [format]) (:require [clojure.string :as string])) ;;(set! *warn-on-reflection* true) ;;;; -(deftype SqlFn [name args]) +(deftype SqlCall [name args]) -(defn sql-fn [name & args] - (SqlFn. name args)) +(defn call [name & args] + (SqlCall. name args)) -(defn read-sql-fn [form] - (apply sql-fn form)) +(defn read-sql-call [form] + (apply call form)) -(defmethod print-method SqlFn [^SqlFn o ^java.io.Writer w] - (.write w (str "#sql/fn " (pr-str (into [(.name o)] (.args o)))))) +(defmethod print-method SqlCall [^SqlCall o ^java.io.Writer w] + (.write w (str "#sql/call " (pr-str (into [(.name o)] (.args o)))))) -(defmethod print-dup SqlFn [o w] +(defmethod print-dup SqlCall [o w] (print-method o w)) ;;;; (deftype SqlRaw [s]) -(defn sql-raw [s] +(defn raw [s] (SqlRaw. (str s))) (defn read-sql-raw [form] - (sql-raw form)) + (raw form)) (defmethod print-method SqlRaw [^SqlRaw o ^java.io.Writer w] (.write w (str "#sql/raw " (pr-str (.s o))))) @@ -75,7 +76,7 @@ (declare to-sql) -(defn format-sql [sql-map] +(defn format [sql-map] (binding [*params* (atom [])] (let [sql-str (to-sql sql-map)] (if (seq @*params*) @@ -104,7 +105,7 @@ (str (to-sql (first x)) " AS " (to-sql (second x))))) - SqlFn + SqlCall (-to-sql [x] (binding [*fn-context?* true] (let [fn-name (name (.name x)) fn-name (fn-aliases fn-name fn-name) @@ -152,7 +153,7 @@ (paren-wrap (string/join (str " " (string/upper-case op-name) " ") (map format-predicate args))) - (to-sql (apply sql-fn pred))))))) + (to-sql (apply call pred))))))) (defmulti format-clause "Takes a map entry representing a clause and returns an SQL string" diff --git a/src/honeysql/util.clj b/src/honeysql/util.clj new file mode 100644 index 0000000..d2393aa --- /dev/null +++ b/src/honeysql/util.clj @@ -0,0 +1,5 @@ +(ns honeysql.util) + +(defmacro defalias [sym var-sym] + `(let [v# (var ~var-sym)] + (intern *ns* (with-meta (quote ~sym) (meta v#)) @v#))) \ No newline at end of file