           (get *datatypes* type)
           (into [] opts)))
 
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Protocols for constructors
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defprotocol IField
+  (field* [_] "Field constructor"))
+
+(defprotocol ISortField
+  (sort-field* [_] "Sort field constructor"))
+
+(defprotocol ITable
+  (table* [_] "Table constructor."))
+
+(defprotocol IName
+  (name* [_] "Name constructor (mainly used with CTE)"))
+
+(defprotocol ITableCoerce
+  (as-table* [_ params] "Table alias constructor"))
+
+(defprotocol IFieldCoerce
+  (as-field* [_ params] "Field alias constructor"))
+
+(defprotocol ICondition
+  (condition* [_] "Condition constructor"))
+
+(defprotocol IVal
+  (val* [_] "Val constructor"))
+
+(defprotocol IDeferred
+  "Protocol mainly defined for uniform unwrapping
+  deferred queries."
+  (unwrap* [_] "Unwrap the object"))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Protocol Implementations
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(extend-protocol IField
+  java.lang.String
+  (field* ^org.jooq.Field [^String s]
+    (DSL/field s))
+
+  clojure.lang.Keyword
+  (field* ^org.jooq.Field [kw]
+    (field* (clojure.core/name kw)))
+
+  org.jooq.Field
+  (field* ^org.jooq.Field [f] f)
+
+  org.jooq.impl.Val
+  (field* ^org.jooq.Field [v] v))
+
+(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.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)))))
+
+(extend-protocol IVal
+  Object
+  (val* [v] (DSL/val v)))
+
+(extend-protocol IFieldCoerce
+  org.jooq.FieldLike
+  (as-field* [n args]
+    (let [^String alias (first args)]
+      (.asField n alias)))
+
+  suricatta.types.Deferred
+  (as-field* [n args] (as-field* @n args)))
+
+(extend-protocol ITableCoerce
+  org.jooq.Name
+  (as-table* [^org.jooq.Name n args]
+    (.as n ^String (first args)))
+
+  org.jooq.DerivedColumnList
+  (as-table* [n args]
+    (.as n (first args)))
+
+  org.jooq.TableLike
+  (as-table* [n args]
+    (let [^String alias (first args)]
+      (->> (into-array String (rest args))
+           (.asTable n alias))))
+
+  suricatta.types.Deferred
+  (as-table* [t args]
+    (as-table* @t args)))
+
+(extend-protocol IDeferred
+  Object
+  (unwrap* [self]
+    (impl/wrap-if-need self))
+
+  suricatta.types.Deferred
+  (unwrap* [self]
+    (unwrap* @self)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Common DSL functions
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defn as-table
+  "Coerce querypart to table expression."
+  [o & args]
+  (defer
+    (->> (map unwrap* args)
+         (as-table* o))))
+
+(defn as-field
+  "Coerce querypart to field expression."
+  [o & args]
+  (defer
+    (->> (map unwrap* args)
+         (as-field* o))))
+
+(defn field
+  "Create a field instance."
+  [data & [{:keys [alias] :as opts}]]
+  (defer
+    (let [f (field* data)]
+      (if alias
+        (.as f (clojure.core/name alias))
+        f))))
+
+(defn val
+  [v]
+  (val* v))
+
+(defn table
+  "Create a table instance."
+  [data & [{:keys [alias] :as opts}]]
+  (defer
+    (let [f (table* data)]
+      (if alias
+        (.as f (clojure.core/name alias))
+        f))))
+
+(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 <table>
+  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
+    (.join @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."
+  [select']
+  (defer
+    (DSL/exists select')))
+
+(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]
+  (DSL/not 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]]
+          (.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]
+   (defer
+     (let [t (unwrap* t)]
+       (reduce (fn [acc [k v]]
+                 (.set t (field* k) v))
+               t kv))))
+  ([t k v]
+   (defer
+     (let [v (unwrap* v)
+           t (unwrap* t)]
+       (if (instance? org.jooq.Row k)
+         (.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

           org.jooq.util.mariadb.MariaDBDataType
           org.jooq.util.mysql.MySQLDataType
           suricatta.types.Context))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Protocols for constructors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defprotocol IField
  (field* [_] "Field constructor"))

(defprotocol ISortField
  (sort-field* [_] "Sort field constructor"))

(defprotocol ITable
  (table* [_] "Table constructor."))

(defprotocol IName
  (name* [_] "Name constructor (mainly used with CTE)"))

(defprotocol ITableCoerce
  (as-table* [_ params] "Table alias constructor"))

(defprotocol IFieldCoerce
  (as-field* [_ params] "Field alias constructor"))

(defprotocol ICondition
  (condition* [_] "Condition constructor"))

(defprotocol IVal
  (val* [_] "Val constructor"))

(defprotocol IDeferred
  "Protocol mainly defined for uniform unwrapping
  deferred queries."
  (unwrap* [_] "Unwrap the object"))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Protocol Implementations
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(extend-protocol IField
  java.lang.String
  (field* ^org.jooq.Field [^String s]
    (DSL/field s))

  clojure.lang.Keyword
  (field* ^org.jooq.Field [kw]
    (field* (clojure.core/name kw)))

  org.jooq.Field
  (field* ^org.jooq.Field [f] f)

  org.jooq.impl.Val
  (field* ^org.jooq.Field [v] v))

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

(extend-protocol IVal
  Object
  (val* [v] (DSL/val v)))

(extend-protocol IFieldCoerce
  org.jooq.FieldLike
  (as-field* [n args]
    (let [^String alias (first args)]
      (.asField n alias)))

  suricatta.types.Deferred
  (as-field* [n args] (as-field* @n args)))

(extend-protocol ITableCoerce
  org.jooq.Name
  (as-table* [^org.jooq.Name n args]
    (.as n ^String (first args)))

  org.jooq.DerivedColumnList
  (as-table* [n args]
    (.as n (first args)))

  org.jooq.TableLike
  (as-table* [n args]
    (let [^String alias (first args)]
      (->> (into-array String (rest args))
           (.asTable n alias))))

  suricatta.types.Deferred
  (as-table* [t args]
    (as-table* @t args)))

(extend-protocol IDeferred
  Object
  (unwrap* [self]
    (impl/wrap-if-need self))

  suricatta.types.Deferred
  (unwrap* [self]
    (unwrap* @self)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Common DSL functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn as-table
  "Coerce querypart to table expression."
  [o & args]
  (defer
    (->> (map unwrap* args)
         (as-table* o))))

(defn as-field
  "Coerce querypart to field expression."
  [o & args]
  (defer
    (->> (map unwrap* args)
         (as-field* o))))

(defn field
  "Create a field instance."
  [data & [{:keys [alias] :as opts}]]
  (defer
    (let [f (field* data)]
      (if alias
        (.as f (clojure.core/name alias))
        f))))

(defn val
  [v]
  (val* v))

(defn table
  "Create a table instance."
  [data & [{:keys [alias] :as opts}]]
  (defer
    (let [f (table* data)]
      (if alias
        (.as f (clojure.core/name alias))
        f))))

(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 <table>
  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
    (.join @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."
  [select']
  (defer
    (DSL/exists select')))

(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]
  (DSL/not 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]]
          (.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]
   (defer
     (let [t (unwrap* t)]
       (reduce (fn [acc [k v]]
                 (.set t (field* k) v))
               t kv))))
  ([t k v]
   (defer
     (let [v (unwrap* v)
           t (unwrap* t)]
       (if (instance? org.jooq.Row k)
         (.set t k v)
         (.set t (field* k) v))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Delete statement
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn delete
  [t]
  (defer
    (DSL/delete (table* t))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DDL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(def ^{:doc "Datatypes translation map" :dynamic true}
  *datatypes*

   :mysql/timestamp MySQLDataType/TIMESTAMP
   :mysql/varchar MySQLDataType/VARCHAR})

(defn- make-datatype
  [{:keys [type] :as opts}]
  (reduce (fn [dt [attname attvalue]]
