diff options
-rw-r--r-- | README.md | 210 |
1 files changed, 144 insertions, 66 deletions
@@ -1,77 +1,168 @@ # clojure-sql -A DSL for [Clojure][1] to compose queries using the constructs of -[Relational Algebra][2]. The RA constructs are then compiled into an -SQL query to be run on a DBMS. `clojure-sql` doesn't connect to a -database itself, but can be configured to perform an action when a -query is to be executed. - -`clojure-sql` provides some utility functions beyond strict RA to -allow for data to be inserted, updated and deleted. - -`clojure-sql` provides no mechanism to create database schemas. +`clojure-sql` is a DSL for the creation of SQL queries in [Clojure][1] +code. It emphasises *composability* primarily. [1]: http://clojure.org/ -[2]: http://en.wikipedia.org/wiki/Relational_Algebra -## Leiningen information +## Release -Just add the following to your `project.clj` to get started with -`clojure-sql`. +Latest release: `0.1.0` + +[Leiningen][2] dependency information: [clojure-sql "0.1.0"] -## Database support +## Example Usage -`clojure-sql` aims to have an extensible compiler. Compilation of -queries is performed by multimethods which dispatch on an arbitrary -(and otherwise unused) `db` parameter. This allows the compilation of -queries to be special-cased per database. By default `clojure-sql` -will produce SQL for PostgreSQL (work on other databases may be -undertaken in future). + (require '[clojure-sql.core :as s]) + + (s/table :users) + ;; => ["SELECT * FROM \"users\" AS \"users1907\""] + + (-> (s/table :users) + (s/project [:id :username])) + ;; => ["SELECT \"users1910\".\"id\" AS \"id\", \"users1910\".\"username\" AS \"username\" FROM \"users\" AS \"users1910\""] -## Usage + (require '[clojure-sql.jdbc :as jdbc]) + (jdbc/use-jdbc! "postgres://user:pass@localhost:5432/db") + + (-> (s/table :users) + (s/project [:id :username]) + deref) + ;; => [{:id 5, :username "username"}] -In `clojure-sql` expressions it is generally assumed that: +## Relational Algebra -* Clojure keywords represent *column names* (exception: the `table` - function) -* Clojure symbols represent *SQL functions* and *SQL operators* -* Clojure strings represent *literal strings* -* Clojure numbers represent *literal numbers* +The constructs and operations available in `clojure-sql` are designed +around those of [Relational Algebra][3]. The majority of operations +take one (or more) queries as arguments and will return a new query as +a result. In this way any transformation can be applied to any query +in a manner which can be easily reasoned about. -Thus: `(and (= :name "Barry") (= (length :username) 10))` compiles -into an SQL expression equivalent to `("name" = 'Barry' AND -"length"("username") = 10)`. +A basic query on a table is constructed with the `table` function: +`(table :tablename)`. -A few more query examples: +[3]: http://en.wikipedia.org/wiki/Relational_Algebra - (require '[clojure-sql.core :as s]) +### Operations - ;; this is only shown as a demonstration - ;; a query like this is not advised for general use - (s/table :users) - ;; => ["SELECT * FROM \"users\" AS \"users1715\""] +The primary operations available in `clojure-sql` are the following: - ;; it is recommended that a query be projected first to provide - ;; clojure-sql with field information - (-> (s/table :users) ;; tables are automatically given internal aliases - (s/project [:id :username])) - ;; => ["SELECT \"users3205\".\"id\" AS \"id\", \"users3205\".\"username\" AS \"username\" FROM \"users\" AS \"users3205\""] +* `select`ion: selecting rows which match a condition - (-> (s/table :users) - (s/project [:id :username]) - (s/rename {:id :uid}) - (s/join (-> (s/table :people) - (s/project {:id :pid, :fname :first}) - (s/select '(= :first "Henry"))) - :on '(= :uid :pid)) - (s/project [:username])) - ;; => ["SELECT \"users3430\".\"username\" AS \"username\" FROM (\"users\" AS \"users3430\" INNER JOIN \"people\" AS \"people3432\" ON (\"users3430\".\"id\" = \"people3432\".\"id\")) WHERE (\"people3432\".\"fname\" = ?)" "Henry"] + (s/select (s/table :users) `(= :username "mange")) + + The second argument is a `clojure-sql` expression. This is expressed + as a quoted Clojure form where the following two assumptions are made: + + * Clojure keywords represent *column names* + * Clojure symbols represent *SQL functions* and *SQL operators* + + Some more examples of `clojure-sql` expressions and their + "equivalent" SQL: + + (let [user-id 5] `(= :user-id ~user-id)) + ;; => "(\"user-id\" = 5)" + `(and (= :user-id 5) (= :username "mange")) + ;; => "((\"user-id\" = 5) AND (\"username\" = 'mange'))" + `(or (= :user-id 10) (= :username "mange")) + ;; => "((\"user-id\" = 10) OR (\"username\" = 'mange'))" + `(and (= :name "Barry") (= (length :username) 10)) + ;; => "((\"name\" = 'Barry') AND (\"length\"(\"username\") = 10)) + +* `project`ion: setting the fields which are exposed by the query + + (s/project (s/table :users) [:id :username]) + (s/project (s/table :users) {:id :uid, :username :uname}) ;; project and rename in one operation + +* `rename`ing: giving an existing field (or fields) a new name + + (-> (s/table :users) + (s/project [:id :username]) + (s/rename {:id :uid})) + + Rename can also be given a function which will do the + transformation. This can be particularly handy when combined with + the helper functions `as-subobject` and `prefix-names`: + + (-> (s/table :users) + (s/project [:id :username]) + (s/rename (s/as-subobject :user))) + + In the final result map fields with a dot will be transformed into + nested maps. `as-subobject` will put all fields from a query into + a specified key in the result map. As an example, the above query + will result in a result map like: + + {:user {:id 5, :username "mange"}} + +* `join`ing: combine two queries into one query + + (s/join (-> (s/table :users) + (s/project [:id :username :person-id])) + (-> (s/table :people) + (s/project [:id :name]) + (s/rename (s/as-subobject :person))) + :on `(= :person.id :person-id)) + + If a join between two queries has common fields then it is assumed + to be a natural join (that is: an inner join on common fields). + + (s/join (-> (s/table :users) + (s/project [:id :username :person,id])) + (-> (s/table :people) + (s/project [:id :name]) + (s/rename (s/as-subobject :person)))) + + With joins the composability of `clojure-sql` becomes much more + useful: + + (def users (-> (s/table :users) + (s/project [:id :username :person]))) + + (def people (-> (s/table :people) + (s/project [:id :name]))) + + (s/join (s/rename users (s/as-subobject :user)) + (s/rename people (s/as-subobject :person)) + :on `(= :user.person :person.id)) + +#### Set operations + +At present `clojure-sql` supports two set operations. These operations +have a very strict requirement that all the sets being operated on +must expose *exactly the same* fields. + +* `union`ing +* `intersection` + +#### Extra-relational operations + +A number of extra-relational operations are also available. They are +equally composable as the above relational operators, but they may +introduce subqueries under some circumstances. (The docstrings should +explain those circumstances for each function.) + +These operations will not be explained in detail here, but are +hopefully self explanatory. + +* `group`ing/`having` +* `sort`ing +* `take`ing/`drop`ing + +## Database support + +`clojure-sql` aims to have an extensible compiler. Compilation of +queries is performed by multimethods which dispatch on an arbitrary +(and otherwise unused) `db` parameter. This allows the compilation of +queries to be special-cased per database. By default `clojure-sql` +will produce SQL for PostgreSQL (work on other databases may be +undertaken in future). ### clojure.java.jdbc -[`clojure.java.jdbc`][3] support is provided, but must be included +[`clojure.java.jdbc`][4] support is provided, but must be included explicitly. `clojure-sql` does not depend on `clojure.java.jdbc`. (require '[clojure-sql.jdbc :as jdbc]) @@ -85,7 +176,7 @@ explicitly. `clojure-sql` does not depend on `clojure.java.jdbc`. Results are returned from queries in an eager vector. -[3]: https://github.com/clojure/java.jdbc +[4]: https://github.com/clojure/java.jdbc ### Other query executors @@ -101,19 +192,6 @@ of query being executed, and the compiled query (as vector of be the return value of the associated query function call (`deref`, `insert!`, `update!` or `delete!`). -## Generated queries - -In general, `clojure-sql` will try to avoid creating subqueries. For -any of the RA primitives (`select`, `join`, `project`, `rename`) no -subqueries will be introduced. The use of non-RA operations (`group`, -`sort`, `take`/`drop`) may introduce a subquery. - -The particular operations which will create a subquery are: - -* `join`ing a query which has been `group`ed -* `sort`ing a query which has been `take`n or `drop`ped from -* `group`ing a query which has been previously `group`ed - ## License Copyright © 2013 Carlo Zancanaro |