diff options
Diffstat (limited to 'src/clojure_sql')
-rw-r--r-- | src/clojure_sql/core.clj | 29 | ||||
-rw-r--r-- | src/clojure_sql/dsl.clj | 105 |
2 files changed, 110 insertions, 24 deletions
diff --git a/src/clojure_sql/core.clj b/src/clojure_sql/core.clj index 03f992b..81c9c13 100644 --- a/src/clojure_sql/core.clj +++ b/src/clojure_sql/core.clj @@ -7,6 +7,23 @@ [clojure-sql.util :as u] [clojure.walk])) + +(defmacro pull [ns & vlist] + `(do ~@(for [i vlist + :let [sym (symbol (name ns) (name i))]] + `(def ~(with-meta i + (u/map-vals (fn [x] `(quote ~x)) (meta (resolve sym)))) + ~sym)) + nil)) + +(pull clojure-sql.dsl + table join + project rename + select + group having + sort take drop) + + (def ^:private ^:dynamic *database-type* nil) (defn set-database-type! [new-type] (alter-var-root #'*database-type* (constantly new-type)) @@ -24,18 +41,6 @@ (pr (c/compile-select *database-type* query)))) -(def table #'d/table) -(def project #'d/project) -(def rename #'d/rename) -(def join #'d/join) -(def select #'d/select) -(def group #'d/group) -(def having #'d/having) -(def sort #'d/sort) -(def take #'d/take) -(def drop #'d/drop) - - (defn run-query [query] (assert *query-executor* "Cannot execute a query without a query executor") (*query-executor* :select (c/compile-select *database-type* query))) diff --git a/src/clojure_sql/dsl.clj b/src/clojure_sql/dsl.clj index 10b1e8f..73de216 100644 --- a/src/clojure_sql/dsl.clj +++ b/src/clojure_sql/dsl.clj @@ -74,7 +74,10 @@ (throw (ex-info "Cannot union queries with different fields" {:queries queries}))) -(defn table [arg] +(defn table + "Create a query on a database table. If `arg` is itself a query it + will be wrapped, otherwise `arg` will be used as the table name." + [arg] (q/map->Query (let [alias (keyword (gensym (if (u/named? arg) (name arg) "table")))] @@ -97,7 +100,15 @@ (keyword? expression) (resolve-field table aliases expression) :else expression)) -(defn project [query fields] +(defn project + "Limit a query's fields to a specified set of fields. `fields` can + be either a seq of allowed field names, or a map of field names to + aliases. + + If the query is currently on a single table then any unknown fields + will be resolved as columns in that table. If the query is on more + than one table then an exception will be thrown for unknown fields." + [query fields] (let [table (if (= (count (:tables query)) 1) (-> query :tables first key)) alias-lookup (or (:fields query) {}) @@ -128,18 +139,32 @@ (missing-rename-error field field-renames query))) (rename-with-fn query field-renames)) -(defn rename [query field-renames] +(defn rename + "Rename fields in a query. All fields must already exist prior to + calling rename (no automatic creation like `project`). + + `field-renames` can be either a function or a map. If a function is + provided then it will be applied to each field: the return value + will be used as the new alias, with a return of nil indicating no + change. If a map is provided then it will behave identically to a + function, but will throw errors if you are attempting to rename a + non-existent field." + [query field-renames] (if (map? field-renames) (rename-with-map query field-renames) (rename-with-fn query field-renames))) -(defn prefix-names-matching [pred prefix] +(defn prefix-names-matching + "Higher-order helper function to use with `rename`." + [pred prefix] (fn [alias] (if (pred alias) (keyword (str prefix (name alias))) alias))) -(defn prefix-names [prefix] +(defn prefix-names + "Higher-order helper function to use with `rename`." + [prefix] (prefix-names-matching (constantly true) prefix)) @@ -178,7 +203,20 @@ (convert-to-subquery (remove-sort query))))) (def ^:private valid-join-type? (comp boolean #{:cross :inner :outer :full-outer})) -(defn join [left right & {:keys [on type]}] +(defn join + "Join two queries into one query The fields of the resultant query + will be the union of the argument queries. + + If `type` is not provided then the join type will be automatically + set: if the arguments have any fields in common, or if an `on` is + provided, then an inner join will be performed (joining on common + attributes if no `on` is provided), otherwise a cross join will be + performed. + + Valid join types are :cross, :inner, :outer and :full-outer. An + outer join is considered a LEFT outer join. To achieve a right outer + join reverse the query arguments. " + [left right & {:keys [on type]}] (if (= type :right) (join right left :on on :type :left) (let [type (if (= type :left) @@ -230,7 +268,18 @@ :sort (seq (concat (:sort left) (:sort right)))))))) -(defn select [query expression] +(defn select + "Apply a filter to a query. The expression is an unevaluated expression + which is compiled by the clojure-sql compiler. + + Any keywords present in the query are interpreted as field names. A + quote can be used to suppress evaluation of terms in the + expression. + + Example: + (select query `(= :id 10)) - filter for an id of 10 + (select query `(in :id '(1 2 3)) - filter for an id of 1, 2 or 3" + [query expression] (let [table-name (if (= (count (:tables query)) 1) (-> query :tables first key)) old-where (:where query) @@ -238,7 +287,13 @@ new-where (combine-conjunctions old-where resolved-expression)] (assoc query :where new-where))) -(defn sort [query fields] +(defn sort + "Apply a sort to a query. + + `fields` is a sequential collection of fields to sort by. Each + element of fields can be either a field name, :field, or a vector of + field and direction, [:field :desc]." + [query fields] (let [table-name (if (= (count (:tables query)) 1) (-> query :tables first key)) fields-seq (if (sequential? fields) @@ -251,7 +306,11 @@ [(resolve-field table-name fields-lookup (first field)) (second field)] [(resolve-field table-name fields-lookup field) :asc]))))) -(defn group [query fields] +(defn group + "Apply a grouping to a query. + + `fields` is a sequential collection of fields to group by." + [query fields] (let [table-name (if (= (count (:tables query)) 1) (-> query :tables first key)) fields-seq (if (sequential? fields) @@ -261,7 +320,17 @@ (assoc query :group (map (partial resolve-field table-name fields-lookup) fields-seq)))) -(defn having [query expression] +(defn having + "Apply a filter to the groupings of a query. + + `expression` is the same as a `select`, but may only reference + fields by which the query is grouped (see `group`) or other fields + within aggregating functions (eg. count). + + Example: + (having query `(< (sum :age) 100)) - select groups with a combined + age under 100" + [query expression] (let [table-name (if (= (count (:tables query)) 1) (-> query :tables first key)) old-having (:having query) @@ -269,12 +338,24 @@ new-having (combine-conjunctions old-having resolved-expression)] (assoc query :having new-having))) -(defn take [query n] +(defn take + "Limit the number of results of a query. + + Note: take/drop will function as they do on clojure sequences. They + will not simply overwrite the previous take/drop value. Example: + (-> query (take 10) (drop 2)) = (-> query (drop 2) (take 8))" + [query n] (if-let [old-take (:take query)] (assoc query :take (min old-take n)) (assoc query :take n))) -(defn drop [query n] +(defn drop + "Exclude the first `n` results of a query. + + Note: take/drop will function as they do on clojure sequences. They + will not simply overwrite the previous take/drop value. Example: + (-> query (take 10) (drop 2)) = (-> query (drop 2) (take 8))" + [query n] (let [query (if-let [old-take (:take query)] (assoc query :take (- old-take n)) query)] |