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)))))