diff --git a/.gitignore b/.gitignore index 993df89..be5fd62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ /target /classes /checkouts -/pom.xml /pom.xml.asc *.jar *.class /.lein-* /.nrepl-port -/doc/dist \ No newline at end of file +/doc/dist +/.cpcache +/.rebel_readline_history \ No newline at end of file diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..b741b5e --- /dev/null +++ b/deps.edn @@ -0,0 +1,21 @@ +{:deps {org.clojure/clojure {:mvn/version "1.10.1"} + org.jooq/jooq {:mvn/version "3.11.11"}} + + :paths ["src"] + :aliases + {:dev {:extra-deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"} + org.clojure/tools.namespace {:mvn/version "0.3.0"} + org.postgresql/postgresql {:mvn/version "42.2.6"} + com.h2database/h2 {:mvn/version "1.4.199"} + cheshire/cheshire {:mvn/version "5.8.1"}} + :extra-paths ["test"]} + :repl {:main-opts ["-m" "rebel-readline.main"]} + :ancient {:main-opts ["-m" "deps-ancient.deps-ancient"] + :extra-deps {deps-ancient {:mvn/version "RELEASE"}}} + + :test {:extra-deps {lambdaisland/kaocha {:mvn/version "0.0-521"}} + :main-opts ["-m" "kaocha.runner"]} + }} + + + diff --git a/doc/content.adoc b/doc/content.adoc index 4b731f1..e79fc73 100644 --- a/doc/content.adoc +++ b/doc/content.adoc @@ -16,8 +16,7 @@ link:http://www.jooq.org/[jooq library]) It consists in four modules: - *suricatta.core*: api for executing queries. -- *suricatta.dsl*: lightweight dsl for idiomatic and composable sql building. -- *suricatta.format*: sql rendering functions. +- *suricatta.dsl.alpha*: lightweight dsl for idiomatic and composable sql building. === Project Maturity @@ -32,7 +31,7 @@ dependency vector on your *_project.clj_* file: [source,clojure] ---- -[funcool/suricatta "1.3.1"] +[funcool/suricatta "2.0.0"] ---- _Suricatta_ is only runs with *JDK >= 8* and *Clojure >= 1.5* @@ -292,41 +291,30 @@ size (currently 100.) === Custom types -Since 0.2.0 version, suricatta comes with support for extension with custom -(or vendor specific) types support. It consist in two protocols, one for converting -user defined types to jooq/jdbc compatible types, and other for backwards conversion. +If you want to use suricatta with a database that exposes +non-standard/complex types, suricatta provides an easy path for +extending it. That consists in two protocols, one for converting user +defined types to jooq/jdbc compatible types, and other for backwards +conversion. .Example adapting clojure persistent map interface to postgresql json file. [source, clojure] ---- (require '[suricatta.proto :as proto] + '[suricatta.impl :as impl] '[cheshire.core :as json]) (import 'org.postgresql.util.PGobject) -(extend-protocol proto/IParamType +(extend-protocol proto/IParam clojure.lang.IPersistentMap - (-render [self ctx] - (if (proto/-inline? ctx) - (str "'" (json/encode self) "'::json") - "?::json")) - - (-bind [self ctx] - (when-not (proto/-inline? ctx) - (let [stmt (proto/-statement ctx) - idx (proto/-next-bind-index ctx) - obj (doto (PGobject.) - (.setType "json") - (.setValue (json/encode (.-data self))))] - (.setObject stmt idx obj))))) + (-param [self ctx] + (let [qp (json/encode (.-data self))] + (impl/sql->param "{0}::json" qp)))) ---- -The `-render` function is responsible of generate the appropiate sql for this field. -The value should be inlined or rendered as bind ready parameter depending on the -`inline` value that can be retrieved from the `RenderContext`. - -The `-bind` function reponsibility is just bind the appropiate values to the -prepared statement only if the context indicates that is not inlined. +The `-param` function is responsible of generate the appropiate sql +part for this field. Now let see the backward conversion example: @@ -360,708 +348,14 @@ value to the query and it is automatically converted. == SQL Building and Formatting -This section intends to explain the usage of sql building library, the lightweight -layer on top of `jooq` dsl. - -You can found all related functions of sql dsl on `suricatta.dsl` namespace: - -[source, clojure] ----- -(require '[suricatta.dsl :as dsl]) ----- - -And functions related to formating sql into string or sqlvec format in -`suricatta.format` namespace: - -[source, clojure] ----- -(require '[suricatta.format :as fmt]) ----- - - -Object instances retured by dsl api are fully compatible with the sql executing -api. Let see an example: - -[source, clojure] ----- -(def my-query - (-> (dsl/select :id) - (dsl/from :books) - (dsl/where ["age > ?", 100]) - (dsl/limit 1))) - -(with-open [ctx (sc/context dbspec)] - (sc/fetch ctx my-query)) -;; => [{:id 4232}] ----- - - -=== The SELECT statement - -==== Select clause - -Simple select clause without from part: - -[source, clojure] ----- -(dsl/select :id :name) ----- - -Would generate SQL like this: - -[source,sql] ----- -select id, name from dual ----- - -The rendering result depends on the dialect used. You can specify a different -dialect by passing the `:dialect` option to the `sql` function of -`suricatta.format` namespace: - -[source, clojure] ----- -(-> (dsl/select :id :name) - (fmt/sql {:dialect :postgresql})) -;; => "select id, name" ----- - - -==== Select DISTINCT - -You can add the distinct keyword by using a special select function: - -[source, clojure] ----- -(-> (dsl/select-distinct :name) - (fmt/sql)) -;; => "select distinct name" ----- - - -==== Select * - -You can ommit fields on `select` function to use the "SELECT *" sql form: - -[source, clojure] ----- -(-> (dsl/select) - (dsl/from :book) - (fmt/sql)) -;; => "select * from book" ----- - - -==== Select with function - -In select clauses you can put any kind of expresions such as sql functions: - -[source, clojure] ----- -(-> (dsl/select '("length(book.title)" "title_length")) - (dsl/from :book) - (fmt/sql)) -;; => "select length(book.title) \"title_length\" from book" ----- - - -==== The FROM clause - -A simple sql "select ... from" clause: - -[source, clojure] ----- -(-> (dsl/select :book.id :book.name) - (dsl/from :book) - (fmt/sql)) -;; => "select book.id, book.name from book" ----- - -Also, the sql from clause supports any number of tables: - -[source, clojure] ----- -(-> (dsl/select-one) - (dsl/from :book :article) - (fmt/sql)) -;; => "select 1 from book, article" ----- - -Also, you can specify an alias for each table: - -[source, clojure] ----- -(-> (dsl/select-one) - (dsl/from '("book" "b") - '("article" "a")) - (fmt/sql)) -;; => "select 1 from book \"a\", article \"b\"" ----- - - -==== The JOIN clause - -_suricata_ comes with a complete dsl for making join clauses. Let see one -simple example: - -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from :book) - (dsl/join :author) - (dsl/on "book.author_id = book.id") - (fmt/sql)) -;; => "select name from book join author on (book.author_id = book.id)" ----- - -You can use table aliases with join clauses: - -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from '("book" "b")) - (dsl/join '("author" "a")) - (dsl/on "b.author_id = a.id") - (fmt/sql)) -;; => "select name from book \"b\" join author \"a\" on (b.author_id = a.id)" ----- - -Also, join clause can be applied to table expressions: - -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from (-> (dsl/table "book") - (dsl/join "author") - (dsl/on "book.author_id = book.id"))) - (fmt/sql)) -;; => "select name from book join author on (book.author_id = book.id)" ----- - - -==== The WHERE clause - -The WHERE clause can be used to JOIN or filter predicates in order to restrict -the data returned by the query: - -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from :book) - (dsl/where "book.age > 100") - (fmt/sql)) -;; => "select name from book where (book.age > 100)" ----- - -Building a where clause with multiple conditions: - -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from :book) - (dsl/where "book.age > 100" - "book.in_store = true") - (fmt/sql)) -;; => "select name from book where ((book.age > 100) and (book.in_store = true))" ----- - - -Bind parameters instead of inlining them on conditions: - -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from :book) - (dsl/where ["book.age > ?" 100] - ["book.in_store = ?", true]) - (fmt/sqlvec)) -;; => ["select name from book where ((book.age > ?) and (book.in_store = ?))" 100 true] ----- - -Using explicit logical operators: - -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from :book) - (dsl/where (dsl/or "book.age > 20" - (dsl/not "book.in_store"))) - (fmt/sql)) -;; => "select name from book where ((book.age > 20) or (not book.in_store))" ----- - -Different kind of joins are suported with that functions: `dsl/full-outer-join`, -`dsl/left-outer-join`, `dsl/right-outer-join` and `dsl/cross-join`. - - -==== The GROUP BY clause - -GROUP BY can be used to create unique groups of data, to form aggregations, to -remove duplicates and for other reasons. Let see an example of how it can be -done using the _suricatta_ dsl: - -[source, clojure] ----- -(-> (dsl/select (dsl/field "name") - (dsl/field "count(*)")) - (dsl/from :book) - (dsl/group-by :name) - (fmt/sql)) -;; => "select name, count(*) from book group by name" ----- - - -==== The HAVING clause - -The HAVING clause is used to further restrict aggregated data. Let see an example: - -[source, clojure] ----- -(-> (dsl/select (dsl/field "name") - (dsl/field "count(*)")) - (dsl/from :book) - (dsl/group-by :name) - (dsl/having ["count(*) > ?", 2]) - (fmt/sql)) -;; => "select name, count(*) from book group by name having (count(*) > ?)" ----- - -==== The ORDER BY clause - -Here's an example of how specify the ordering to the query: - -.Ordering by field with implicit sort direction -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from :book) - (dsl/order-by :name) - (fmt/sql)) -;; => "select name from book order by name asc" ----- - -In previous example we specified the order field without order direction. -_surricata_ automatically uses `ASC` for sort fields that comes without explicit -ordering direction. - -.Specify sort direction explicitly -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from :book) - (dsl/order-by [:name :desc]) - (fmt/sql)) -;; => "select name from book order by name desc" ----- - -.Handling nulls -[source, clojure] ----- -(-> (dsl/select :name) - (dsl/from :book) - (dsl/order-by [:name :desc :nulls-last]) - (fmt/sql)) -;; => "select name from book order by name desc nulls last" ----- - -.Ordering by index -[source, clojure] ----- -(-> (dsl/select :id :name) - (dsl/from :book) - (dsl/order-by ["1" :asc] - ["2" :desc]) - (fmt/sql)) -;; => "select name from book order by 1 asc, 2 desc" ----- - - -==== The LIMIT and OFFSET clauses - -Let see some examples of how to apply `limit` and `offset` to your queries -with _suricatta_: +TBD -[source, clojure] ----- -(-> (dsl/select :id :name) - (dsl/from :book) - (dsl/limit 10) - (dsl/offset 100) - (fmt/sql)) -;; => "select name from book limit ? offset ?" ----- - - -==== The FOR UPDATE clause - -For inter-process synchronisation and other reasons, you may choose to use the -`SELECT .. FOR UPDATE` clause to indicate to the database, that a set of cells -or records should be locked by a given transaction for subsequent updates. Let -see an example of how use it with _suricatta_ dsl: - -.Without specific fields -[source, clojure] ----- -(-> (dsl/select) - (dsl/from :book) - (dsl/for-update) - (fmt/sql)) -;; => "select * from book for update" ----- - -.With specific fields -[source, clojure] ----- -(-> (dsl/select) - (dsl/from :book) - (dsl/for-update :name) - (fmt/sql)) -;; => "select * from book for update of \"name\"" ----- - - -==== The UNION and UNION ALL clause - -These operators combine two results into one. UNION removes all duplicate -records resulting from this combination and UNION ALL preserves all results as -they are. - -.Using UNION clause -[source, clojure] ----- -(-> (dsl/union - (-> (dsl/select :name) - (dsl/from :books)) - (-> (dsl/select :name) - (dsl/from :articles))) - (fmt/sql)) -;; => "(select name from books) union (select name from articles)" ----- - -.Using UNION ALL clause -[source, clojure] ----- -(-> (dsl/union-all - (-> (dsl/select :name) - (dsl/from :books)) - (-> (dsl/select :name) - (dsl/from :articles))) - (fmt/sql)) -;; => "(select name from books) union all (select name from articles)" ----- - - -=== The INSERT statement - -The INSERT statement is used to insert new records into a database table. - -.Example of insert two rows in one table. -[source, clojure] ----- -(-> (dsl/insert-into :table1) - (dsl/insert-values {:f1 1 :f2 2 :f3 3}) - (dsl/insert-values {:f1 4 :f2 5 :f3 6}) - (fmt/sqlvec)) -;; => ["insert into t1 (f1, f2, f3) values (?, ?, ?), (?, ?, ?)" 1 2 3 4 5 6] ----- - - -=== The UPDATE statement - -The UPDATE statement is used to modify one or several pre-existing records in -a database table. - -.Example of update statement without condition. -[source, clojure] ----- -(-> (dsl/update :t1) - (dsl/set :name "foo") - (fmt/sql)) -;; => "update t1 set name = ?" ----- - -.Example of update statement without condition using a map -[source, clojure] ----- -(-> (dsl/update :t1) - (dsl/set {:name "foo" :bar "baz"}) - (fmt/sql)) -;; => "update t1 set name = ?, bar = ?" ----- - -.Example of update statement with one condition. -[source, clojure] ----- -(-> (dsl/update :t1) - (dsl/set :name "foo") - (dsl/where ["id = ?" 1]) - (fmt/sql)) -;; => "update t1 set name = ? where (id = ?)" ----- - -.Example of update statement using subquery. -[source, clojure] ----- -(-> (dsl/update :t1) - (dsl/set :f1 (-> (dsl/select :f2) - (dsl/from :t2) - (dsl/where ["id = ?" 2]))) - (fmt/sql {:dialect :pgsql})) -;; => "update t1 set f1 = (select f2 from t2 where (id = ?))" ----- - -.Example of multiple assignation un update statement using subquery. -[source, clojure] ----- -(-> (dsl/update :t1) - (dsl/set (dsl/row (dsl/field :f1) - (dsl/field :f2)) - (-> (dsl/select :f3 :f4) - (dsl/from :t2) - (dsl/where ["id = ?" 2]))) - (fmt/sql {:dialect :pgsql})) -;; => "update t1 set (f1, f2) = (select f3, f4 from t2 where (id = ?))" ----- - -.Example of returning clause used in UPDATE statement. -[source, clojure] ----- -(-> (dsl/update :t1) - (dsl/set :name "foo") - (dsl/returning :id) - (fmt/sql {:dialect :pgsql})) -;; => "update t1 set name = ? returning id" ----- - -.Example using function as value. -[source, clojure] ----- -(-> (dsl/update :t1) - (dsl/set :name (dsl/f ["concat(name, ?)" "-foo"])) - (dsl/set :name_length (dsl/f "length(name)")) - (dsl/returning :id) - (fmt/sql {:dialect :pgsql})) -;; => "update t1 set name = ? returning id" ----- - - -=== The DELETE statement - -.Simple example of delete statement with one condition -[source, clojure] ----- -(-> (dsl/delete :t1) - (dsl/where "id = 1") - (fmt/sql)) -;; => "delete from t1 where (id = 1)" ----- - - -=== The DDL statements - - -==== The TRUNCATE statement - -[source, clojure] ----- -(-> (dsl/truncate :table1) - (fmt/sql)) -;; => "truncate table table1" ----- - - -==== The CREATE TABLE statement - -[source, clojure] -(-> (dsl/create-table :t1) - (dsl/add-column :title {:type :pg/varchar :length 100 :null false}) - (fmt/sql)) -;; => "create table t1 (title varchar(100) not null)" - -NOTE: at this moment, the add-column function doest not permit the way to setup -default value for a field in table creation statement. - - -==== The DROP TABLE statement - -.Drop table example -[source, clojure] ----- -(-> (dsl/drop-table :t1) - (fmt/sql)) -;; => "drop table t1" ----- - - -==== The ALTER TABLE statement - -Alter statements are used mainly to add, modify or delete columns from table. - -.Add new column -[source, clojure] ----- -(-> (dsl/alter-table :t1) - (dsl/add-column :title {:type :pg/varchar :length 2 :null false}) - (fmt/sql)) -;; => "alter table t1 add title varchar(2) not null" ----- - -.Change type of column -[source, clojure] ----- -(-> (dsl/alter-table :t1) - (dsl/alter-column :title {:type :pg/varchar :length 100 :null false}) - (fmt/sql)) -;; => "alter table t1 alter title varchar(100) not null" ----- - -.Drop column -[source, clojure] ----- -(-> (dsl/alter-table :t1) - (dsl/drop-column :title :cascade) - (fmt/sql)) -;; => "alter table t1 drop title cascade" ----- - - -[[create-index]] -==== The CREATE INDEX statement - -.Create simple on field -[source, clojure] ----- -(-> (dsl/create-index "test") - (dsl/on :t1 :title) - (fmt/sql)) -;; => "create index \"test\" on t1(title)" ----- - -.Create index on field expression -[source, clojure] ----- -(-> (dsl/create-index "test") - (dsl/on :t1 (dsl/field "lower(title)")) - (fmt/sql)) -;; => "create index \"test\" on t1(lower(title))" ----- - - -[[drop-index]] -==== The DROP INDEX statement - -.Drop index -[source, clojure] ----- -(-> (dsl/drop-index "test") - (fmt/sql)) -;; => "drop index \"test\"" ----- - - -==== The CREATE SEQUENCE statement - -[source, clojure] ----- -(-> (dsl/create-sequence "testseq") - (fmt/sql)) -;; => "create sequence \"testseq\"" ----- - - -==== The ALTER SEQUENCE statement - -.Restart sequence -[source, clojure] ----- -(-> (dsl/alter-sequence "testseq" true) - (fmt/sql)) -;; => "alter sequence \"testseq\" restart" ----- - -.Restart sequence with concrete number -[source, clojure] ----- -(-> (dsl/alter-sequence "testseq" 19) - (fmt/sql)) -;; => "alter sequence \"testseq\" restart with 19" ----- - - -==== The DROP SEQUENCE statement - -.Drop sequence -[source, clojure] ----- -(-> (dsl/drop-sequence "testseq") - (fmt/sql)) -;; => "drop sequence \"testseq\"" ----- - -.Drop sequence if exists -[source, clojure] ----- -(-> (dsl/drop-sequence "testseq" true) - (fmt/sql)) -;; => "drop sequence if exists \"testseq\"" ----- - - -=== Table Expressions - -==== The VALUES() table constructor - -Some databases allow expressing in-memory temporary tables using a `values()` syntax. - -.Select from `values()` example -[source, clojure] ----- -(-> (dsl/select :f1 :f2) - (dsl/from (-> (dsl/values (dsl/row 1 2) - (dsl/row 3 4)) - (dsl/as-table "t1" "f1" "f2"))) - (fmt/sql {:type :inlined})) -;; => "select f1, f2 from (values(1, 2), (3, 4)) as \"t1\" (\"f1\", \"f2\")" ----- - -WARNING: `suricatta.dsl/row` is defined as a macro and only accepts literals. - - -==== Nested SELECTs - -.Using nested select in where clause -[source, clojure] ----- -(-> (dsl/select) - (dsl/from :book) - (dsl/where (list "book.age = ({0})" (dsl/select-one))) - (fmt/sql)) - -;; => "select * from book where (book.age = (select 1 as \"one\"))" ----- - -.Using nested select in from clause -[source, clojure] ----- -(-> (dsl/select) - (dsl/from (-> (dsl/select :f1) - (dsl/from :t1) - (dsl/as-table "tt1"))) - (fmt/sql)) -;; => "select \"tt1\".\"f1\" from (select f1 from t1) as \"tt1\"(\"f1\")" ----- - -.Using nested select in select fields clauses -[source, clojure] ----- -(-> (dsl/select :fullname, (-> (dsl/select (dsl/field "count(*)")) - (dsl/from :book) - (dsl/where "book.authorid = author.id") - (dsl/as-field "books"))) - (dsl/from :author) - (fmt/sql)) -;; => "select fullname, (select count(*) from book where (book.authorid = author.id)) "books" from author" ----- +Since version 2.0.0, the complex sql composition functions based on +jooq are eliminated (buggy and complex code that uses api mostly +defined for java). There are `suricatta.dsl.alpha` namespace that +contains a work in progress of the new approach. +If you want play with that look tests code to see how it works. == FAQ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..daf4d4f --- /dev/null +++ b/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + funcool + suricatta + jar + 2.0.0 + suricatta + High level sql toolkit for clojure (backed by jooq library) + https://github.com/funcool/suricatta + + + BSD (2-Clause) + http://opensource.org/licenses/BSD-2-Clause + + + + scm:git:git://github.com/funcool/suricatta.git + scm:git:ssh://git@github.com/funcool/suricatta.git + 5be805429be9e4226013af63947d19b93ecdd083 + + https://github.com/funcool/suricatta + + + src + test + + + resources + + + + + + clojars + https://repo.clojars.org/ + + + + + + + + org.clojure + clojure + 1.10.1 + + + org.jooq + jooq + 3.11.11 + + + diff --git a/project.clj b/project.clj deleted file mode 100644 index f55b446..0000000 --- a/project.clj +++ /dev/null @@ -1,24 +0,0 @@ -(defproject funcool/suricatta "1.3.1" - :description "High level sql toolkit for clojure (backed by jooq library)" - :url "https://github.com/funcool/suricatta" - :license {:name "BSD (2-Clause)" - :url "http://opensource.org/licenses/BSD-2-Clause"} - :dependencies [[org.clojure/clojure "1.9.0-alpha14" :scope "provided"] - [org.jooq/jooq "3.9.0"]] - :javac-options ["-target" "1.8" "-source" "1.8" "-Xlint:-options"] - :profiles - {:dev {:global-vars {*warn-on-reflection* false} - :jvm-opts ["-Dclojure.compiler.direct-linking=true"] - :aliases {"test-all" - ["with-profile" "dev,1.8:dev,1.7:dev,1.6:dev,1.5:dev" "test"]} - :plugins [[lein-ancient "0.6.10"]] - :dependencies [[org.postgresql/postgresql "9.4.1212"] - [com.h2database/h2 "1.4.193"] - [cheshire "5.6.3"]]} - :1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]} - :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} - :1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]} - :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}} - - :java-source-paths ["src/java"]) - diff --git a/src/java/suricatta/impl/IParam.java b/src/java/suricatta/impl/IParam.java deleted file mode 100644 index 19e87cf..0000000 --- a/src/java/suricatta/impl/IParam.java +++ /dev/null @@ -1,10 +0,0 @@ -package suricatta.impl; - -import org.jooq.RenderContext; -import org.jooq.BindContext; - - -public interface IParam { - public Object render(Object value, RenderContext ctx); - public Object bind(Object value, BindContext ctx); -} diff --git a/src/java/suricatta/impl/ParamWrapper.java b/src/java/suricatta/impl/ParamWrapper.java deleted file mode 100644 index 9d3d5c2..0000000 --- a/src/java/suricatta/impl/ParamWrapper.java +++ /dev/null @@ -1,81 +0,0 @@ -package suricatta.impl; - -import org.jooq.impl.DefaultDataType; -import org.jooq.conf.ParamType; -import org.jooq.RenderContext; -import org.jooq.BindContext; -import org.jooq.Context; -import org.jooq.DataType; -import org.jooq.Param; -import org.jooq.ParamMode; - -import static org.jooq.conf.ParamType.INDEXED; -import static org.jooq.conf.ParamType.INLINED; - - -@SuppressWarnings({"unchecked", "deprecation"}) -public class ParamWrapper extends org.jooq.impl.CustomField - implements Param { - - private final IParam adapter; - private boolean inline; - private Object value; - - public ParamWrapper(final IParam adapter, final Object value) { - super(null, DefaultDataType.getDefaultDataType("__suricatta_other")); - this.adapter = adapter; - this.value = value; - this.inline = false; - } - - @Override - public ParamMode getParamMode() { - return ParamMode.IN; - } - - @Override - public Object getValue() { - return value; - } - - @Override - public String getParamName() { - return null; - } - - @Override - public final ParamType getParamType() { - return inline ? INLINED: INDEXED; - } - - @Override - public void setValue(final Object val) { - this.setConverted(val); - } - - @Override - public void setConverted(final Object val) { - this.value = getDataType().convert(val); - } - - @Override - public void setInline(final boolean inline) { - this.inline = inline; - } - - @Override - public boolean isInline() { - return this.inline; - } - - @Override - public void accept(Context ctx) { - if (ctx instanceof RenderContext) { - this.adapter.render(this.value, (RenderContext) ctx); - } else { - this.adapter.bind(this.value, (BindContext) ctx); - } - } -} - - diff --git a/src/suricatta/core.clj b/src/suricatta/core.clj index ea4ab16..302b52f 100644 --- a/src/suricatta/core.clj +++ b/src/suricatta/core.clj @@ -95,6 +95,11 @@ ([cursor] (impl/cursor->lazyseq cursor {})) ([cursor opts] (impl/cursor->lazyseq cursor opts))) +(defn typed-field + "Get a instance of Field definitio." + [data type] + (impl/typed-field data type)) + (defn load-into "Load data into a table. Supports csv and json formats." ([ctx tablename data] (load-into ctx tablename data {})) diff --git a/src/suricatta/dsl.clj b/src/suricatta/dsl.clj deleted file mode 100644 index 6f6be39..0000000 --- a/src/suricatta/dsl.clj +++ /dev/null @@ -1,811 +0,0 @@ -;; Copyright (c) 2014-2015, Andrey Antukh -;; All rights reserved. -;; -;; Redistribution and use in source and binary forms, with or without -;; modification, are permitted provided that the following conditions are met: -;; -;; * Redistributions of source code must retain the above copyright notice, this -;; list of conditions and the following disclaimer. -;; -;; * Redistributions in binary form must reproduce the above copyright notice, -;; this list of conditions and the following disclaimer in the documentation -;; and/or other materials provided with the distribution. -;; -;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -(ns suricatta.dsl - "Sql building dsl" - (:refer-clojure :exclude [val group-by and or not name set update]) - (:require [suricatta.core :as core] - [suricatta.impl :as impl] - [suricatta.types :as types :refer [defer]]) - (:import org.jooq.SQLDialect - org.jooq.SelectJoinStep - org.jooq.InsertReturningStep - org.jooq.Row - org.jooq.TableLike - org.jooq.FieldLike - org.jooq.Field - org.jooq.Select - org.jooq.impl.DSL - org.jooq.impl.DefaultConfiguration - org.jooq.impl.DefaultDataType - org.jooq.impl.SQLDataType - org.jooq.util.postgres.PostgresDataType - org.jooq.util.mariadb.MariaDBDataType - org.jooq.util.mysql.MySQLDataType - suricatta.types.Context)) - -(def ^{:doc "Datatypes translation map" :dynamic true} - *datatypes* - {:pg/varchar PostgresDataType/VARCHAR - :pg/any PostgresDataType/ANY - :pg/bigint PostgresDataType/BIGINT - :pg/bigserial PostgresDataType/BIGSERIAL - :pg/boolean PostgresDataType/BOOLEAN - :pg/date PostgresDataType/DATE - :pg/decimal PostgresDataType/DECIMAL - :pg/real PostgresDataType/REAL - :pg/double PostgresDataType/DOUBLEPRECISION - :pg/int4 PostgresDataType/INT4 - :pg/int2 PostgresDataType/INT2 - :pg/int8 PostgresDataType/INT8 - :pg/integer PostgresDataType/INTEGER - :pg/serial PostgresDataType/SERIAL - :pg/serial4 PostgresDataType/SERIAL4 - :pg/serial8 PostgresDataType/SERIAL8 - :pg/smallint PostgresDataType/SMALLINT - :pg/text PostgresDataType/TEXT - :pg/time PostgresDataType/TIME - :pg/timetz PostgresDataType/TIMETZ - :pg/timestamp PostgresDataType/TIMESTAMP - :pg/timestamptz PostgresDataType/TIMESTAMPTZ - :pg/uuid PostgresDataType/UUID - :pg/char PostgresDataType/CHAR - :pg/bytea PostgresDataType/BYTEA - :pg/numeric PostgresDataType/NUMERIC - :pg/json PostgresDataType/JSON - :maria/bigint MariaDBDataType/BIGINT - :maria/ubigint MariaDBDataType/BIGINTUNSIGNED - :maria/binary MariaDBDataType/BINARY - :maria/blob MariaDBDataType/BLOB - :maria/bool MariaDBDataType/BOOL - :maria/boolean MariaDBDataType/BOOLEAN - :maria/char MariaDBDataType/CHAR - :maria/date MariaDBDataType/DATE - :maria/datetime MariaDBDataType/DATETIME - :maria/decimal MariaDBDataType/DECIMAL - :maria/double MariaDBDataType/DOUBLE - :maria/enum MariaDBDataType/ENUM - :maria/float MariaDBDataType/FLOAT - :maria/int MariaDBDataType/INT - :maria/integer MariaDBDataType/INTEGER - :maria/uint MariaDBDataType/INTEGERUNSIGNED - :maria/longtext MariaDBDataType/LONGTEXT - :maria/mediumint MariaDBDataType/MEDIUMINT - :maria/real MariaDBDataType/REAL - :maria/smallint MariaDBDataType/SMALLINT - :maria/time MariaDBDataType/TIME - :maria/timestamp MariaDBDataType/TIMESTAMP - :maria/varchar MariaDBDataType/VARCHAR - :mysql/bigint MySQLDataType/BIGINT - :mysql/ubigint MySQLDataType/BIGINTUNSIGNED - :mysql/binary MySQLDataType/BINARY - :mysql/blob MySQLDataType/BLOB - :mysql/bool MySQLDataType/BOOL - :mysql/boolean MySQLDataType/BOOLEAN - :mysql/char MySQLDataType/CHAR - :mysql/date MySQLDataType/DATE - :mysql/datetime MySQLDataType/DATETIME - :mysql/decimal MySQLDataType/DECIMAL - :mysql/double MySQLDataType/DOUBLE - :mysql/enum MySQLDataType/ENUM - :mysql/float MySQLDataType/FLOAT - :mysql/int MySQLDataType/INT - :mysql/integer MySQLDataType/INTEGER - :mysql/uint MySQLDataType/INTEGERUNSIGNED - :mysql/longtext MySQLDataType/LONGTEXT - :mysql/mediumint MySQLDataType/MEDIUMINT - :mysql/real MySQLDataType/REAL - :mysql/smallint MySQLDataType/SMALLINT - :mysql/time MySQLDataType/TIME - :mysql/timestamp MySQLDataType/TIMESTAMP - :mysql/varchar MySQLDataType/VARCHAR}) - -(defn- make-datatype - [{:keys [type] :as opts}] - (reduce (fn [dt [attname attvalue]] - (case attname - :length (.length dt attvalue) - :null (.nullable dt attvalue) - :precision (.precision dt attvalue) - dt)) - (clojure.core/or - (get *datatypes* type) - (DefaultDataType. - SQLDialect/DEFAULT - SQLDataType/OTHER - (clojure.core/name type))) - (into [] opts))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Protocols for constructors -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defprotocol ISortField - (-sort-field [_] "Sort field constructor")) - -(defprotocol ITable - (-table [_] "Table constructor.")) - -(defprotocol IField - (-field [_] "Field constructor.")) - -(defprotocol IName - (-name [_] "Name constructor (mainly used with CTE)")) - -(defprotocol ICondition - (-condition [_] "Condition constructor")) - -(defprotocol IVal - (-val [_] "Val constructor")) - -(defprotocol IDeferred - "Protocol mainly defined for uniform unwrapping - deferred queries." - (-unwrap [_] "Unwrap the object")) - -(defprotocol ITableCoerce - (-as-table [_ params] "Table coersion.")) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Protocol Implementations -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(extend-protocol IField - java.lang.String - (-field [v] - (DSL/field v)) - - clojure.lang.Keyword - (-field [v] - (DSL/field (clojure.core/name v))) - - clojure.lang.IPersistentList - (-field [v] - (let [[fname falias] v - falias (clojure.core/name falias)] - (-> (-field fname) - (.as falias)))) - - clojure.lang.IPersistentVector - (-field [v] - (let [[fname & params] v - fname (clojure.core/name fname) - params (into-array Object params)] - (DSL/field fname params))) - - org.jooq.FieldLike - (-field [v] (.asField v)) - - org.jooq.Field - (-field [v] v) - - org.jooq.impl.Val - (-field [v] v) - - suricatta.types.Deferred - (-field [t] - (-field @t))) - - -(extend-protocol ISortField - java.lang.String - (-sort-field ^org.jooq.SortField [s] - (-> (DSL/field s) - (.asc))) - - clojure.lang.Keyword - (-sort-field ^org.jooq.SortField [kw] - (-sort-field (clojure.core/name kw))) - - org.jooq.Field - (-sort-field ^org.jooq.SortField [f] (.asc f)) - - org.jooq.SortField - (-sort-field ^org.jooq.SortField [v] v) - - clojure.lang.PersistentVector - (-sort-field ^org.jooq.SortField [v] - (let [^org.jooq.Field field (-field (first v)) - ^org.jooq.SortField field (case (second v) - :asc (.asc field) - :desc (.desc field))] - (if (= (count v) 3) - (case (first (drop 2 v)) - :nulls-last (.nullsLast field) - :nulls-first (.nullsFirst field)) - field)))) - -(extend-protocol ITable - java.lang.String - (-table [s] - (DSL/table s)) - - clojure.lang.IPersistentList - (-table [pv] - (let [[tname talias] pv - talias (clojure.core/name talias)] - (-> (-table tname) - (.as talias)))) - - clojure.lang.Keyword - (-table [kw] - (-table (clojure.core/name kw))) - - org.jooq.Table - (-table [t] t) - - org.jooq.TableLike - (-table [t] (.asTable t)) - - suricatta.types.Deferred - (-table [t] @t)) - -(extend-protocol IName - java.lang.String - (-name [s] - (-> (into-array String [s]) - (DSL/name))) - - clojure.lang.Keyword - (-name [kw] (-name (clojure.core/name kw)))) - -(extend-protocol ICondition - java.lang.String - (-condition [s] (DSL/condition s)) - - org.jooq.Condition - (-condition [c] c) - - clojure.lang.PersistentVector - (-condition [v] - (let [sql (first v) - params (rest v)] - (->> (map -unwrap params) - (into-array Object) - (DSL/condition sql)))) - - suricatta.types.Deferred - (-condition [s] - (-condition @s))) - -(extend-protocol IVal - Object - (-val [v] (DSL/val v))) - -(extend-protocol IDeferred - nil - (-unwrap [v] v) - - Object - (-unwrap [self] - (impl/wrap-if-need self)) - - suricatta.types.Deferred - (-unwrap [self] - (-unwrap @self))) - -(extend-protocol ITableCoerce - org.jooq.Name - (-as-table [v selectexp] - (assert (instance? Select selectexp)) - (.as v selectexp)) - - org.jooq.DerivedColumnList - (-as-table [v selectexp] - (assert (instance? Select selectexp)) - (.as v selectexp))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Common DSL functions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn field - "Create a field instance." - ([v] - (-field v)) - ([v alias] - (-> (-field v) - (.as (clojure.core/name alias))))) - -(defn table - "Create a table instance." - ([v] - (-table v)) - ([v alias] - (-> (-table v) - (.as (clojure.core/name alias))))) - -(defn to-table - "Coerce querypart to table expression." - [texp talias & params] - (defer - (let [texp (-unwrap texp) - talias (-unwrap talias)] - (cond - (clojure.core/and (satisfies? ITableCoerce texp) - (instance? Select talias)) - (-as-table texp talias) - - (instance? TableLike texp) - (let [talias (clojure.core/name (-unwrap talias)) - params (into-array String (map clojure.core/name params))] - (.asTable texp talias params)))))) - -(defn f - "Create a field instance, specialized on - create function like field instance." - [v] - (-field v)) - -(defn typed-field - [data type] - (let [f (clojure.core/name data) - dt (get *datatypes* type)] - (DSL/field f dt))) - -(defn val - [v] - (-val v)) - -(defn select - "Start select statement." - [& fields] - (defer - (let [fields (map -unwrap fields)] - (cond - (instance? org.jooq.WithStep (first fields)) - (.select (first fields) - (->> (map -field (rest fields)) - (into-array org.jooq.Field))) - :else - (->> (map -field fields) - (into-array org.jooq.Field) - (DSL/select)))))) - -(defn select-distinct - "Start select statement." - [& fields] - (defer - (->> (map (comp field -unwrap) fields) - (into-array org.jooq.Field) - (DSL/selectDistinct)))) - -(defn select-from - "Helper for create select * from - statement directly (without specify fields)" - [t] - (-> (-table t) - (DSL/selectFrom))) - -(defn select-count - [] - (DSL/selectCount)) - -(defn select-one - [] - (defer (DSL/selectOne))) - -(defn select-zero - [] - (DSL/selectZero)) - -(defn from - "Creates from clause." - [f & tables] - (defer - (->> (map -table tables) - (into-array org.jooq.TableLike) - (.from @f)))) - -(defn join - "Create join clause." - [q t] - (defer - (let [q (-unwrap q) - t (-table (-unwrap t))] - (.join ^SelectJoinStep q t)))) - -(defn cross-join - [step tlike] - (defer - (let [t (-table (-unwrap tlike)) - step (-unwrap step)] - (.crossJoin step t)))) - -(defn full-outer-join - [step tlike] - (defer - (let [t (-table (-unwrap tlike)) - step (-unwrap step)] - (.fullOuterJoin step t)))) - -(defn left-outer-join - [step tlike] - (defer - (let [t (-table (-unwrap tlike)) - step (-unwrap step)] - (.leftOuterJoin step t)))) - -(defn right-outer-join - [step tlike] - (defer - (let [t (-table (-unwrap tlike)) - step (-unwrap step)] - (.rightOuterJoin step t)))) - -(defmulti on (comp class -unwrap first vector)) - -(defmethod on org.jooq.SelectOnStep - [step & clauses] - (defer - (->> (map -condition clauses) - (into-array org.jooq.Condition) - (.on (-unwrap step))))) - -(defmethod on org.jooq.TableOnStep - [step & clauses] - (defer - (->> (map -condition clauses) - (into-array org.jooq.Condition) - (.on (-unwrap step))))) - -(defn where - "Create where clause with variable number - of conditions (that are implicitly combined - with `and` logical operator)." - [q & clauses] - (defer - (->> (map -condition clauses) - (into-array org.jooq.Condition) - (.where @q)))) - -(defn exists - "Create an exists condition." - [q] - (defer - (DSL/exists @q))) - -(defn not-exists - "Create a not-exists condition." - [q] - (defer - (DSL/notExists @q))) - -(defn group-by - [q & fields] - (defer - (->> (map (comp -field -unwrap) fields) - (into-array org.jooq.GroupField) - (.groupBy @q)))) - -(defn having - "Create having clause with variable number - of conditions (that are implicitly combined - with `and` logical operator)." - [q & clauses] - (defer - (->> (map -condition clauses) - (into-array org.jooq.Condition) - (.having @q)))) - -(defn order-by - [q & clauses] - (defer - (->> (map -sort-field clauses) - (into-array org.jooq.SortField) - (.orderBy @q)))) - -(defn for-update - [q & fields] - (defer - (let [q (.forUpdate @q)] - (if (seq fields) - (->> (map -field fields) - (into-array org.jooq.Field) - (.of q)) - q)))) - -(defn limit - "Creates limit clause." - [q num] - (defer - (.limit @q num))) - -(defn offset - "Creates offset clause." - [q num] - (defer - (.offset @q num))) - -(defn union - [& clauses] - (defer - (reduce (fn [acc v] (.union acc @v)) - (-> clauses first deref) - (-> clauses rest)))) - -(defn union-all - [& clauses] - (defer - (reduce (fn [acc v] (.unionAll acc @v)) - (-> clauses first deref) - (-> clauses rest)))) - -(defn returning - [t & fields] - (defer - (if (= (count fields) 0) - (.returning @t) - (.returning @t (->> (map -field fields) - (into-array org.jooq.Field)))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Logical operators (for conditions) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn and - "Logican operator `and`." - [& conditions] - (let [conditions (map -condition conditions)] - (reduce (fn [acc v] (.and acc v)) - (first conditions) - (rest conditions)))) - -(defn or - "Logican operator `or`." - [& conditions] - (let [conditions (map -condition conditions)] - (reduce (fn [acc v] (.or acc v)) - (first conditions) - (rest conditions)))) - -(defn not - "Negate a condition." - [c] - (defer - (DSL/not (-condition c)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Common Table Expresions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn name - [v] - (defer - (-name v))) - -(defn with - "Create a WITH clause" - [& tables] - (defer - (->> (map -table tables) - (into-array org.jooq.CommonTableExpression) - (DSL/with)))) - -(defn with-fields - "Add a list of fields to this name to make this name a DerivedColumnList." - [n & fields] - (defer - (let [fields (->> (map clojure.core/name fields) - (into-array String))] - (.fields @n fields)))) - -(defmacro row - [& values] - `(DSL/row ~@(map (fn [x#] `(-unwrap ~x#)) values))) - -(defn values - [& rows] - (defer - (-> (into-array org.jooq.RowN rows) - (DSL/values)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Insert statement -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn insert-into - [t] - (defer - (DSL/insertInto (-table t)))) - -(defn insert-values - [t values] - (defer - (-> (fn [acc [k v]] - (if (nil? v) - acc - (.set acc (-field k) (-unwrap v)))) - (reduce (-unwrap t) values) - (.newRecord)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Update statement -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn update - "Returns empty UPDATE statement." - [t] - (defer - (DSL/update (-table t)))) - -(defn set - "Attach values to the UPDATE statement." - ([t kv] - {:pre [(map? kv)]} - (defer - (let [t (-unwrap t)] - (reduce-kv (fn [acc k v] - (let [k (-unwrap k) - v (-unwrap v)] - (.set acc (-field k) v))) - t kv)))) - ([t k v] - (defer - (let [v (-unwrap v) - k (-unwrap k) - t (-unwrap t)] - (if (clojure.core/and - (instance? org.jooq.Row k) - (clojure.core/or - (instance? org.jooq.Row v) - (instance? org.jooq.Select v))) - (.set t k v) - (.set t (-field k) v)))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Delete statement -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn delete - [t] - (defer - (DSL/delete (-table t)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; DDL -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn truncate - [t] - (defer - (DSL/truncate (-table t)))) - -(defn alter-table - "Creates new and empty alter table expression." - [name] - (defer - (-> (-unwrap name) - (-table) - (DSL/alterTable)))) - -(defn create-table - [name] - (defer - (-> (-unwrap name) - (-table) - (DSL/createTable)))) - -(defn drop-table - "Drop table statement constructor." - [t] - (defer - (DSL/dropTable (-table t)))) - -;; Columns functions -(defmulti add-column (comp class -unwrap first vector)) - -(defmethod add-column org.jooq.AlterTableStep - [step name & [{:keys [default] :as opts}]] - (defer - (let [step (-unwrap step) - name (-field name) - type (make-datatype opts) - step (.add step name type)] - (if default - (.setDefault step (-field default)) - step)))) - -(defmethod add-column org.jooq.CreateTableAsStep - [step name & [{:keys [default] :as opts}]] - (defer - (let [step (-unwrap step) - name (-field name) - type (cond-> (make-datatype opts) - default (.defaultValue default))] - (.column step name type)))) - -(defn alter-column - [step name & [{:keys [type default null length] :as opts}]] - (defer - (let [step (-> (-unwrap step) - (.alter (-field name)))] - (when (clojure.core/and (clojure.core/or null length) (clojure.core/not type)) - (throw (IllegalArgumentException. - "For change null or length you should specify type."))) - (when type - (.set step (make-datatype opts))) - (when default - (.defautValue step default)) - step))) - -(defn drop-column - "Drop column from alter table step." - [step name & [type]] - (defer - (let [step (-> (-unwrap step) - (.drop (-field name)))] - (case type - :cascade (.cascade step) - :restrict (.restrict step) - step)))) - -;; Index functions - -(defmethod on org.jooq.CreateIndexStep - [step table field & extrafields] - (defer - (let [fields (->> (concat [field] extrafields) - (map (comp -field -unwrap)) - (into-array org.jooq.Field))] - (.on (-unwrap step) (-table table) fields)))) - -(defn create-index - [indexname] - (defer - (let [indexname (clojure.core/name indexname)] - (DSL/createIndex indexname)))) - -(defn drop-index - [indexname] - (defer - (let [indexname (clojure.core/name indexname)] - (DSL/dropIndex indexname)))) - - -;; Sequence functions - -(defn create-sequence - [seqname] - (defer - (let [seqname (clojure.core/name seqname)] - (DSL/createSequence seqname)))) - -(defn alter-sequence - [seqname restart] - (defer - (let [seqname (clojure.core/name seqname) - step (DSL/alterSequence seqname)] - (if (true? restart) - (.restart step) - (.restartWith step restart))))) - -(defn drop-sequence - ([seqname] (drop-sequence seqname false)) - ([seqname ifexists] - (defer - (let [seqname (clojure.core/name seqname)] - (if ifexists - (DSL/dropSequenceIfExists seqname) - (DSL/dropSequence seqname)))))) diff --git a/src/suricatta/dsl/alpha.clj b/src/suricatta/dsl/alpha.clj new file mode 100644 index 0000000..0473f6b --- /dev/null +++ b/src/suricatta/dsl/alpha.clj @@ -0,0 +1,256 @@ +;; Copyright (c) 2019 Andrey Antukh +;; All rights reserved. +;; +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions are met: +;; +;; * Redistributions of source code must retain the above copyright notice, this +;; list of conditions and the following disclaimer. +;; +;; * Redistributions in binary form must reproduce the above copyright notice, +;; this list of conditions and the following disclaimer in the documentation +;; and/or other materials provided with the distribution. +;; +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +(ns suricatta.dsl.alpha + "A SQL build helpers." + (:refer-clojure :exclude [test update set format])) + +(alias 'core 'clojure.core) + +(defn- query? + [m] + (::query m)) + +(defn select + [] + {::query true + ::type ::select}) + +(defn update + ([table] + (update table nil)) + ([table alias] + {::query true + ::type ::update + ::table [table alias]})) + +(defn delete + [] + {::query true + ::type ::delete}) + +(defn insert + [table fields] + {::query true + ::table table + ::fields fields + ::type ::insert}) + +(defn from + ([m name] + (from m name nil)) + ([m name alias] + {:pre [(query? m)]} + (core/update m ::from (fnil conj []) [name alias]))) + +(defn field + ([m name] + (field m name nil)) + ([m name alias] + {:pre [(query? m)]} + (core/update m ::fields (fnil conj []) [name alias]))) + +(defn fields + [m & fields] + {:pre [(query? m)]} + (reduce (fn [acc item] + (if (vector? item) + (apply field acc item) + (field acc item))) + m + fields)) + +(defn limit + [m n] + {:pre [(= (::type m) ::select) + (query? m)]} + (assoc m ::limit n)) + +(defn offset + [m n] + {:pre [(= (::type m) ::select) + (query? m)]} + (assoc m ::offset n)) + +(defn- join* + [m type table alias condition] + {:pre [(= (::type m) ::select) + (query? m)]} + (core/update m ::joins (fnil conj []) + {:type type + :name table + :alias alias + :condition condition})) + +(defn join + ([m table condition] + (join m table nil condition)) + ([m table alias condition] + {:pre [(= (::type m) ::select) + (query? m)]} + (join* m :inner table alias condition))) + +(defn left-join + ([m table condition] + (left-join m table nil condition)) + ([m table alias condition] + {:pre [(= (::type m) ::select) + (query? m)]} + (join* m :left table alias condition))) + +(defn where + [m condition & params] + {:pre [(query? m)]} + (-> m + (core/update ::where (fnil conj []) condition) + (cond-> (seq params) + (core/update ::params (fnil into []) params)))) + +(defn set + [m field value] + {:pre [(query? m)]} + (-> m + (core/update ::assignations (fnil conj []) field) + (core/update ::params (fnil conj []) value))) + +(defn values + [m values] + {:pre [(query? m)]} + (-> m + (assoc ::values values) + (core/update ::params (fnil into []) (mapcat identity values)))) + +(defn raw + [m sql & params] + (-> m + (core/update ::raw (fnil conj []) sql) + (core/update ::params (fnil into []) params))) + +(defmulti format ::type) + +(defn fmt + [m] + (into [(format m)] (::params m))) + +;; --- Formating + +(defn- format-fields + [fields] + (letfn [(transform [[name alias]] + (if (string? alias) + (str name " " alias) + name))] + (apply str (->> (map transform fields) + (interpose ", "))))) + +(defn- format-join + [{:keys [type name alias condition]}] + (str (case type + :inner "INNER JOIN " + :left "LEFT JOIN ") + (if alias + (str name " " alias) + name) + " ON (" condition ")")) + +(defn- format-joins + [clauses] + (apply str (->> (map format-join clauses) + (interpose " ")))) + +(defn- format-where + [conditions] + (when (seq conditions) + (str "WHERE (" (apply str (interpose ") AND (" conditions)) ")"))) + + + +(defn- format-assignations + [assignations] + (apply str (->> (map #(str % " = ?") assignations) + (interpose ", ")))) + +(defn- format-raw + [items] + (when (seq items) + (apply str (interpose " " items)))) + +(defmethod format ::select + [{:keys [::fields ::from ::joins ::where]}] + (str "SELECT " + (format-fields fields) + " FROM " + (format-fields from) + " " + (format-joins joins) + " " + (format-where where))) + +(defmethod format ::update + [{:keys [::table ::assignations ::where]}] + (str "UPDATE " + (format-fields [table]) + " SET " + (format-assignations assignations) + " " + (format-where where))) + +(defmethod format ::delete + [{:keys [::from ::where]}] + (str "DELETE FROM " + (format-fields from) + " " + (format-where where))) + +(defmethod format ::insert + [{:keys [::table ::fields ::values ::raw]}] + (let [fsize (count fields) + pholder (str "(" (apply str (->> (map (constantly "?") fields) + (interpose ", "))) ")")] + + (str "INSERT INTO " table "(" (apply str (interpose ", " fields)) ")" + " VALUES " (apply str (->> (map (constantly pholder) values) + (interpose ", "))) + " " + (format-raw raw)))) + +;; (defn test-update +;; [] +;; (-> (update "users" "u") +;; (set "u.username" "foobar") +;; (set "u.email" "niwi@niwi.nz") +;; (where "u.id = ? AND u.deleted_at IS null" 555))) + +;; (defn test-delete +;; [] +;; (-> (delete) +;; (from "users" "u") +;; (where "u.id = ? AND u.deleted_at IS null" 555))) + +;; (defn test-insert +;; [] +;; (-> (insert "users" ["id", "username"]) +;; (values [[1 "niwinz"] [2 "niwibe"]]) +;; (raw "RETURNING *"))) + diff --git a/src/suricatta/dsl/pgsql.clj b/src/suricatta/dsl/pgsql.clj deleted file mode 100644 index 84c36c1..0000000 --- a/src/suricatta/dsl/pgsql.clj +++ /dev/null @@ -1,60 +0,0 @@ -;; Copyright (c) 2014-2015, Andrey Antukh -;; All rights reserved. -;; -;; Redistribution and use in source and binary forms, with or without -;; modification, are permitted provided that the following conditions are met: -;; -;; * Redistributions of source code must retain the above copyright notice, this -;; list of conditions and the following disclaimer. -;; -;; * Redistributions in binary form must reproduce the above copyright notice, -;; this list of conditions and the following disclaimer in the documentation -;; and/or other materials provided with the distribution. -;; -;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -(ns suricatta.dsl.pgsql - "PostgreSQL-specific DSL extensions" - (:require [suricatta.proto :as proto] - [suricatta.types :as types :refer [defer]]) - (:import org.jooq.util.postgres.PostgresDSL)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Protocol Implementations -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(extend-protocol proto/ISQLType - (Class/forName "[Ljava.lang.String;") - (-convert [self] - (into [] self))) - -(extend-protocol proto/ISQLType - (Class/forName "[Ljava.lang.Long;") - (-convert [self] - (into [] self))) - -(extend-protocol proto/ISQLType - java.sql.Array - (-convert [self] - (into [] (map proto/-convert) (.getArray self)))) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Common DSL functions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn array - "Convert an expression to an array." - [q] - (defer - (PostgresDSL/array @q))) - diff --git a/src/suricatta/format.clj b/src/suricatta/format.clj deleted file mode 100644 index 30b8ae6..0000000 --- a/src/suricatta/format.clj +++ /dev/null @@ -1,62 +0,0 @@ -;; Copyright (c) 2014-2017 Andrey Antukh -;; All rights reserved. -;; -;; Redistribution and use in source and binary forms, with or without -;; modification, are permitted provided that the following conditions are met: -;; -;; * Redistributions of source code must retain the above copyright notice, this -;; list of conditions and the following disclaimer. -;; -;; * Redistributions in binary form must reproduce the above copyright notice, -;; this list of conditions and the following disclaimer in the documentation -;; and/or other materials provided with the distribution. -;; -;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -(ns suricatta.format - "Sql formatting related code." - (:require [suricatta.core :as core] - [suricatta.proto :as proto] - [suricatta.types :as types] - [suricatta.impl :as impl]) - (:import org.jooq.Configuration - org.jooq.DSLContext - org.jooq.impl.DefaultConfiguration - org.jooq.impl.DSL)) - -(defn sql - "Renders a query sql into string." - ([q] - (proto/-sql q nil nil)) - ([q {:keys [type dialect] :or {type :indexed} :as opts}] - (proto/-sql q type dialect))) - -(def ^:deprecated get-sql - "Deprecated alias to get-sql." - sql) - -(defn bind-values - "Get bind values from query" - [q] - (proto/-bind-values q)) - -(def ^:deprecated get-bind-values - "Deprecated alias to bind-values." - bind-values) - -(defn sqlvec - "Get sql with bind values in a `sqlvec` format." - ([q] (sqlvec q nil)) - ([q opts] - (apply vector - (sql q opts) - (bind-values q)))) diff --git a/src/suricatta/impl.clj b/src/suricatta/impl.clj index ed88bf9..d4ad068 100644 --- a/src/suricatta/impl.clj +++ b/src/suricatta/impl.clj @@ -33,14 +33,19 @@ org.jooq.tools.jdbc.JDBCUtils org.jooq.SQLDialect org.jooq.DSLContext + org.jooq.QueryPart org.jooq.ResultQuery org.jooq.Query org.jooq.Field + org.jooq.Param org.jooq.Result org.jooq.Cursor org.jooq.RenderContext org.jooq.BindContext org.jooq.Configuration + org.jooq.util.postgres.PostgresDataType + org.jooq.util.mariadb.MariaDBDataType + org.jooq.util.mysql.MySQLDataType clojure.lang.PersistentVector java.net.URI java.util.Properties @@ -91,60 +96,19 @@ (or (= ptype ParamType/INLINED) (= ptype ParamType/NAMED_OR_INLINED)))) -(extend-protocol proto/IParamContext - RenderContext - (-statement [_] nil) - (-next-bind-index [_] nil) - (-inline? [it] (render-inline? it)) - - BindContext - (-statement [it] (.statement it)) - (-next-bind-index [it] (.nextIndex it)) - (-inline? [it] (render-inline? it))) - -(def ^:private param-adapter - (reify suricatta.impl.IParam - (render [_ value ^RenderContext ctx] - (when-let [sql (proto/-render value ctx)] - (.sql ctx sql))) - (bind [_ value ^BindContext ctx] - (proto/-bind value ctx)))) - -(extend-protocol proto/IRenderer - org.jooq.Query - (-sql [q type dialect] - (let [^Configuration conf (DefaultConfiguration.) - ^DSLContext context (DSL/using conf)] - (when dialect - (.set conf (translate-dialect dialect))) - (condp = type - nil (.render context q) - :named (.renderNamedParams context q) - :indexed (.render context q) - :inlined (.renderInlined context q)))) - - (-bind-values [q] - (let [^Configuration conf (DefaultConfiguration.) - ^DSLContext context (DSL/using conf)] - (into [] (.extractBindValues context q)))) - - suricatta.types.Deferred - (-sql [self type dialect] - (proto/-sql @self type dialect)) - - (-bind-values [self] - (proto/-bind-values @self))) - -(defn make-param-impl - "Wraps a value that implements IParamType - protocol in valid jOOQ Param implementation." - [value] - (suricatta.impl.ParamWrapper. param-adapter value)) +(defn sql->param + [sql & parts] + (letfn [(wrap-if-need [item] + (if (instance? Param item) + item + (DSL/val item)))] + (DSL/field sql (->> (map wrap-if-need parts) + (into-array QueryPart))))) (defn wrap-if-need - [obj] - (if (satisfies? proto/IParamType obj) - (make-param-impl obj) + [ctx obj] + (if (satisfies? proto/IParam obj) + (proto/-param obj ctx) obj)) (defn- querystring->map @@ -282,15 +246,11 @@ clojure.lang.PersistentVector (-execute [^PersistentVector sqlvec ^Context ctx] (let [^DSLContext context (proto/-context ctx) - ^Query query (->> (map wrap-if-need (rest sqlvec)) + ^Query query (->> (map (partial wrap-if-need context) (rest sqlvec)) (into-array Object) (.query context (first sqlvec)))] (.execute context query))) - suricatta.types.Deferred - (-execute [deferred ctx] - (proto/-execute @deferred ctx)) - suricatta.types.Query (-execute [query ctx] (let [^DSLContext context (if (nil? ctx) @@ -346,7 +306,7 @@ PersistentVector (-fetch [^PersistentVector sqlvec ^Context ctx opts] (let [^DSLContext context (proto/-context ctx) - ^ResultQuery query (->> (into-array Object (map wrap-if-need (rest sqlvec))) + ^ResultQuery query (->> (into-array Object (map (partial wrap-if-need context) (rest sqlvec))) (.resultQuery context (first sqlvec)))] (-> (.fetch context query) (result->vector opts)))) @@ -357,20 +317,6 @@ (-> (.fetch context query) (result->vector opts)))) - org.jooq.Query - (-fetch [^Query query ^Context ctx opts] - (let [^DSLContext context (proto/-context ctx) - ^Configuration config (proto/-config ctx) - ^SQLDialect dialect (.dialect config) - sqlvec (apply vector - (proto/-sql query :indexed dialect) - (proto/-bind-values query))] - (proto/-fetch sqlvec ctx opts))) - - suricatta.types.Deferred - (-fetch [deferred ctx opts] - (proto/-fetch @deferred ctx opts)) - suricatta.types.Query (-fetch [query ctx opts] (let [^DSLContext context (if (nil? ctx) @@ -404,11 +350,7 @@ (-fetch-lazy [^ResultQuery query ^Context ctx opts] (let [^DSLContext context (proto/-context ctx)] (.fetchSize query (get opts :fetch-size 100)) - (.fetchLazy context query))) - - suricatta.types.Deferred - (-fetch-lazy [deferred ctx opts] - (proto/-fetch-lazy @deferred ctx opts))) + (.fetchLazy context query)))) (defn cursor->lazyseq [^Cursor cursor {:keys [format mapfn] :or {format :record}}] @@ -448,6 +390,88 @@ ;; Load into implementation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def ^{:doc "Datatypes translation map" :dynamic true} + *datatypes* + {:pg/varchar PostgresDataType/VARCHAR + :pg/any PostgresDataType/ANY + :pg/bigint PostgresDataType/BIGINT + :pg/bigserial PostgresDataType/BIGSERIAL + :pg/boolean PostgresDataType/BOOLEAN + :pg/date PostgresDataType/DATE + :pg/decimal PostgresDataType/DECIMAL + :pg/real PostgresDataType/REAL + :pg/double PostgresDataType/DOUBLEPRECISION + :pg/int4 PostgresDataType/INT4 + :pg/int2 PostgresDataType/INT2 + :pg/int8 PostgresDataType/INT8 + :pg/integer PostgresDataType/INTEGER + :pg/serial PostgresDataType/SERIAL + :pg/serial4 PostgresDataType/SERIAL4 + :pg/serial8 PostgresDataType/SERIAL8 + :pg/smallint PostgresDataType/SMALLINT + :pg/text PostgresDataType/TEXT + :pg/time PostgresDataType/TIME + :pg/timetz PostgresDataType/TIMETZ + :pg/timestamp PostgresDataType/TIMESTAMP + :pg/timestamptz PostgresDataType/TIMESTAMPTZ + :pg/uuid PostgresDataType/UUID + :pg/char PostgresDataType/CHAR + :pg/bytea PostgresDataType/BYTEA + :pg/numeric PostgresDataType/NUMERIC + :pg/json PostgresDataType/JSON + :maria/bigint MariaDBDataType/BIGINT + :maria/ubigint MariaDBDataType/BIGINTUNSIGNED + :maria/binary MariaDBDataType/BINARY + :maria/blob MariaDBDataType/BLOB + :maria/bool MariaDBDataType/BOOL + :maria/boolean MariaDBDataType/BOOLEAN + :maria/char MariaDBDataType/CHAR + :maria/date MariaDBDataType/DATE + :maria/datetime MariaDBDataType/DATETIME + :maria/decimal MariaDBDataType/DECIMAL + :maria/double MariaDBDataType/DOUBLE + :maria/enum MariaDBDataType/ENUM + :maria/float MariaDBDataType/FLOAT + :maria/int MariaDBDataType/INT + :maria/integer MariaDBDataType/INTEGER + :maria/uint MariaDBDataType/INTEGERUNSIGNED + :maria/longtext MariaDBDataType/LONGTEXT + :maria/mediumint MariaDBDataType/MEDIUMINT + :maria/real MariaDBDataType/REAL + :maria/smallint MariaDBDataType/SMALLINT + :maria/time MariaDBDataType/TIME + :maria/timestamp MariaDBDataType/TIMESTAMP + :maria/varchar MariaDBDataType/VARCHAR + :mysql/bigint MySQLDataType/BIGINT + :mysql/ubigint MySQLDataType/BIGINTUNSIGNED + :mysql/binary MySQLDataType/BINARY + :mysql/blob MySQLDataType/BLOB + :mysql/bool MySQLDataType/BOOL + :mysql/boolean MySQLDataType/BOOLEAN + :mysql/char MySQLDataType/CHAR + :mysql/date MySQLDataType/DATE + :mysql/datetime MySQLDataType/DATETIME + :mysql/decimal MySQLDataType/DECIMAL + :mysql/double MySQLDataType/DOUBLE + :mysql/enum MySQLDataType/ENUM + :mysql/float MySQLDataType/FLOAT + :mysql/int MySQLDataType/INT + :mysql/integer MySQLDataType/INTEGER + :mysql/uint MySQLDataType/INTEGERUNSIGNED + :mysql/longtext MySQLDataType/LONGTEXT + :mysql/mediumint MySQLDataType/MEDIUMINT + :mysql/real MySQLDataType/REAL + :mysql/smallint MySQLDataType/SMALLINT + :mysql/time MySQLDataType/TIME + :mysql/timestamp MySQLDataType/TIMESTAMP + :mysql/varchar MySQLDataType/VARCHAR}) + +(defn typed-field + [data type] + (let [f (clojure.core/name data) + dt (get *datatypes* type)] + (DSL/field f dt))) + (defn load-into [ctx tablename data {:keys [format commit fields ignore-rows nullstring quotechar separator] diff --git a/src/suricatta/proto.clj b/src/suricatta/proto.clj index 2dba0d1..9e4299f 100644 --- a/src/suricatta/proto.clj +++ b/src/suricatta/proto.clj @@ -40,28 +40,11 @@ (defprotocol IFetchLazy (-fetch-lazy [q ctx opts] "Fetch lazy results executing query.")) -(defprotocol IRenderer - (-sql [_ type dialect] "Render a query sql into a string.") - (-bind-values [_] "Get query bind values.")) - (defprotocol IQuery (-query [_ ctx] "Build a query.")) -;; Custom data types binding protocols - -(defprotocol IParamContext - "A lightweight abstraction for access - to the basic properties on the render/bind - context instances." - (-statement [_] "Get the prepared statement if it is awailable.") - (-next-bind-index [_] "Get the next bind index (WARN: side effectful)") - (-inline? [_] "Return true in case the context is setup for inline.")) - -(defprotocol IParamType - "A basic abstraction for adapt user defined - types to work within suricatta." - (-render [_ ctx] "Render the value as sql.") - (-bind [_ ctx] "Bind param value to the prepared statement.")) +(defprotocol IParam + (-param [_ ctx] "Returns a jOOQ compatible param type.")) (defprotocol ISQLType "An abstraction for handle the backward type diff --git a/src/suricatta/types.clj b/src/suricatta/types.clj index e99e0f5..9977df4 100644 --- a/src/suricatta/types.clj +++ b/src/suricatta/types.clj @@ -1,4 +1,4 @@ -;; Copyright (c) 2014-2015, Andrey Antukh +;; Copyright (c) 2014-2019 Andrey Antukh ;; All rights reserved. ;; ;; Redistribution and use in source and binary forms, with or without @@ -52,27 +52,6 @@ [ctx] (instance? Context ctx)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Deferred Computation (without caching the result unlike delay) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(deftype Deferred [^clojure.lang.IFn func] - clojure.lang.IDeref - (deref [_] (func))) - -(defn ->deferred - [o] - (Deferred. o)) - -(defmacro defer - [& body] - `(let [func# (fn [] ~@body)] - (->deferred func#))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (deftype Query [^ResultQuery query ^Configuration conf] java.io.Closeable (close [_] diff --git a/test/suricatta/core_test.clj b/test/suricatta/core_test.clj index bca9ead..6b340c5 100644 --- a/test/suricatta/core_test.clj +++ b/test/suricatta/core_test.clj @@ -1,8 +1,6 @@ (ns suricatta.core-test (:require [clojure.test :refer :all] - [suricatta.core :as sc] - [suricatta.dsl :as dsl] - [suricatta.format :refer [get-sql get-bind-values sqlvec] :as fmt]) + [suricatta.core :as sc]) (:import org.jooq.impl.DSL org.jooq.util.postgres.PostgresDataType)) @@ -10,7 +8,9 @@ :subname "mem:"}) (def pgdbspec {:subprotocol "postgresql" - :subname "//127.0.0.1/test"}) + :subname "//127.0.0.1:5433/test" + :user "test" + :password "test"}) (def ^:dynamic *ctx*) @@ -52,14 +52,6 @@ (is (= (sc/fetch q) [{:x 1}])) (is (= (sc/execute q) 1)) (is (= (sc/execute q) 1)))) - - (testing "Fetch from insert statement." - (sc/execute *ctx* "create temporary table foo (n int) on commit drop") - (let [op (-> (dsl/insert-into :foo) - (dsl/insert-values {:n 1}) - (dsl/returning :n)) - result (sc/fetch-one *ctx* op)] - (is (= result {:n 1})))) ) (deftest lazy-fetch @@ -76,7 +68,7 @@ (is (= (vec res) [{:x 1} {:x 2} {:x 3}])))))) ) -(deftest fetch-format +#_(deftest fetch-format (testing "Fetch in csv format" (let [sql "select x, x+1 as i, 'a,b' as k from generate_series(1, 1) as x" result (sc/fetch *ctx* sql {:format :csv})] @@ -97,8 +89,8 @@ (testing "load csv" (sc/execute *ctx* "create table foo1 (a int, b int)") (let [data (str "1,2\n3,4\n")] - (sc/load-into *ctx* :foo1 data {:fields [(dsl/typed-field "a" :pg/int4) - (dsl/typed-field "b" :pg/int4)] + (sc/load-into *ctx* :foo1 data {:fields [(sc/typed-field "a" :pg/int4) + (sc/typed-field "b" :pg/int4)] :format :csv})) (let [result (sc/fetch *ctx* "select * from foo1")] (is (= [{:a 1, :b 2} {:a 3, :b 4}] result))))) @@ -145,16 +137,3 @@ (let [result (sc/fetch ctx "select * from foo")] (is (= 0 (count result)))))) ) - - -(deftest formatting - (testing "sqlvec properly handles dialect." - (let [op (-> (dsl/insert-into :users) - (dsl/insert-values {:username "foobar"}) - (dsl/returning :id)) - result1 (fmt/sqlvec op) - result2 (fmt/sqlvec op {:dialect :postgresql})] - (is (= result1 ["insert into users (username) values (?)" "foobar"])) - (is (= result2 ["insert into users (username) values (?) returning id" - "foobar"]))))) - diff --git a/test/suricatta/dsl_test.clj b/test/suricatta/dsl_test.clj index 7da63fb..d58725e 100644 --- a/test/suricatta/dsl_test.clj +++ b/test/suricatta/dsl_test.clj @@ -1,487 +1,30 @@ (ns suricatta.dsl-test (:require [clojure.test :refer :all] - [suricatta.core :refer :all] - [suricatta.dsl :as dsl] - [suricatta.dsl.pgsql :as pgsql] - [suricatta.format :as fmt])) - -(def dbspec {:subprotocol "h2" - :subname "mem:"}) - -(deftest rendering-dialect - (testing "Default dialect." - (let [q (dsl/select :id :name)] - (is (= (fmt/sql q) "select id, name")))) - - (testing "Specify concrete dialect" - (let [q (dsl/select :id :name)] - (is (= (fmt/sql q {:dialect :pgsql}) - "select id, name"))))) - -(deftest dsl-fetch-and-execute - (testing "Fetch using query builded with dsl" - (with-open [ctx (context dbspec)] - (let [q (dsl/select-one)] - (is (= (fetch ctx q) - [{:one 1}]))))) - - (testing "Execute using query builded with dsl" - (with-open [ctx (context dbspec)] - (execute ctx "create table foo (id integer)") - (let [r (execute ctx (dsl/truncate :foo))] - (is (= r 0)))))) - -(deftest dsl-select-clause - (testing "Basic select clause" - (let [q (-> (dsl/select :id :name) - (dsl/from :books) - (dsl/where ["books.id = ?" 2])) - sql (fmt/sql q) - bv (fmt/get-bind-values q)] - (is (= sql "select id, name from books where (books.id = ?)")) - (is (= bv [2])))) - - (testing "Select clause with field as condition and alias" - (let [q (-> (dsl/select '("foo > 5" "bar")) - (dsl/from "baz"))] - (is (= (fmt/sql q) - "select foo > 5 \"bar\" from baz")))) - - (testing "Select clause with count(*) expresion" - (let [q (-> (dsl/select '("count(*)" "count")) - (dsl/from "baz"))] - (is (= (fmt/sql q) - "select count(*) \"count\" from baz")))) - - (testing "Select with two tables in from clause 2" - (let [q (-> (dsl/select-one) - (dsl/from '("table1" "foo") - '("table2" "bar")))] - (is (= (fmt/sql q) - "select 1 \"one\" from table1 \"foo\", table2 \"bar\"")))) - - (testing "Select clause with join" - (let [q (-> (dsl/select-one) - (dsl/from "book") - (dsl/join "author") - (dsl/on "book.authorid = book.id"))] - (is (= (fmt/sql q) - "select 1 \"one\" from book join author on (book.authorid = book.id)")))) - - (testing "Select clause with join and aliases" - (let [q (-> (dsl/select-one) - (dsl/from '("book" "b")) - (dsl/join '("author" "a")) - (dsl/on "b.authorid = a.id"))] - (is (= (fmt/sql q) - "select 1 \"one\" from book \"b\" join author \"a\" on (b.authorid = a.id)")))) - - (testing "Select clause with join on table" - (let [q (-> (dsl/select-one) - (dsl/from (-> (dsl/table "book") - (dsl/join "author") - (dsl/on "book.authorid = book.id"))))] - (is (= (fmt/sql q) - "select 1 \"one\" from book join author on (book.authorid = book.id)")))) - - (testing "Select clause with where" - (let [q (-> (dsl/select-one) - (dsl/from "book") - (dsl/where ["book.age > ?" 100] - ["book.in_store is ?", true]))] - (is (= (fmt/sql q) - "select 1 \"one\" from book where ((book.age > ?) and (book.in_store is ?))")))) - - (testing "Where with nil argument" - (let [q (-> (dsl/select "name") - (dsl/from "book") - (dsl/where ["author_id = coalesce(?, 123)" nil]))] - (is (= (fmt/sql q) - "select name from book where (author_id = coalesce(?, 123))")))) - - (testing "Select clause with group by" - (let [q (-> (dsl/select (dsl/field "authorid") - (dsl/field "count(*)")) - (dsl/from "book") - (dsl/group-by (dsl/field "authorid")))] - (is (= (fmt/sql q) - "select authorid, count(*) from book group by authorid")))) - - (testing "Select clause with group by with having" - (let [q (-> (dsl/select (dsl/field "authorid") - (dsl/field "count(*)")) - (dsl/from "book") - (dsl/group-by (dsl/field "authorid")) - (dsl/having ["count(*) > ?", 2]))] - (is (= (fmt/sql q) - "select authorid, count(*) from book group by authorid having (count(*) > ?)")))) - - (testing "Select clause with order by without explicit order" - (let [q (-> (dsl/select :name) - (dsl/from "book") - (dsl/order-by :name))] - (is (= (fmt/sql q) - "select name from book order by name asc")))) - - (testing "Select clause with order by with explicit order" - (let [q (-> (dsl/select :name) - (dsl/from "book") - (dsl/order-by [:name :desc]))] - (is (= (fmt/sql q) - "select name from book order by name desc")))) - - (testing "Select clause with order by with explicit order by index" - (let [q (-> (dsl/select :id :name) - (dsl/from "book") - (dsl/order-by ["1" :desc] - ["2" :asc]))] - (is (= (fmt/sql q) - "select id, name from book order by 1 desc, 2 asc")))) - - (testing "Select clause with order by with explicit order with nulls" - (let [q (-> (dsl/select :name) - (dsl/from "book") - (dsl/order-by [:name :desc :nulls-last]))] - (is (= (fmt/sql q) - "select name from book order by name desc nulls last")))) - - (testing "select with limit and offset" - (let [q (-> (dsl/select :name) - (dsl/from :book) - (dsl/limit 10) - (dsl/offset 100))] - (is (= (fmt/sql q) - "select name from book limit ? offset ?")))) - - (testing "select with for update without fields" - (let [q (-> (dsl/select :name) - (dsl/from :book) - (dsl/for-update))] - (is (= (fmt/sql q) - "select name from book for update")))) - - (testing "select with for update with fields" - (let [q (-> (dsl/select :name) - (dsl/from :book) - (dsl/for-update :name))] - (is (= (fmt/sql q) - "select name from book for update of name")))) - - (testing "union two selects" - (let [q (dsl/union - (-> (dsl/select :name) - (dsl/from :books)) - (-> (dsl/select :name) - (dsl/from :articles)))] - (is (= (fmt/sql q) - "(select name from books) union (select name from articles)")))) - - (testing "union all two selects" - (let [q (dsl/union-all - (-> (dsl/select :name) - (dsl/from :books)) - (-> (dsl/select :name) - (dsl/from :articles)))] - (is (= (fmt/sql q) - "(select name from books) union all (select name from articles)")))) -) - -(deftest dsl-join - (testing "cross-join" - (let [q (-> (dsl/select-one) - (dsl/from :book) - (dsl/cross-join :article))] - (is (= (fmt/sql q) - "select 1 \"one\" from book cross join article")))) - - (testing "dsl-full-outer-join" - (let [q (-> (dsl/select-one) - (dsl/from :book) - (dsl/full-outer-join :article) - (dsl/on "article.id = book.id"))] - (is (= (fmt/sql q) - "select 1 \"one\" from book full outer join article on (article.id = book.id)")))) - - (testing "dsl-left-outer-join" - (let [q (-> (dsl/select-one) - (dsl/from :book) - (dsl/left-outer-join :article) - (dsl/on "article.id = book.id"))] - (is (= (fmt/sql q) - "select 1 \"one\" from book left outer join article on (article.id = book.id)")))) - - (testing "dsl-right-outer-join" - (let [q (-> (dsl/select-one) - (dsl/from :book) - (dsl/right-outer-join :article) - (dsl/on "article.id = book.id"))] - (is (= (fmt/sql q) - "select 1 \"one\" from book right outer join article on (article.id = book.id)")))) -) - -(deftest dsl-table-expressions - (testing "Values table expression" - (let [q (-> (dsl/select :f1 :f2) - (dsl/from - (-> (dsl/values - (dsl/row 1 2) - (dsl/row 3 4)) - (dsl/to-table "t1" "f1" "f2"))))] - (is (= (fmt/sql q {:dialect :pgsql}) - "select f1, f2 from (values(?, ?), (?, ?)) as \"t1\"(\"f1\", \"f2\")")))) - - (testing "Nested select in condition clause" - (let [q (-> (dsl/select) - (dsl/from :book) - (dsl/where ["book.age = ({0})" (dsl/select-one)]))] - (is (= (fmt/sql q {:dialect :pgsql}) - "select * from book where (book.age = (select 1 as \"one\"))")))) - - (testing "Nested select in from clause" - (let [q (-> (dsl/select) - (dsl/from (-> (dsl/select :f1) - (dsl/from :t1) - (dsl/to-table "tt1" "f1"))))] - (is (= (fmt/sql q {:dialect :pgsql}) - "select \"tt1\".\"f1\" from (select f1 from t1) as \"tt1\"(\"f1\")")))) - - (testing "Nested select in select fields" - (let [sq (-> (dsl/select (dsl/field "count(*)")) - (dsl/from :book) - (dsl/where "book.authorid = author.id")) - q (-> (dsl/select :fullname, (dsl/field sq "books")) - (dsl/from :author))] - (is (= (fmt/sql q) - "select fullname, (select count(*) from book where (book.authorid = author.id)) \"books\" from author")))) - - (testing "Nested select returned as array" - (let [sq (-> (dsl/select (dsl/field :id)) - (dsl/from :book) - (dsl/where "book.authorid = author.id")) - q (-> (dsl/select :fullname, (dsl/field (pgsql/array sq) "books")) - (dsl/from :author))] - (is (= (fmt/sql q) - "select fullname, array(select id from book where (book.authorid = author.id)) \"books\" from author")))) - - (testing "Nested select in where clause using exists" - (let [q (-> (dsl/select :fullname) - (dsl/from :author) - (dsl/where (dsl/exists (-> (dsl/select :id) - (dsl/from :table) - (dsl/where ["table.author_id = author.id"])))))] - (is (= (fmt/sql q) - "select fullname from author where exists (select id from table where (table.author_id = author.id))")))) - - (testing "Nested select in where clause using not-exists" - (let [q (-> (dsl/select :fullname) - (dsl/from :author) - (dsl/where (dsl/not-exists (-> (dsl/select :id) - (dsl/from :table) - (dsl/where ["table.author_id = author.id"])))))] - (is (= (fmt/sql q) - "select fullname from author where not exists (select id from table where (table.author_id = author.id))"))))) - -(deftest dsl-insert - (testing "Insert statement from values as maps" - (let [q (-> (dsl/insert-into :t1) - (dsl/insert-values {:f1 1 :f2 3}) - (dsl/insert-values {:f1 2 :f2 4}))] - (is (= (fmt/sql q {:dialect :pgsql}) - "insert into t1 (f1, f2) values (?, ?), (?, ?)")))) - (testing "Insert statement with nil values" - (let [q (-> (dsl/insert-into :t1) - (dsl/insert-values {:f1 1 :f2 nil :f3 2}))] - (is (= (fmt/sql q {:dialect :pgsql}) - "insert into t1 (f1, f3) values (?, ?)")))) - (testing "Insert statement from values as maps with returning" - (let [q (-> (dsl/insert-into :t1) - (dsl/insert-values {:f1 1 :f2 3}) - (dsl/insert-values {:f1 2 :f2 4}) - (dsl/returning :f1))] - (is (= (fmt/sql q {:dialect :pgsql}) - "insert into t1 (f1, f2) values (?, ?), (?, ?) returning f1"))))) - -(deftest dsl-update - (testing "Update statement without condition using map" - (let [q (-> (dsl/update :t1) - (dsl/set (sorted-map :id 2 :name "foo")))] - (is (= (fmt/sql q) - "update t1 set id = ?, name = ?")))) - - (testing "Update statement without condition" - (let [q (-> (dsl/update :t1) - (dsl/set :id 2) - (dsl/set :name "foo"))] - (is (= (fmt/sql q) - "update t1 set id = ?, name = ?")))) - - (testing "Update statement without condition and using function" - (let [q (-> (dsl/update :t1) - (dsl/set :val (dsl/f ["concat(val, ?)" "bar"])))] - (is (= (fmt/sqlvec q) - ["update t1 set val = concat(val, ?)" "bar"])))) - - (testing "Update statement with condition" - (let [q (-> (dsl/update :t1) - (dsl/set :name "foo") - (dsl/where ["id = ?" 2]))] - (is (= (fmt/sql q) - "update t1 set name = ? where (id = ?)")))) - - (testing "Update statement with subquery" - (let [q (-> (dsl/update :t1) - (dsl/set :f1 (-> (dsl/select :f2) - (dsl/from :t2) - (dsl/where ["id = ?" 2]))))] - (is (= (fmt/sql q {:dialect :pgsql}) - "update t1 set f1 = (select f2 from t2 where (id = ?))")))) - - (testing "Update statement with subquery with two fields" - (let [q (-> (dsl/update :t1) - (dsl/set (dsl/row (dsl/field :f1) - (dsl/field :f2)) - (-> (dsl/select :f3 :f4) - (dsl/from :t2) - (dsl/where ["id = ?" 2]))))] - (is (= (fmt/sql q {:dialect :pgsql}) - "update t1 set (f1, f2) = (select f3, f4 from t2 where (id = ?))")))) - - (testing "Update statement with returning clause" - (let [q (-> (dsl/update :t1) - (dsl/set :f1 2) - (dsl/returning :id))] - (is (= (fmt/sql q {:dialect :pgsql}) - "update t1 set f1 = ? returning id")))) -) - -(deftest dsl-delete - (testing "Delete statement" - (let [q (-> (dsl/delete :t1) - (dsl/where "id = 1"))] - (is (= (fmt/sql q) - "delete from t1 where (id = 1)"))))) - -(deftest dsl-common-table-expressions - (testing "Common table expressions" - (let [ - ;; cte1 (-> (dsl/name :t1) - ;; (dsl/with-fields :f1 :f2) - ;; (dsl/to-table (dsl/select (dsl/val 1) (dsl/val "a")))) - ;; cte2 (-> (dsl/name :t2) - ;; (dsl/with-fields :f1 :f2) - ;; (dsl/to-table (dsl/select (dsl/val 2) (dsl/val "b")))) - ;; q1 (-> (dsl/with cte1 cte2) - ;; (dsl/select (dsl/field "t1.f2")) - ;; (dsl/from :t1 :t2)) - - ;; Same as previous code but less verbose. - q (-> (dsl/with - (-> (dsl/name :t1) - (dsl/with-fields :f1 :f2) - (dsl/to-table (dsl/select (dsl/val 1) (dsl/val "a")))) - (-> (dsl/name :t2) - (dsl/with-fields :f1 :f2) - (dsl/to-table (dsl/select (dsl/val 2) (dsl/val "b"))))) - (dsl/select (dsl/field "t1.f2")) - (dsl/from :t1 :t2)) - sql (fmt/sql q {:type :inlined :dialect :pgsql}) - esql (str "with \"t1\"(\"f1\", \"f2\") as (select 1, 'a'), " - "\"t2\"(\"f1\", \"f2\") as (select 2, 'b') " - "select t1.f2 from t1, t2")] - (is (= sql esql)))) -) - -(deftest dsl-ddl - (testing "Truncate table" - (let [q (dsl/truncate :table1)] - (is (= (fmt/sql q) - "truncate table table1")))) - - (testing "Alter table with add column" - (let [q (-> (dsl/alter-table :t1) - (dsl/add-column :title {:type :pg/varchar :length 2 :null false}))] - (is (= (fmt/sql q) - "alter table t1 add title varchar(2) not null")))) - - (testing "Create table with add column" - (let [q (-> (dsl/create-table :t1) - (dsl/add-column :title {:type :pg/varchar :length 2 :null false}))] - (is (= (fmt/sql q) - "create table t1(title varchar(2) not null)")))) - - (testing "Alter table with set new datatype" - (let [q (-> (dsl/alter-table :t1) - (dsl/alter-column :title {:type :pg/varchar :length 100}))] - (is (= (fmt/sql q) - "alter table t1 alter title varchar(100)")))) - - (testing "Alter table with drop column" - (let [q (-> (dsl/alter-table :t1) - (dsl/drop-column :title :cascade))] - (is (= (fmt/sql q) - "alter table t1 drop title cascade")))) - - (testing "Create index support" - (let [q (-> (dsl/create-index "test") - (dsl/on :t1 :title))] - (is (= (fmt/sql q) - "create index \"test\" on t1(title)")))) - - (testing "Create index with expressions support" - (let [q (-> (dsl/create-index "test") - (dsl/on :t1 (dsl/field "lower(title)")))] - (is (= (fmt/sql q) - "create index \"test\" on t1(lower(title))")))) - - (testing "Drop index" - (let [q (dsl/drop-index :test)] - (is (= (fmt/sql q) - "drop index \"test\"")))) - - (testing "Create sequence" - (let [q (dsl/create-sequence "testseq")] - (is (= (fmt/sql q {:dialect :pgsql}) - "create sequence \"testseq\"")))) - - (testing "Alter sequence" - (let [q (dsl/alter-sequence "testseq" true)] - (is (= (fmt/sql q {:dialect :pgsql}) - "alter sequence \"testseq\" restart")))) - - (testing "Alter sequence with specific number" - (let [q (dsl/alter-sequence "testseq" 19)] - (is (= (fmt/sql q {:dialect :pgsql}) - "alter sequence \"testseq\" restart with 19")))) - - (testing "Drop sequence" - (let [q (dsl/drop-sequence :test)] - (is (= (fmt/sql q {:dialect :pgsql}) - "drop sequence \"test\"")))) - - (testing "Drop sequence if exists" - (let [q (dsl/drop-sequence :test true)] - (is (= (fmt/sql q {:dialect :pgsql}) - "drop sequence if exists \"test\"")))) - - (testing "Drop table" - (let [q (dsl/drop-table :t1)] - (is (= (fmt/sql q) - "drop table t1")))) -) - -(deftest dsl-not - (testing "boolean negation" - (let [q (-> - (dsl/select :name) - (dsl/from :book) - (dsl/where (dsl/not "new")))] - (is (= (fmt/sql q) - "select name from book where not((new))")))) - - (testing "negation of vector condition" - (let [q (-> - (dsl/select :name) - (dsl/from :book) - (dsl/where (dsl/not ["title = ?" "test"])))] - (is (= (fmt/sql q) - "select name from book where not((title = ?))"))))) + [suricatta.core :as sc] + [suricatta.dsl.alpha :as dsl])) + +(deftest select-statement-1 + (let [qq (-> (dsl/select) + (dsl/from "posts" "p") + (dsl/join "authors" "a" "p.author_id = a.id") + (dsl/field "p.*") + (dsl/field "a.slug" "author_slug") + (dsl/limit 10))] + (is (= (dsl/fmt qq) + ["SELECT p.*, a.slug author_slug FROM posts p INNER JOIN authors a ON (p.author_id = a.id) "])))) + +(deftest select-statement-2 + (let [qq (-> (dsl/select) + (dsl/from "posts" "p") + (dsl/field "p.id" "post_id") + (dsl/where "p.category = ?" "offtopic"))] + (is (= (dsl/fmt qq) + ["SELECT p.id post_id FROM posts p WHERE (p.category = ?)" "offtopic"])))) + +(deftest update-statement-1 + (let [qq (-> (dsl/update "users" "u") + (dsl/set "u.username" "foobar") + (dsl/set "u.email" "foo@bar.com") + (dsl/where "u.id = ? AND u.deleted_at IS null" 555))] + (is (= (dsl/fmt qq) + ["UPDATE users u SET u.username = ?, u.email = ? WHERE (u.id = ? AND u.deleted_at IS null)" "foobar" "foo@bar.com" 555])))) diff --git a/test/suricatta/extend_test.clj b/test/suricatta/extend_test.clj index 972f697..bcedc5d 100644 --- a/test/suricatta/extend_test.clj +++ b/test/suricatta/extend_test.clj @@ -2,13 +2,12 @@ (:require [clojure.test :refer :all] [suricatta.core :as sc] [suricatta.impl :as impl] - [suricatta.dsl :as dsl] [suricatta.proto :as proto] - [suricatta.format :refer [get-sql get-bind-values sqlvec] :as fmt] [cheshire.core :as json]) (:import org.postgresql.util.PGobject org.jooq.RenderContext org.jooq.BindContext + org.jooq.QueryPart org.jooq.impl.DSL)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -16,7 +15,9 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def dbspec {:subprotocol "postgresql" - :subname "//127.0.0.1/test"}) + :subname "//127.0.0.1:5433/test" + :user "test" + :password "test"}) (def ^:dynamic *ctx*) @@ -40,21 +41,11 @@ [data] (MyJson. data)) -(extend-protocol proto/IParamType +(extend-protocol proto/IParam MyJson - (-render [self ctx] - (if (proto/-inline? ctx) - (str "'" (json/encode (.-data self)) "'::json") - "?::json")) - - (-bind [self ctx] - (when-not (proto/-inline? ctx) - (let [stmt (proto/-statement ctx) - idx (proto/-next-bind-index ctx) - obj (doto (PGobject.) - (.setType "json") - (.setValue (json/encode (.-data self))))] - (.setObject stmt idx obj))))) + (-param [self ctx] + (let [qp (json/encode (.-data self))] + (impl/sql->param "{0}::json" qp)))) (extend-protocol proto/ISQLType PGobject @@ -69,22 +60,13 @@ [data] (MyArray. data)) -(extend-protocol proto/IParamType +(extend-protocol proto/IParam MyArray - (-render [self ctx] - (if (proto/-inline? ctx) - (let [items (->> (map str (.-data self)) - (interpose ","))] - (str "'{" (apply str items) "}'::bigint[]")) - "?::bigint[]")) - (-bind [self ctx] - (when-not (proto/-inline? ctx) - (let [stmt (proto/-statement ctx) - idx (proto/-next-bind-index ctx) - con (.getConnection stmt) - arr (into-array Long (.-data self)) - arr (.createArrayOf con "bigint" arr)] - (.setArray stmt idx arr))))) + (-param [self ctx] + (let [items (->> (map str (.-data self)) + (interpose ","))] + (impl/sql->param (str "'{" (apply str items) "}'::bigint[]"))))) + (extend-protocol proto/ISQLType (Class/forName "[Ljava.lang.Long;") @@ -107,52 +89,9 @@ result1 (first result)] (is (= (:k result1) {:foo 1})))) -(deftest inserting-json-using-dsl-test - (sc/execute *ctx* "create table t1 (k json)") - - (let [q (-> (dsl/insert-into :t1) - (dsl/insert-values {:k (myjson {:foo 1})}))] - (sc/execute *ctx* q)) - - (let [result (sc/fetch-one *ctx* ["select * from t1"])] - (is (= (:k result) {:foo 1})))) - -(deftest extract-bind-values-test - (let [d (myjson {:foo 1}) - q (-> (dsl/insert-into :table) - (dsl/insert-values {:data d})) - r (fmt/get-bind-values q)] - (is (= (count r) 1)) - (is (= (.data d) (.data (first r)))))) - -(deftest render-json-test - (let [q (-> (dsl/insert-into :t1) - (dsl/insert-values {:data (myjson {:foo 1})}))] - - (is (= (fmt/get-sql q) - "insert into t1 (data) values (?::json)")) - - (is (= (fmt/get-sql q {:dialect :pgsql :type :inlined}) - "insert into t1 (data) values ('{\"foo\":1}'::json)")) - - (is (= (fmt/get-sql q {:dialect :pgsql :type :inlined}) - "insert into t1 (data) values ('{\"foo\":1}'::json)")))) - -(deftest ddl-with-custom-datatypes-test - (let [q (-> (dsl/create-table :t1) - (dsl/add-column :title {:type "json" :null false}))] - (is (= (fmt/get-sql q) - "create table t1(title json not null)")))) - (deftest inserting-arrays-test (sc/execute *ctx* "create table t1 (data bigint[])") (let [data (myintarray [1 2 3])] (sc/execute *ctx* ["insert into t1 (data) values (?)" data])) (let [result (sc/fetch *ctx* "select * from t1")] (is (= result [{:data [1 2 3]}])))) - -(deftest render-array-test - (let [q (-> (dsl/insert-into :t1) - (dsl/insert-values {:data (myintarray [1 2 3])}))] - (is (= (fmt/get-sql q {:dialect :pgsql :type :inlined}) - "insert into t1 (data) values ('{1,2,3}'::bigint[])")))) diff --git a/test/user.clj b/test/user.clj new file mode 100644 index 0000000..a5d28de --- /dev/null +++ b/test/user.clj @@ -0,0 +1,25 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2016-2019 Andrey Antukh + +(ns user + (:require [clojure.tools.namespace.repl :as repl] + [clojure.walk :refer [macroexpand-all]] + [clojure.pprint :refer [pprint]] + [clojure.test :as test])) + +(defn- run-test + ([] (run-test #"^suricatta\..*test.*")) + ([o] + (repl/refresh) + (cond + (instance? java.util.regex.Pattern o) + (test/run-all-tests o) + + (symbol? o) + (if-let [sns (namespace o)] + (do (require (symbol sns)) + (test/test-vars [(resolve o)])) + (test/test-ns o)))))