├── .gitignore ├── README.md ├── project.clj ├── src └── java_jdbc │ ├── ddl.clj │ └── sql.clj └── test └── java_jdbc ├── ddl_test.clj └── sql_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /target 3 | /lib 4 | /classes 5 | /checkouts 6 | pom.xml 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | /.nrepl-port 13 | /pom.xml.asc 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-jdbc/dsl 2 | 3 | Basic DSL for SQL/DDL generation formerly part of [org.clojure/java.jdbc](https://github.com/clojure/java.jdbc) (0.3.0). 4 | 5 | This DSL was grown here in this repository as an experiment for inclusion in the java.jdbc contrib library but community feedback indicated that adding a DSL to java.jdbc caused confusion and was unnecessary. In particular, this DSL wasn't very sophisticated and I had no plans to make it sophisticated. Projects like [HoneySQL](https://github.com/jkk/honeysql) and [SQLingvo](https://github.com/r0man/sqlingvo) are always going to be better DSLs. 6 | 7 | Because this DSL is being removed from org.clojure/java.jdbc, the java.jdbc.sql and java.jdbc.ddl namespaces have been moved to this external project so that anyone using the DSL can continue doing so by simply switching to the java-jdbc.sql and java-jdbc.ddl namespaces from this project instead. 8 | 9 | ## Releases and Dependency Information 10 | 11 | Latest stable release: 0.1.3: 12 | 13 | * [All Released Versions](https://clojars.org/java-jdbc/dsl) 14 | 15 | [Leiningen](https://github.com/technomancy/leiningen) dependency information: 16 | ```clojure 17 | [java-jdbc/dsl "0.1.3"] 18 | ``` 19 | [Maven](http://maven.org/) dependency information: 20 | ```xml 21 | 22 | java-jdbc 23 | dsl 24 | 0.1.3 25 | 26 | ``` 27 | 28 | ## Usage 29 | 30 | Generate SQL and parameters that can be used in **query** and **execute** expressions: 31 | ```clojure 32 | (select * :person) 33 | ;;=> ("SELECT * FROM person") 34 | (select [:id :name] :person 35 | (where {:email "user@domain.com" :sex ["M", "F"]})) 36 | ;;=> ("SELECT id,name FROM person WHERE email = ? AND sex IN (?, ?)" "user@domain.com" "M", "F") 37 | (select [{:p.id :userid} :p.name :a.city] {:person :p} 38 | (join {:address :a} {:p.id :a.personid}) 39 | (where {:p.email "user@domain.com"})) 40 | ;;=> ("SELECT p.id AS userid,p.name,a.city FROM person p JOIN address a ON p.id = a.personid 41 | ;; WHERE p.email = ?" "user@domain.com") 42 | (select [:id :name] :person 43 | (order-by :name)) 44 | ;;=> ("SELECT id,name FROM person ORDER BY name ASC") 45 | (update :person {:status "active"}) 46 | ;;=> ("UPDATE person SET status = ?" "active") 47 | (update :person {:status "suspended"} 48 | (where {:country "NG"})) 49 | ;;=> ("UPDATE person SET status = ? WHERE country = ?" "suspended" "NG") 50 | (delete :person (where {:id 42})) 51 | ;;=> ("DELETE FROM person WHERE id = ?" 42) 52 | ``` 53 | **select** expects a *column-spec*, a *table-spec*, optional *join-clauses* (as strings), an optional *where-clause* (sequence of SQL conditions as a string followed by parameters). It returns a sequence whose first element is a string containing a SQL SELECT statement and whose remaining elements are the values to be substituted for parameters (**?**) in that string. 54 | 55 | A *column-spec* may be \*, a single *column* or a sequence of *columns*. A *column* may be a string or keyword, or a map (of a single key to a single value, that specifies a column alias). 56 | 57 | A *table-spec* may be a string or a keyword, or a map (of a single key to a single value, that specifies a table alias). 58 | 59 | A *join-clause* is just a string containing a SQL JOIN clause. It can be generated by the **join** function. 60 | 61 | A *where-clause* is a sequence whose first element is a string containing SQL conditions and whose remaining elements are the values to be substituted for parameters (**?**) in that string. It can be generated by the **where** function. 62 | 63 | **delete** expects a *table-spec* and a *where-clause* (which is not optional). It returns a sequence whose first element is a string containing a SQL DELETE statement and whose remaining elements are the values to be substituted for parameters (**?**) in that string. 64 | 65 | **insert** expects a *table-spec* and either *column-names* followed by *values* or *value-maps* representing rows. 66 | 67 | **join** expects a *table-spec* and a *join-map* which is used to generate the ON clause. It returns a string containing a SQL JOIN/ON clause. 68 | 69 | A *join-map* is a map whose keys and values represent columns in the two tables being joined. 70 | 71 | **where** expects a *value-map* which is used to generate a string that contains the conditional part of a WHERE clause and the values to be substituted for parameters (**?**) within that string. It returns a sequence whose first element is the string and whose remaining elements are the parameter values. 72 | 73 | **order-by** expects a sequence of *col-sorts*. A *col-sort* is either string or keyword representing a column to sort on (ascending), or a map with one key / value pair that specifies a column name and a sort direction (**:asc**, **:desc**). 74 | 75 | **update** expects a *table-spec*, an *update-map* and an optional *where-clause*. It returns a sequence whose first element is a string containing a SQL UPDATE statement and whose remaining elements are the values to be substituted for parameters (**?**) in that string. 76 | 77 | An *update-map* is a map whose keys represent columns to be set to the corresponding values. 78 | 79 | All functions that generate SQL may have an optional **:entities** keyword argument (after the specified arguments) which specifies an identifier naming convention, e.g., **(quoted \\`)** which would cause SQL entities to be quoted with **\`** (**:id** would become **\`id\`**). Other built-in naming conventions as **as-is** and **lower-case**. 80 | 81 | The **entities** macro can be used to apply an identifier naming convention to a DSL expression. It expects a function representing the naming convention and a DSL expression. It post-walks the DSL expression and inserts the **:entities** keyword argument and naming convention at the end of each expression. 82 | 83 | ## Changees 84 | 85 | * 0.1.3 2015-06-10 Update to support Clojure 1.7.0-RC1 (conflict with `update`). 86 | * 0.1.2 2015-04-13 Add support for more WHERE clauses ([@kofrasa](https://github.com/seancorfield/jsql/pull/3)). 87 | * 0.1.1 2014-11-27 Add support for IN ([@dryewo](https://github.com/seancorfield/jsql/pull/1)); bump base Clojure version to 1.6.0 88 | * 0.1.0 2013-11-24 Initial version extracted from java.jdbc 89 | 90 | ## License 91 | 92 | Copyright (c) 2012-2015 Sean Corfield 93 | 94 | Distributed under the Eclipse Public License, the same as Clojure. 95 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject java-jdbc/dsl "0.1.3" 2 | :description "Basic DSL for generating SQL/DDL formerly part of org.clojure/java.jdbc" 3 | :url "https://github.com/seancorfield/jsql" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.6.0"]]) 7 | -------------------------------------------------------------------------------- /src/java_jdbc/ddl.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Sean Corfield. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse Public 3 | ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can 4 | ;; be found in the file epl-v10.html at the root of this distribution. By 5 | ;; using this software in any fashion, you are agreeing to be bound by the 6 | ;; terms of this license. You must not remove this notice, or any other, 7 | ;; from this software. 8 | ;; 9 | ;; ddl.clj 10 | ;; 11 | ;; A basic DDL DSL for use with clojure.java.jdbc (or you can use any 12 | ;; other DDL DSL you want to...) 13 | ;; 14 | ;; seancorfield (gmail) 15 | ;; December 2013 16 | 17 | (ns 18 | ^{:author "Sean Corfield", 19 | :doc "An optional DSL for generating DDL. 20 | 21 | Intended to be used with clojure.java.jdbc, this provides a simple DSL - 22 | Domain Specific Language - that generates raw DDL strings. Any other DSL 23 | can be used instead. This DSL is entirely optional and is deliberately 24 | not very sophisticated." } 25 | java-jdbc.ddl 26 | (:require [java-jdbc.sql :as sql])) 27 | 28 | (defn create-table 29 | "Given a table name and column specs with an optional table-spec 30 | return the DDL string for creating that table." 31 | [name & specs] 32 | (let [col-specs (take-while (fn [s] 33 | (not (or (= :table-spec s) 34 | (= :entities s)))) specs) 35 | other-specs (drop (count col-specs) specs) 36 | {:keys [table-spec entities] :or {entities sql/as-is}} other-specs 37 | table-spec-str (or (and table-spec (str " " table-spec)) "") 38 | specs-to-string (fn [specs] 39 | (apply str 40 | (map (sql/as-str entities) 41 | (apply concat 42 | (interpose [", "] 43 | (map (partial interpose " ") specs))))))] 44 | (format "CREATE TABLE %s (%s)%s" 45 | (sql/as-str entities name) 46 | (specs-to-string col-specs) 47 | table-spec-str))) 48 | 49 | (defn drop-table 50 | "Given a table name, return the DDL string for dropping that table." 51 | [name & {:keys [entities] :or {entities sql/as-is}}] 52 | (format "DROP TABLE %s" (sql/as-str entities name))) 53 | 54 | (defn create-index 55 | "Given an index name, table name, vector of column names, and 56 | (optional) is-unique, return the DDL string for creating an index. 57 | 58 | Examples: 59 | (create-index :indexname :tablename [:field1 :field2] :unique) 60 | \"CREATE UNIQUE INDEX indexname ON tablename (field1, field2)\" 61 | 62 | (create-index :indexname :tablename [:field1 :field2]) 63 | \"CREATE INDEX indexname ON tablename (field1, field2)\"" 64 | [index-name table-name cols & specs] 65 | (let [is-unique (seq (filter #(= :unique %) specs)) 66 | entities-spec (drop-while #(not= :entities %) specs) 67 | {:keys [entities] :or {entities sql/as-is}} (take 2 entities-spec) 68 | cols-string (apply str 69 | (interpose ", " 70 | (map (sql/as-str entities) 71 | cols))) 72 | is-unique (if is-unique "UNIQUE " "")] 73 | (format "CREATE %sINDEX %s ON %s (%s)" 74 | is-unique 75 | (sql/as-str entities index-name) 76 | (sql/as-str entities table-name) 77 | cols-string))) 78 | 79 | (defn drop-index 80 | "Given an index name, return the DDL string for dropping that index." 81 | [name & {:keys [entities] :or {entities sql/as-is}}] 82 | (format "DROP INDEX %s" (sql/as-str entities name))) 83 | 84 | -------------------------------------------------------------------------------- /src/java_jdbc/sql.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Sean Corfield. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse Public 3 | ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can 4 | ;; be found in the file epl-v10.html at the root of this distribution. By 5 | ;; using this software in any fashion, you are agreeing to be bound by the 6 | ;; terms of this license. You must not remove this notice, or any other, 7 | ;; from this software. 8 | ;; 9 | ;; sql.clj 10 | ;; 11 | ;; A basic SQL DSL for use with clojure.java.jdbc (or you can use any 12 | ;; other SQL DSL you want to...) 13 | ;; 14 | ;; seancorfield (gmail) 15 | ;; December 2012 16 | 17 | (ns 18 | ^{:author "Sean Corfield", 19 | :doc "An optional DSL for generating SQL. 20 | 21 | Intended to be used with clojure.java.jdbc, this provides a simple DSL - 22 | Domain Specific Language - that generates raw SQL strings. Any other DSL 23 | can be used instead. This DSL is entirely optional and is deliberately 24 | not very sophisticated. It is sufficient to support the delete!, insert! 25 | and update! high-level operations within clojure.java.jdbc directly." } 26 | java-jdbc.sql 27 | (:refer-clojure :exclude [update]) 28 | (:require [clojure.string :as str] 29 | [clojure.walk :as walk])) 30 | 31 | ;; implementation utilities 32 | 33 | (defn as-str 34 | "Given a naming strategy and a keyword, return the keyword as a 35 | string per that naming strategy. Given (a naming strategy and) 36 | a string, return it as-is. 37 | A keyword of the form :x.y is treated as keywords :x and :y, 38 | both are turned into strings via the naming strategy and then 39 | joined back together so :x.y might become `x`.`y` if the naming 40 | strategy quotes identifiers with `." 41 | ([f] 42 | (fn [x] 43 | (as-str f x))) 44 | ([f x] 45 | (if (instance? clojure.lang.Named x) 46 | (let [n (name x) 47 | i (.indexOf n (int \.))] 48 | (if (= -1 i) 49 | (f n) 50 | (str/join "." (map f (.split n "\\."))))) 51 | (str x)))) 52 | 53 | (defn as-quoted-str 54 | "Given a quoting pattern - either a single character or a vector pair of 55 | characters - and a string, return the quoted string: 56 | (as-quoted-str X foo) will return XfooX 57 | (as-quoted-str [A B] foo) will return AfooB" 58 | ([q] 59 | (fn [x] 60 | (as-quoted-str q x))) 61 | ([q x] 62 | (if (vector? q) 63 | (str (first q) x (last q)) 64 | (str q x q)))) 65 | 66 | (defn- col-str 67 | "Transform a column spec to an entity name for SQL. The column spec may be a 68 | string, a keyword or a map with a single pair - column name and alias." 69 | [col entities] 70 | (if (map? col) 71 | (let [[k v] (first col)] 72 | (str (as-str entities k) " AS " (as-str entities v))) 73 | (as-str entities col))) 74 | 75 | (defn- table-str 76 | "Transform a table spec to an entity name for SQL. The table spec may be a 77 | string, a keyword or a map with a single pair - table name and alias." 78 | [table entities] 79 | (if (map? table) 80 | (let [[k v] (first table)] 81 | (str (as-str entities k) " " (as-str entities v))) 82 | (as-str entities table))) 83 | 84 | (def ^{:private true 85 | :doc "Symbols that need to be processed for entities within their forms."} 86 | entity-symbols 87 | #{"delete" "delete!" 88 | "insert" "insert!" 89 | "select" "join" "where" "order-by" 90 | "update" "update!" 91 | "create-table" "drop-table" "create-index" "drop-index"}) 92 | 93 | (def ^{:private true 94 | :doc "Symbols that need to be processed for identifiers within their forms."} 95 | identifier-symbols 96 | #{"query"}) 97 | 98 | (defn- order-direction 99 | "Transform a column order spec to an order by entity for SQL. The order spec may be a 100 | string, a keyword or a map with a single pair - column name and direction. If the order 101 | spec is not a map, the default direction is ascending." 102 | [col entities] 103 | (if (map? col) 104 | (str (as-str entities (first (keys col))) 105 | " " 106 | (let [dir (first (vals col))] 107 | (get {:asc "ASC" :desc "DESC"} dir dir))) 108 | (str (as-str entities col) " ASC"))) 109 | 110 | (defn- insert-multi-row 111 | "Given a table and a list of columns, followed by a list of column value sequences, 112 | return a vector of the SQL needed for the insert followed by the list of column 113 | value sequences. The entities function specifies how column names are transformed." 114 | [table columns values entities] 115 | (let [nc (count columns) 116 | vcs (map count values)] 117 | (if (not (and (or (zero? nc) (= nc (first vcs))) (apply = vcs))) 118 | (throw (IllegalArgumentException. "insert called with inconsistent number of columns / values")) 119 | (into [(str "INSERT INTO " (table-str table entities) 120 | (when (seq columns) 121 | (str " ( " 122 | (str/join ", " (map (fn [col] (col-str col entities)) columns)) 123 | " )")) 124 | " VALUES ( " 125 | (str/join ", " (repeat (first vcs) "?")) 126 | " )")] 127 | values)))) 128 | 129 | (defn- insert-single-row 130 | "Given a table and a map representing a row, return a vector of the SQL needed for 131 | the insert followed by the list of column values. The entities function specifies 132 | how column names are transformed." 133 | [table row entities] 134 | (let [ks (keys row)] 135 | (into [(str "INSERT INTO " (table-str table entities) " ( " 136 | (str/join ", " (map (fn [col] (col-str col entities)) ks)) 137 | " ) VALUES ( " 138 | (str/join ", " (repeat (count ks) "?")) 139 | " )")] 140 | (vals row)))) 141 | 142 | ;; quoting strategy helpers 143 | 144 | (defmacro entities 145 | "Given an entities function and a SQL-generating DSL form, transform the DSL form 146 | to inject an :entities keyword argument with the function at the end of each appropriate 147 | form." 148 | [entities sql] 149 | (walk/postwalk (fn [form] 150 | (if (and (seq? form) 151 | (symbol? (first form)) 152 | (entity-symbols (name (first form)))) 153 | (concat form [:entities entities]) 154 | form)) sql)) 155 | 156 | (defmacro identifiers 157 | "Given an identifiers function and a SQL-generating DSL form, transform the DSL form 158 | to inject an :identifiers keyword argument with the function at the end of each 159 | appropriate form." 160 | [identifiers sql] 161 | (walk/postwalk (fn [form] 162 | (if (and (seq? form) 163 | (symbol? (first form)) 164 | (identifier-symbols (name (first form)))) 165 | (concat form [:identifiers identifiers]) 166 | form)) sql)) 167 | 168 | ;; some common entity/identifier strategies 169 | 170 | (def as-is identity) 171 | (def lower-case str/lower-case) 172 | (defn quoted [q] (as-quoted-str q)) 173 | 174 | ;; SQL generation functions 175 | 176 | (defn delete 177 | "Given a table name, a where class and its parameters and an optional entities spec, 178 | return a vector of the SQL for that delete operation followed by its parameters. The 179 | entities spec (default 'as-is') specifies how to transform column names." 180 | [table [where & params] & {:keys [entities] :or {entities as-is}}] 181 | (into [(str "DELETE FROM " (table-str table entities) 182 | (when where " WHERE ") where)] 183 | params)) 184 | 185 | (defn insert 186 | "Given a table name and either column names and values or maps representing rows, retun 187 | return a vector of the SQL for that insert operation followed by its parameters. An 188 | optional entities spec (default 'as-is') specifies how to transform column names." 189 | [table & clauses] 190 | (let [rows (take-while map? clauses) 191 | n-rows (count rows) 192 | cols-and-vals-etc (drop n-rows clauses) 193 | cols-and-vals (take-while (comp not keyword?) cols-and-vals-etc) 194 | n-cols-and-vals (count cols-and-vals) 195 | no-cols-and-vals (zero? n-cols-and-vals) 196 | options (drop (+ (count rows) (count cols-and-vals)) clauses) 197 | {:keys [entities] :or {entities as-is}} (apply hash-map options)] 198 | (if (zero? n-rows) 199 | (if no-cols-and-vals 200 | (throw (IllegalArgumentException. "insert called without data to insert")) 201 | (if (< n-cols-and-vals 2) 202 | (throw (IllegalArgumentException. "insert called with columns but no values")) 203 | (insert-multi-row table (first cols-and-vals) (rest cols-and-vals) entities))) 204 | (if no-cols-and-vals 205 | (map (fn [row] (insert-single-row table row entities)) rows) 206 | (throw (IllegalArgumentException. "insert may take records or columns and values, not both")))))) 207 | 208 | (defn join 209 | "Given a table name and a map of how to join it (to the existing SQL fragment), 210 | retun the SQL string for the JOIN clause. The optional entities spec (default 'as-is') 211 | specifies how to transform column names." 212 | [table on-map & {:keys [entities] :or {entities as-is}}] 213 | (str "JOIN " (table-str table entities) " ON " 214 | (str/join 215 | " AND " 216 | (map (fn [[k v]] (str (as-str entities k) " = " (as-str entities v))) on-map)))) 217 | 218 | (defn order-by 219 | "Given a sequence of column order specs, and an optional entities spec, return the 220 | SQL string for the ORDER BY clause. A column order spec may be a column name or a 221 | map of the column name to the desired order." 222 | [cols & {:keys [entities] :or {entities as-is}}] 223 | (let [singleton (or (string? cols) (keyword? cols) (map? cols))] 224 | (if (or singleton (seq cols)) 225 | (str "ORDER BY " 226 | (if singleton 227 | (order-direction cols entities) 228 | (str/join "," (map #(order-direction % entities) cols)))) 229 | ""))) 230 | 231 | (defn select 232 | "Given a sequence of column names (or *) and a table name, followed by optional SQL 233 | clauses, return a vector for the SQL followed by its parameters. The general form is: 234 | (select [columns] table joins [where params] order-by options) 235 | where joins are optional strings, as is order-by, and the where clause is a vector 236 | of a where SQL clause followed by its parameters. The options may may include an 237 | entities spec to specify how column names should be transformed. 238 | The intent is that the joins, where clause and order by clause are generated by 239 | other parts of the DSL: 240 | (select * {:person :p} 241 | (join {:address :a} {:p.addressId :a.id}) 242 | (where {:a.zip 94546}) 243 | (order-by :p.name))" 244 | [col-seq table & clauses] 245 | (let [joins (take-while string? clauses) 246 | where-etc (drop (count joins) clauses) 247 | [where-clause & more] where-etc 248 | [where & params] (when-not (keyword? where-clause) where-clause) 249 | order-etc (if (keyword? where-clause) where-etc more) 250 | [order-clause & more] order-etc 251 | order-by (when (string? order-clause) order-clause) 252 | options (if order-by more order-etc) 253 | {:keys [entities] :or {entities as-is}} (apply hash-map options)] 254 | (cons (str "SELECT " 255 | (cond 256 | (= * col-seq) "*" 257 | (or (string? col-seq) 258 | (keyword? col-seq) 259 | (map? col-seq)) (col-str col-seq entities) 260 | :else (str/join "," (map #(col-str % entities) col-seq))) 261 | " FROM " (table-str table entities) 262 | (when (seq joins) (str/join " " (cons "" joins))) 263 | (when where " WHERE ") 264 | where 265 | (when order-by " ") 266 | order-by) 267 | params))) 268 | 269 | (defn update 270 | "Given a table name and a map of columns to set, and optional map of columns to 271 | match (and an optional entities spec), return a vector of the SQL for that update 272 | followed by its parameters. Example: 273 | (update :person {:zip 94540} (where {:zip 94546})) 274 | returns: 275 | [\"UPDATE person SET zip = ? WHERE zip = ?\" 94540 94546]" 276 | [table set-map & where-etc] 277 | (let [[where-clause & options] (when-not (keyword? (first where-etc)) where-etc) 278 | [where & params] where-clause 279 | {:keys [entities] :or {entities as-is}} (if (keyword? (first where-etc)) where-etc options) 280 | ks (keys set-map) 281 | vs (vals set-map)] 282 | (cons (str "UPDATE " (table-str table entities) 283 | " SET " (str/join 284 | "," 285 | (map (fn [k v] 286 | (str (as-str entities k) 287 | " = " 288 | (if (nil? v) "NULL" "?"))) 289 | ks vs)) 290 | (when where " WHERE ") 291 | where) 292 | (concat (remove nil? vs) params)))) 293 | 294 | (defn where 295 | "Given a map of columns and values, return a vector containing the where clause SQL 296 | followed by its parameters. Example: 297 | (where {:a 42 :b nil}) 298 | returns: 299 | [\"a = ? AND b IS NULL\" 42]" 300 | [param-map & {:keys [entities] :or {entities as-is}}] 301 | (let [ks (keys param-map) 302 | vs (vals param-map)] 303 | (if (vector? param-map) 304 | param-map 305 | (cons (str/join 306 | " AND " 307 | (map (fn [k v] 308 | (str (as-str entities k) 309 | (if (sequential? v) 310 | (str " IN (" (str/join ", " (repeat (count v) "?")) ")") 311 | (if (nil? v) " IS NULL" " = ?")))) 312 | ks vs)) 313 | (remove nil? (flatten vs)))))) 314 | -------------------------------------------------------------------------------- /test/java_jdbc/ddl_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse Public 3 | ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can 4 | ;; be found in the file epl-v10.html at the root of this distribution. By 5 | ;; using this software in any fashion, you are agreeing to be bound by the 6 | ;; terms of this license. You must not remove this notice, or any other, 7 | ;; from this software. 8 | ;; 9 | ;; ddl_test.clj 10 | ;; 11 | ;; This namespace contains tests of all the functions within 12 | ;; java-jdbc.ddl and does not rely on any databases. 13 | ;; 14 | ;; scgilardi (gmail) 15 | ;; Created 13 September 2008 16 | ;; 17 | ;; seancorfield (gmail) 18 | ;; Migrated from clojure.contrib.test-sql 17 April 2011 19 | ;; 20 | ;; moquist (gmail) 21 | ;; Migrated test-create-table from clojure.java.test-utilities, added 22 | ;; new tests. 23 June 2013 23 | 24 | (ns java-jdbc.ddl-test 25 | (:use clojure.test) 26 | (:require [java-jdbc.ddl :as ddl] 27 | [java-jdbc.sql :as sql] 28 | [clojure.string :as str])) 29 | 30 | (deftest test-create-table 31 | (is (= "CREATE TABLE table (col1 int, col2 int)" 32 | (ddl/create-table :table ["col1 int"] [:col2 :int]))) 33 | (is (= "CREATE TABLE TABLE (col1 int, COL2 INT)" 34 | (ddl/create-table :table ["col1 int"] [:col2 :int] :entities str/upper-case))) 35 | (is (= "CREATE TABLE TABLE (col1 int, COL2 INT)" 36 | (sql/entities str/upper-case 37 | (ddl/create-table :table ["col1 int"] [:col2 :int])))) 38 | (is (= "CREATE TABLE table (col1 int, col2 int) ENGINE=MyISAM" 39 | (ddl/create-table :table [:col1 "int"] ["col2" :int] :table-spec "ENGINE=MyISAM"))) 40 | (is (= "CREATE TABLE TABLE (COL1 int, col2 INT) ENGINE=MyISAM" 41 | (ddl/create-table :table [:col1 "int"] ["col2" :int] :entities str/upper-case :table-spec "ENGINE=MyISAM"))) 42 | (is (= "CREATE TABLE TABLE (COL1 int, col2 INT) ENGINE=MyISAM" 43 | (ddl/create-table :table [:col1 "int"] ["col2" :int] :table-spec "ENGINE=MyISAM" :entities str/upper-case)))) 44 | 45 | (deftest test-drop-table 46 | (is (= "DROP TABLE table") 47 | (ddl/drop-table :table)) 48 | (is (= "DROP TABLE TABLE") 49 | (ddl/drop-table :table :entities str/upper-case)) 50 | (is (= "DROP TABLE TABLE") 51 | (sql/entities str/upper-case 52 | (ddl/drop-table :table)))) 53 | 54 | (deftest test-create-index 55 | (is (= "CREATE INDEX index ON table (col1, col2)" 56 | (ddl/create-index :index :table [:col1 "col2"]))) 57 | (is (= "CREATE INDEX INDEX ON TABLE (COL1, col2)" 58 | (ddl/create-index :index :table [:col1 "col2"] :entities str/upper-case))) 59 | (is (= "CREATE INDEX INDEX ON TABLE (COL1, col2)" 60 | (sql/entities str/upper-case 61 | (ddl/create-index :index :table [:col1 "col2"])))) 62 | (is (= "CREATE UNIQUE INDEX index ON table (col1, col2)" 63 | (ddl/create-index :index :table [:col1 "col2"] :unique))) 64 | (is (= "CREATE UNIQUE INDEX INDEX ON TABLE (COL1, col2)" 65 | (ddl/create-index :index :table [:col1 "col2"] :unique :entities str/upper-case))) 66 | (is (= "CREATE UNIQUE INDEX INDEX ON TABLE (COL1, col2)" 67 | (ddl/create-index :index :table [:col1 "col2"] :entities str/upper-case :unique)))) 68 | 69 | (deftest test-drop-index 70 | (is (= "DROP INDEX index") 71 | (ddl/drop-index :index)) 72 | (is (= "DROP INDEX INDEX") 73 | (ddl/drop-index :index :entities str/upper-case)) 74 | (is (= "DROP INDEX INDEX") 75 | (sql/entities str/upper-case 76 | (ddl/drop-index :index)))) 77 | -------------------------------------------------------------------------------- /test/java_jdbc/sql_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Sean Corfield. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse Public 3 | ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can 4 | ;; be found in the file epl-v10.html at the root of this distribution. By 5 | ;; using this software in any fashion, you are agreeing to be bound by the 6 | ;; terms of this license. You must not remove this notice, or any other, 7 | ;; from this software. 8 | ;; 9 | ;; sql_test.clj 10 | ;; 11 | ;; This namespace contains tests that exercise java-jdbc.sql 12 | ;; 13 | ;; seancorfield (gmail) 14 | ;; Constructured from tests for jsql DSL December 2012 15 | 16 | (ns java-jdbc.sql-test 17 | (:refer-clojure :exclude [update]) 18 | (:use clojure.test 19 | java-jdbc.sql)) 20 | 21 | (deftest select-dsl 22 | (is (= ["SELECT * FROM table"] (select * :table))) 23 | (is (= ["SELECT id FROM table"] (select :id :table))) 24 | (is (= ["SELECT id,name FROM table"] (select [:id :name] :table))) 25 | (is (= ["SELECT id AS foo,name FROM table"] (select [{:id :foo} :name] :table))) 26 | (is (= ["SELECT t.id FROM table t"] (select :t.id {:table :t})))) 27 | 28 | (deftest quoting-fns 29 | (is (= "`a`" ((quoted \`) "a"))) 30 | (is (= "`A`" ((quoted \`) "A"))) 31 | (is (= "a" (as-is "a"))) 32 | (is (= "A" (as-is "A"))) 33 | (is (= "a" (lower-case "a"))) 34 | (is (= "a" (lower-case "A")))) 35 | 36 | (deftest select-entities 37 | (is (= ["SELECT * FROM `table`"] (entities (quoted \`) (select * :table)))) 38 | (is (= ["SELECT * FROM table"] (entities as-is (select * :table)))) 39 | (is (= ["SELECT `t`.`id` FROM `table` `t`"] (entities (quoted \`) (select :t.id {:table :t}))))) 40 | 41 | (deftest join-dsl 42 | (is (= "JOIN t ON a.id = t.id" (join :t {:a.id :t.id})))) 43 | 44 | (deftest select-join-dsl 45 | (is (= ["SELECT * FROM a JOIN b ON a.id = b.id"] (select * :a (join :b {:a.id :b.id})))) 46 | (is (= ["SELECT * FROM a JOIN b ON a.id = b.id JOIN c ON c.id = b.id"] 47 | (select * :a (join :b {:a.id :b.id}) (join :c {:c.id :b.id}))))) 48 | 49 | (deftest where-dsl 50 | (is (= ["id = ?" 1] (where ["id = ?" 1]))) 51 | (is (= ["id = ?" 42] (where {:id 42}))) 52 | (is (= ["id IS NULL"] (where {:id nil}))) 53 | (is (= ["id IN (?, ?, ?, ?, ?, ?)" 4 8 15 16 23 42] (where {:id [4 8 15 16 23 42]}))) 54 | (is (= ["name = ? AND code IS NULL AND id IN (?, ?, ?)" "wow" 4 8 15] (where (array-map :name "wow" :code nil :id [4 8 15]))))) 55 | 56 | (deftest select-where-dsl 57 | (is (#{["SELECT * FROM a WHERE c = ? AND b = ?" 3 2] 58 | ["SELECT * FROM a WHERE b = ? AND c = ?" 2 3]} 59 | (select * :a (where {:b 2 :c 3})))) 60 | (is (#{["SELECT * FROM a WHERE c IS NULL AND b = ?" 2] 61 | ["SELECT * FROM a WHERE b = ? AND c IS NULL" 2]} 62 | (select * :a (where {:b 2 :c nil}))))) 63 | 64 | (deftest order-by-dsl 65 | (is (= "" (order-by []))) 66 | (is (= "ORDER BY a ASC" (order-by :a))) 67 | (is (= "ORDER BY a ASC" (order-by [:a]))) 68 | (is (= "ORDER BY a DESC" (order-by {:a :desc}))) 69 | (is (= "ORDER BY a ASC,b ASC" (order-by [:a :b]))) 70 | (is (= "ORDER BY a DESC,b ASC" (order-by [{:a :desc} :b]))) 71 | (is (= "ORDER BY a DESC,b DESC" (order-by [{:a :desc} {:b :desc}]))) 72 | (is (= "ORDER BY `a` ASC,`b` DESC" (entities (quoted \`) (order-by [{:a :asc} {:b :desc}])))) 73 | (is (= "ORDER BY `a` ASC,`b` DESC" (order-by [{:a :asc} {:b :desc}] :entities (quoted \`))))) 74 | 75 | (deftest select-order-dsl 76 | (is (= ["SELECT id FROM person ORDER BY name ASC"] (select :id :person (order-by :name)))) 77 | (is (= ["SELECT a FROM b JOIN c ON b.id = c.id ORDER BY d ASC" 78 | (select :a :b (join :c {:b.id :c.id}) (order-by :d))])) 79 | (is (= ["SELECT a FROM b WHERE c = ? ORDER BY d ASC" 3] 80 | (select :a :b (where {:c 3}) (order-by :d)))) 81 | (is (= ["SELECT a FROM b JOIN c ON b.id = c.id WHERE d = ? ORDER BY e ASC" 4] 82 | (select :a :b (join :c {:b.id :c.id}) (where {:d 4}) (order-by :e))))) 83 | 84 | (deftest select-join-alias-dsl 85 | (is (= ["SELECT a.id,b.name FROM aa a JOIN bb b ON a.id = b.id WHERE b.test = ?" 42] 86 | (select [:a.id :b.name] {:aa :a} 87 | (join {:bb :b} {:a.id :b.id}) 88 | (where {:b.test 42})))) 89 | (is (= ["SELECT `a`.`id`,`b`.`name` FROM `aa` `a` JOIN `bb` `b` ON `a`.`id` = `b`.`id` WHERE `b`.`test` = ?" 42] 90 | (entities (quoted \`) 91 | (select [:a.id :b.name] {:aa :a} 92 | (join {:bb :b} {:a.id :b.id}) 93 | (where {:b.test 42})))))) 94 | 95 | (deftest update-dsl 96 | (is (= ["UPDATE a SET b = ?" 2] (update :a {:b 2}))) 97 | (is (= ["UPDATE a SET b = ? WHERE c = ?" 2 3] (update :a {:b 2} (where {:c 3})))) 98 | (is (= ["UPDATE `a` SET `b` = ? WHERE `c` = ?" 2 3] 99 | (entities (quoted \`) (update :a {:b 2} (where {:c 3})))))) 100 | 101 | (deftest delete-dsl 102 | (is (= ["DELETE FROM a WHERE b = ?" 2] (delete :a (where {:b 2})))) 103 | (is (#{["DELETE FROM a WHERE c IS NULL AND b = ?" 2] 104 | ["DELETE FROM a WHERE b = ? AND c IS NULL" 2]} 105 | (delete :a (where {:b 2 :c nil})))) 106 | (is (= ["DELETE FROM `a` WHERE `b` = ?" 2] 107 | (entities (quoted \`) (delete :a (where {:b 2})))))) 108 | 109 | (deftest insert-dsl 110 | (is (= ["INSERT INTO a ( b ) VALUES ( ? )" [2]] (insert :a [:b] [2]))) 111 | (is (= ["INSERT INTO a VALUES ( ? )" [2]] (insert :a nil [2]))) 112 | (is (= ["INSERT INTO a VALUES ( ? )" [2]] (insert :a [] [2]))) 113 | (is (= [["INSERT INTO a ( b ) VALUES ( ? )" 2]] (insert :a {:b 2}))) 114 | (is (= ["INSERT INTO a ( b ) VALUES ( ? )" [2] [3]] (insert :a [:b] [2] [3]))) 115 | (is (= ["INSERT INTO a VALUES ( ? )" [2] [3]] (insert :a nil [2] [3]))) 116 | (is (= ["INSERT INTO a VALUES ( ? )" [2] [3]] (insert :a [] [2] [3]))) 117 | (is (= ["INSERT INTO a ( b, c, d ) VALUES ( ?, ?, ? )" [2 3 4] [3 4 5]] 118 | (insert :a [:b :c :d] [2 3 4] [3 4 5]))) 119 | (is (= ["INSERT INTO a VALUES ( ?, ?, ? )" [2 3 4] [3 4 5]] 120 | (insert :a nil [2 3 4] [3 4 5]))) 121 | (is (= ["INSERT INTO a VALUES ( ?, ?, ? )" [2 3 4] [3 4 5]] 122 | (insert :a [] [2 3 4] [3 4 5]))) 123 | (is (= [["INSERT INTO a ( b ) VALUES ( ? )" 2] 124 | ["INSERT INTO a ( b ) VALUES ( ? )" 3]] 125 | (insert :a {:b 2} {:b 3}))) 126 | (is (= ["INSERT INTO `a` ( `b` ) VALUES ( ? )" [2]] 127 | (entities (quoted \`) (insert :a [:b] [2])))) 128 | (is (= ["INSERT INTO `a` VALUES ( ? )" [2]] 129 | (entities (quoted \`) (insert :a nil [2])))) 130 | (is (= ["INSERT INTO `a` VALUES ( ? )" [2]] 131 | (entities (quoted \`) (insert :a [] [2]))))) 132 | 133 | (deftest bad-insert-args 134 | (is (thrown? IllegalArgumentException (insert))) 135 | (is (thrown? IllegalArgumentException (insert :a))) 136 | (is (thrown? IllegalArgumentException (insert :a [:b]))) 137 | (is (thrown? IllegalArgumentException (insert :a {:b 1} [:c] [2]))) 138 | (is (thrown? IllegalArgumentException (insert :a [:b] [2 3])))) 139 | --------------------------------------------------------------------------------