summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--src/clojure_sql/compiler.clj75
-rw-r--r--src/clojure_sql/core.clj35
-rw-r--r--src/clojure_sql/dsl.clj36
-rw-r--r--src/clojure_sql/jdbc.clj12
5 files changed, 91 insertions, 75 deletions
diff --git a/README.md b/README.md
index 74c63bb..ad7ae3d 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ allow for data to be inserted, updated and deleted.
queries is performed by multimethods which dispatch on an arbitrary
(and otherwise unused) `db` parameter. This allows the compilation of
queries to be entirely special-cased per database. By default
-`clojure-sql` will produce standard ANSI SQL.
+`clojure-sql` will produce SQL for PostgreSQL.
## Usage
@@ -39,8 +39,10 @@ queries to be entirely special-cased per database. By default
'(= :uid :pid))
(s/project [:username]))
; => ["SELECT \"u\".\"username\" FROM \"users\" AS \"u\" JOIN \"people\" AS \"p\" ON (\"u\".\"id\" = \"p\".\"id\") WHERE (\"p\".\"fname\" = ?)" "Henry")]
-
- (s/use-jdbc! "postgres://user:pass@localhost:5432/db")
+
+ (require '[clojure-sql.jdbc :as jdbc])
+ ; => nil
+ (jdbc/use-jdbc! "postgres://user:pass@localhost:5432/db")
; => nil
diff --git a/src/clojure_sql/compiler.clj b/src/clojure_sql/compiler.clj
index 5bec649..0cbb808 100644
--- a/src/clojure_sql/compiler.clj
+++ b/src/clojure_sql/compiler.clj
@@ -21,16 +21,16 @@
;; DB specific escaping methods
;; ==============================================================
-(defmulti field-name (fn [db _] db))
-(defmethod field-name :default [_ field]
+(defmulti field-name (fn [db _] db) :default :postgres)
+(defmethod field-name :postgres [_ field]
(str \" (name field) \"))
-(defmulti table-name (fn [db _] db))
-(defmethod table-name :default [_ table]
+(defmulti table-name (fn [db _] db) :default :postgres)
+(defmethod table-name :postgres [_ table]
(str \" (name table) \"))
-(defmulti function-name (fn [db _] db))
-(defmethod function-name :default [_ function]
+(defmulti function-name (fn [db _] db) :default :postgres)
+(defmethod function-name :postgres [_ function]
(str \" (name function) \"))
@@ -70,15 +70,15 @@
(declare compile-query compile-expression)
-(defmulti compile-expression-list (fn [db _] db))
-(defmethod compile-expression-list :default [db ex]
+(defmulti compile-expression-list (fn [db _] db) :default :postgres)
+(defmethod compile-expression-list :postgres [db ex]
(->> (map (partial compile-expression db) ex)
(apply sequence)
((p-lift string/join ","))
$add-parentheses))
-(defmulti compile-expression-sequential (fn [db _] db))
-(defmethod compile-expression-sequential :default [db ex]
+(defmulti compile-expression-sequential (fn [db _] db) :default :postgres)
+(defmethod compile-expression-sequential :postgres [db ex]
(let [compile-exprs #(map (partial compile-expression db) %)
op-name (operator-name (first ex))
num-args (dec (count ex))]
@@ -113,8 +113,8 @@
(add-parentheses (string/join "," vals))))))
$add-parentheses)))
-(defmulti compile-expression (fn [db _] db))
-(defmethod compile-expression :default [db ex]
+(defmulti compile-expression (fn [db _] db) :default :postgres)
+(defmethod compile-expression :postgres [db ex]
(condp u/funcall ex
boolean? (return (string/upper-case (str ex)))
query? ($add-parentheses (compile-query db ex))
@@ -144,8 +144,8 @@
(return " AS ")
(return (field-name db alias)))))
-(defmulti compile-fields (fn [db _] db))
-(defmethod compile-fields :default [db fields-map]
+(defmulti compile-fields (fn [db _] db) :default :postgres)
+(defmethod compile-fields :postgres [db fields-map]
(if (seq fields-map)
(->> (for [[alias field] fields-map]
(make-field-name db field alias))
@@ -159,8 +159,8 @@
:full-outer "FULL OUTER"
:cross "CROSS"})
-(defmulti compile-tables (fn [db _ _] db))
-(defmethod compile-tables :default [db join tables-map]
+(defmulti compile-tables (fn [db _ _] db) :default :postgres)
+(defmethod compile-tables :postgres [db join tables-map]
(if (vector? join)
(->> (for [table-alias join]
(make-table-name db (get tables-map table-alias) table-alias))
@@ -183,14 +183,14 @@
(return "TRUE"))
(return ")"))))))
-(defmulti compile-where (fn [db _] db))
-(defmethod compile-where :default [db expr]
+(defmulti compile-where (fn [db _] db) :default :postgres)
+(defmethod compile-where :postgres [db expr]
(if expr
($str (return " WHERE ") (compile-expression db expr))
(return nil)))
-(defmulti compile-sort (fn [db _] db))
-(defmethod compile-sort :default [db fields]
+(defmulti compile-sort (fn [db _] db) :default :postgres)
+(defmethod compile-sort :postgres [db fields]
(if fields
(->> (for [[[table field] dir] fields]
($str (make-field-name db [table field])
@@ -200,8 +200,8 @@
($str (return " ORDER BY ")))
(return nil)))
-(defmulti compile-group (fn [db _] db))
-(defmethod compile-group :default [db fields]
+(defmulti compile-group (fn [db _] db) :default :postgres)
+(defmethod compile-group :postgres [db fields]
(if fields
(->> (for [[table field] fields]
(make-field-name db [table field]))
@@ -210,14 +210,21 @@
($str (return " GROUP BY ")))
(return nil)))
-(defmulti compile-having (fn [db _] db))
-(defmethod compile-having :default [db expr]
+(defmulti compile-having (fn [db _] db) :default :postgres)
+(defmethod compile-having :postgres [db expr]
(if expr
($str (return " HAVING ") (compile-expression db expr))
(return nil)))
-(defmulti compile-query (fn [db _] db))
-(defmethod compile-query :default [db {:keys [tables fields joins where sort group having union]}]
+(defmulti compile-limit (fn [db _ _] db) :default :postgres)
+(defmethod compile-limit :postgres [db take drop]
+ (return (str (if take
+ (str " LIMIT " take))
+ (if drop
+ (str " OFFSET " drop)))))
+
+(defmulti compile-query (fn [db _] db) :default :postgres)
+(defmethod compile-query :postgres [db {:keys [tables fields joins where sort group having union take drop]}]
(if union
(->> union
(map (partial compile-query db))
@@ -232,23 +239,19 @@
(compile-where db where)
(compile-group db group)
(compile-having db having)
- (compile-sort db sort))))
+ (compile-sort db sort)
+ (compile-limit db take drop))))
-(defn compile [db query]
+(defn compile-select [db query]
(let [[sql vars] ((compile-query db query) [])]
(vec (cons sql vars))))
-;; Utility functions
-
-(defn ^:private build-insertion [db fields record]
- )
-
-(defn insert! [db {:keys [fields tables joins]} & records]
+(defn compile-insert [db {:keys [fields tables joins]} & records]
(assert (= (count tables) 1) "Cannot insert into a multiple-table query")
(let [fields-order (map key fields)
wrap #(str "INSERT INTO "
@@ -275,7 +278,7 @@
;; {:uid 10, :name :carlo, :other :stuff}
;; {:uid 1, :name :carl, :other :stuf})
-(defn update! [db {:keys [tables fields where joins]} partial-record]
+(defn compile-update [db {:keys [tables fields where joins]} partial-record]
(assert (= (count tables) 1) "Cannot delete from a multiple-table query")
(assert (seq (set/intersection (set (keys partial-record))
(set (keys fields)))) "At least one field must be being updated")
@@ -307,7 +310,7 @@
;; {:username "carlozancnaro"
;; :blah "blah"})
-(defn delete! [db {:keys [tables where joins]}]
+(defn compile-delete [db {:keys [tables where joins]}]
(assert (= (count tables) 1) "Cannot delete from a multiple-table query")
(let [table (compile-tables db joins tables)
where-expression (if where
diff --git a/src/clojure_sql/core.clj b/src/clojure_sql/core.clj
index ebdc01f..03f992b 100644
--- a/src/clojure_sql/core.clj
+++ b/src/clojure_sql/core.clj
@@ -1,5 +1,5 @@
(ns clojure-sql.core
- (:refer-clojure :exclude [sort])
+ (:refer-clojure :exclude [sort take drop])
(:require [clojure.set :as set]
[clojure-sql.compiler :as c]
[clojure-sql.dsl :as d]
@@ -21,7 +21,7 @@
(defmethod print-method clojure_sql.query.Query [query writer]
(binding [*out* writer]
- (pr (c/compile *database-type* query))))
+ (pr (c/compile-select *database-type* query))))
(def table #'d/table)
@@ -32,46 +32,27 @@
(def group #'d/group)
(def having #'d/having)
(def sort #'d/sort)
-
-;(def take #'d/take)
-;(def drop #'d/drop)
+(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* :query (c/compile *database-type* query)))
+ (*query-executor* :select (c/compile-select *database-type* query)))
(defn insert! [query & records]
(assert *query-executor* "Cannot execute a query without a query executor")
- (let [compiled (apply c/insert! *database-type* query records)]
+ (let [compiled (apply c/compile-insert *database-type* query records)]
(*query-executor* :insert compiled)))
(defn update! [query partial-record]
(assert *query-executor* "Cannot execute a query without a query executor")
- (let [compiled (c/update! *database-type* query partial-record)]
+ (let [compiled (c/compile-update *database-type* query partial-record)]
(*query-executor* :update compiled)))
(defn delete! [query]
(assert *query-executor* "Cannot execute a query without a query executor")
- (let [compiled (c/delete! *database-type* query)]
+ (let [compiled (c/compile-delete *database-type* query)]
(*query-executor* :delete compiled)))
(q/set-query-deref-behaviour! run-query)
-
-
-
-
-
-
-
-;; FIXME: how to do this better? With some discovery, maybe?
-
-(require '[clojure.java.jdbc :as jdbc])
-(defn use-jdbc! [connection-string]
- (set-query-executor! (fn [type query]
- (jdbc/with-connection connection-string
- (case type
- :query (jdbc/with-query-results results query
- (vec results))
- :insert (jdbc/do-prepared-return-keys (first query) (next query))
- (jdbc/do-prepared (first query) (next query)))))))
diff --git a/src/clojure_sql/dsl.clj b/src/clojure_sql/dsl.clj
index c091d0e..10b1e8f 100644
--- a/src/clojure_sql/dsl.clj
+++ b/src/clojure_sql/dsl.clj
@@ -1,5 +1,5 @@
(ns clojure-sql.dsl
- (:refer-clojure :exclude [sort group])
+ (:refer-clojure :exclude [sort group take drop])
(:require [clojure.set :as set]
[clojure.walk :as walk]
[clojure-sql.query :as q]
@@ -159,7 +159,9 @@
(defn ^:private joinable? [query]
(and (nil? (:group query))
- (nil? (:having query))))
+ (nil? (:having query))
+ (nil? (:take query))
+ (nil? (:drop query))))
(defn ^:private convert-to-subquery [query]
(-> (table query)
@@ -168,6 +170,13 @@
(defn ^:private remove-sort [query]
(dissoc query :sort))
+(defn ^:private make-join-subquery [query]
+ (if (joinable? query)
+ query
+ (if (or (:take query) (:drop query))
+ (convert-to-subquery query)
+ (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]}]
(if (= type :right)
@@ -175,18 +184,14 @@
(let [type (if (= type :left)
:outer
type)
- left (if (joinable? left)
- left
- (convert-to-subquery (remove-sort left)))
- right (if (joinable? right)
- right
- (convert-to-subquery (remove-sort right)))
+ left (make-join-subquery left)
+ right (make-join-subquery right)
common-tables (set/intersection (set (keys (:tables left)))
(set (keys (:tables right))))
right (reduce (fn [query [alias table]]
(rename-table query alias (keyword (gensym (name table)))))
right (:tables right))
- ;_ (assert (empty? common-tables) "Cannot join two tables with the same name")
+ ;;_ (assert (empty? common-tables) "Cannot join two tables with the same name")
merged-tables (merge (:tables left) (:tables right))
common-fields (set/intersection (set (keys (:fields left)))
(set (keys (:fields right))))
@@ -263,3 +268,16 @@
resolved-expression (process-expression table-name (:fields query) expression)
new-having (combine-conjunctions old-having resolved-expression)]
(assoc query :having new-having)))
+
+(defn take [query n]
+ (if-let [old-take (:take query)]
+ (assoc query :take (min old-take n))
+ (assoc query :take n)))
+
+(defn drop [query n]
+ (let [query (if-let [old-take (:take query)]
+ (assoc query :take (- old-take n))
+ query)]
+ (if-let [old-drop (:drop query)]
+ (assoc query :drop (+ old-drop n))
+ (assoc query :drop n))))
diff --git a/src/clojure_sql/jdbc.clj b/src/clojure_sql/jdbc.clj
new file mode 100644
index 0000000..8611336
--- /dev/null
+++ b/src/clojure_sql/jdbc.clj
@@ -0,0 +1,12 @@
+(ns clojure-sql.jdbc
+ (:require [clojure.java.jdbc :as jdbc]
+ [clojure-sql.core :refer [set-query-executor!]]))
+
+(defn use-jdbc! [connection-string]
+ (set-query-executor! (fn [type query]
+ (jdbc/with-connection connection-string
+ (case type
+ :select (jdbc/with-query-results results query
+ (vec results))
+ :insert (jdbc/do-prepared-return-keys (first query) (next query))
+ (jdbc/do-prepared (first query) (next query)))))))