Skip to content
Browse files

Added core and internal namespaces.

  • Loading branch information...
1 parent e3d0e3c commit 44171fc69feaef412069100c20f2a46100dd6639 @LauJensen committed Oct 31, 2010
Showing with 213 additions and 0 deletions.
  1. +107 −0 src/clojureql/core.clj
  2. +106 −0 src/clojureql/internal.clj
View
107 src/clojureql/core.clj
@@ -0,0 +1,107 @@
+(ns clojureql.core
+ (:use
+ clojureql.internal
+ [clojure.string :only [join] :rename {join join-str}]
+ [clojure.contrib sql])
+ (refer-clojure :exclude [take drop sort conj! disj!] :rename {take take-coll}))
+
+
+(def db
+ {:classname "com.mysql.jdbc.Driver"
+ :subprotocol "mysql"
+ :user "cql"
+ :password "cql"
+ :subname "//localhost:3306/cql"})
+
+
+(defprotocol Relation
+ (pick [_ predicate] "Queries the table using a predicate")
+ (drop [_ predicate] "Queries the table using an inverted predicate")
+ (conj! [this records] "Inserts record(s) into the table")
+ (disj! [this predicate] "Deletes record(s) from the table")
+ (take [_ n] "Queries the table with LIMIT n")
+ (sort [_ col type] "Sorts the query either :asc or :desc")
+ (join [_ table2 join_on] "Joins two table")
+ )
+
+(defrecord Table [cnx tname tcols]
+ clojure.lang.IDeref
+ (deref [_]
+ (with-connection cnx
+ (with-query-results rs
+ [(format "SELECT %s FROM %s" (colkeys->string tcols) (name tname))]
+ (doall rs))))
+ Relation
+ (pick [_ predicate]
+ (with-connection cnx
+ (with-query-results rs
+ [(format "SELECT %s FROM %s WHERE %s" (colkeys->string tcols) (name tname) predicate)]
+ (doall rs))))
+ (drop [_ predicate]
+ (with-connection cnx
+ (with-query-results rs
+ [(format "SELECT %s FROM %s WHERE not(%s)" (colkeys->string tcols) (name tname) predicate)]
+ (doall rs))))
+ (conj! [this records]
+ (with-connection cnx
+ (if (map? records)
+ (insert-records tname records)
+ (apply insert-records tname records)))
+ this)
+ (disj! [this predicate]
+ (with-connection cnx
+ (delete-rows tname [(map->predicate predicate)]))
+ this)
+ (take [_ n]
+ (with-connection cnx
+ (with-query-results rs
+ [(format "SELECT %s FROM %s LIMIT %d" (colkeys->string tcols) (name tname) n)]
+ (doall rs))))
+ (sort [_ col type]
+ (with-connection cnx
+ (with-query-results rs
+ [(format "SELECT %s FROM %s ORDER BY %s %s"
+ (colkeys->string tcols)
+ (name tname)
+ (name col)
+ (if (= :asc type) "ASC" "DESC"))]
+ (doall rs))))
+ (join [_ table2 join_on]
+ (with-cnx cnx
+ (with-results rs
+ [(format "SELECT %s FROM %s JOIN %s ON %s"
+ (colkeys->string tcols)
+ (name tname)
+ (-> table2 :tname name)
+ (->> join_on (map name) (join-str \=)))]
+ (doall rs))))
+ )
+
+
+
+
+
+
+#_(do
+
+ (def users (table users [:name :title]))
+ (def salary (table salary ["*"]))
+
+ @users ; select <constructor supplied columns> from users
+ ({:name "Lau" :title "Developer"} {:name "cgrand" :title "Design Guru"})
+
+ (conj! users {:name "sthuebner" :title "Mr. Macros"}) ; insert into
+
+ (disj! users {:name "Lau" 'title "Dev"}) ; remove entry with name=Lau OR title=Dev
+
+ (sort users :col :asc) ; select <cols> from users order by 'col' ASC
+
+ (take users 5) ; select <cols> from users LIMIT 5
+
+ (join users salary #{:users.id :salary.id})
+
+ (let [where-pred (sql-clause "%1 = %2" "id" 2)]
+ (pick users where-pred) ; select <cols> where id = 2
+ (drop users where-pred)) ; select <cols> where id != 2
+
+ )
View
106 src/clojureql/internal.clj
@@ -0,0 +1,106 @@
+(ns clojureql.internal
+ (:use [clojure.string :only [join] :rename {join join-str}]))
+
+(def *db* {:connection nil :level 0})
+
+(defn with-cnx*
+ "Evaluates func in the context of a new connection to a database then
+ closes the connection."
+ [db-spec func]
+ (with-open [con (clojure.contrib.sql.internal/get-connection db-spec)]
+ (binding [*db* (assoc *db*
+ :connection con :level 0 :rollback (atom false))]
+ (func))))
+
+(defmacro with-cnx
+ [db-spec & body]
+ `(with-cnx* ~db-spec (fn [] ~@body)))
+
+(defn colkeys->string
+ " [:k1 :k2] becomes \"k1,k2\" "
+ [tcols]
+ (->> tcols (map name) (join-str \,)))
+
+(defn map->predicate
+ " {:x 5 :y 10} becomes \"x=5 AND y=10\"
+ {'x 5 'y 10} becomes \"x=5 OR y=10\"
+ Strings are automatically quoted. "
+ [pred]
+ (reduce (fn [acc [k v]]
+ (str acc (if-not (empty? acc)
+ (if (symbol? k) " OR " " AND "))
+ (name k)
+ \=
+ (if (string? v)
+ (format "'%s'" v)
+ v)
+ )) "" pred))
+
+(defn non-unique-map
+ " Reduces a collection of key/val pairs to a single hashmap.
+
+ [[:a 5] [:b 10]] => {:a 5 :b 10}
+
+ [[:a 5] [:b 10] [:a 15]] => {:a [5 15] :b 10} "
+ [ks]
+ (reduce (fn [acc [k v]]
+ (assoc acc k (if-let [vl (acc k)]
+ (if (not= vl (acc k))
+ (conj [vl] v)
+ vl)
+ v))) {} ks))
+
+(defn sql-clause [pred & args]
+ " Allows you to generate an sql clause by identifying params as %1, %2, %n.
+
+ (sql-clause '%1 > %2 < %1' 'one' 'two') => 'one > two < one' "
+ (letfn [(rep [s i] (.replaceAll s (str "%" (inc i))
+ (let [item (nth args i)]
+ (if (keyword? item)
+ (name item)
+ (str item)))))]
+ (loop [i 0 retr pred]
+ (if (= i (-> args count dec))
+ (rep retr i)
+ (recur (inc i) (rep retr i))))))
+
+ ; SQL Specifics
+
+(defn result-seq
+ "Creates and returns a lazy sequence of structmaps corresponding to
+ the rows in the java.sql.ResultSet rs"
+ [^java.sql.ResultSet rs]
+ (let [rsmeta (. rs (getMetaData))
+ idxs (range 1 (inc (. rsmeta (getColumnCount))))
+ keys (map (comp keyword #(.toLowerCase ^String %))
+ (map (fn [i] (. rsmeta (getColumnLabel i))) idxs))
+ row-values (fn [] (map (fn [^Integer i] (. rs (getObject i))) idxs))
+ rows (fn thisfn []
+ (when (. rs (next))
+ (cons
+ (->> (for [i idxs :let [vals (row-values)]]
+ [(nth keys (dec i)) (nth vals (dec i))])
+ non-unique-map)
+ (lazy-seq (thisfn)))))]
+ (rows)))
+
+(defn with-results*
+ "Executes a query, then evaluates func passing in a seq of the results as
+ an argument. The first argument is a vector containing the (optionally
+ parameterized) sql query string followed by values for any parameters."
+ [[sql & params :as sql-params] func]
+ (when-not (vector? sql-params)
+ (throw "sql-params must be a vector"))
+ (with-open [stmt (.prepareStatement (:connection *db*) sql)]
+ (doseq [[index value] (map vector (iterate inc 1) params)]
+ (.setObject stmt index value))
+ (with-open [rset (.executeQuery stmt)]
+ (func (result-seq rset)))))
+
+(defmacro with-results
+ "Executes a query, then evaluates body with results bound to a seq of the
+ results. sql-params is a vector containing a string providing
+ the (optionally parameterized) SQL query followed by values for any
+ parameters."
+ [results sql-params & body]
+ `(with-results* ~sql-params (fn [~results] ~@body)))

0 comments on commit 44171fc

Please sign in to comment.
Something went wrong with that request. Please try again.