Finish and document DDL

This commit is contained in:
Sean Corfield 2021-02-12 21:50:22 -08:00
parent 167d7cee0c
commit 41ed38ea38
4 changed files with 187 additions and 25 deletions

View file

@ -13,8 +13,62 @@ dialects that HoneySQL supports.
## alter-table, add-column, drop-column, modify-column, rename-column
`:alter-table` can accept either a single table name or
a sequence that begins with a table name and is followed
by clauses that manipulate columns (or indices, see below).
If a single table name is provided, a single column
(or index) operation can provided in the hash map DSL:
```clojure
user=> (sql/format {:alter-table :fruit
:add-column [:id :int [:not nil]]})
["ALTER TABLE fruit ADD COLUMN id INT NOT NULL"]
user=> (sql/format {:alter-table :fruit
:drop-column :ident})
["ALTER TABLE fruit DROP COLUMN ident"]
user=> (sql/format {:alter-table :fruit
:modify-column [:id :int :unsigned nil]})
["ALTER TABLE fruit MODIFY COLUMN id INT UNSIGNED NULL"]
user=> (sql/format {:alter-table :fruit
:rename-column [:look :appearance]})
["ALTER TABLE fruit RENAME COLUMN look TO appearance"]
```
If a sequence of a table name and various clauses is
provided, the generated `ALTER` statement will have
comma-separated clauses:
```clojure
user=> (sql/format {:alter-table [:fruit
{:add-column [:id :int [:not nil]]}
{:drop-column :ident}]})
["ALTER TABLE fruit ADD COLUMN id INT NOT NULL, DROP COLUMN ident"]
```
As can be seen above, `:add-column` and `:modify-column`
both accept a column description (as a sequence of simple
expressions); `:drop-column` accepts a single column name,
and `:rename-column` accepts a sequence with two column
names: the "from" and the "to" names.
## add-index, drop-index
`:add-index` accepts a single (function) expression
that describes an index, and `:drop-index` accepts a
single index name:
```clojure
user=> (sql/format {:alter-table :fruit
:add-index [:index :look :appearance]})
["ALTER TABLE fruit ADD INDEX look(appearance)"]
user=> (sql/format {:alter-table :fruit
:add-index [:unique nil :color :appearance]})
["ALTER TABLE fruit ADD UNIQUE(color,appearance)"]
user=> (sql/format {:alter-table :fruit :drop-index :look})
["ALTER TABLE fruit DROP INDEX look"]
```
## create-table, with-columns
`:create-table` can accept a single table name or a pair
@ -38,6 +92,17 @@ user=> (sql/format {:create-table :fruit
)"]
```
The `:with-columns` clause is formatted as if `{:inline true}`
was specified so nothing is parameterized. In addition,
everything except the first element of a column description
will be uppercased (mostly to give the appearance of separating
the column name from the SQL keywords).
Various function-like expressions can be specified, as shown
in the example above, but allow things like `CHECK` for a
constraint, `FOREIGN KEY` (with a column name), `REFERENCES`
(with a pair of column names). See [special-syntax.md#clause-descriptors](Clause Descriptors in Special Syntax) for more details.
## create-view
`:create-view` accepts a single view name:
@ -66,6 +131,14 @@ user=> (sql/format {:drop-table [:foo :bar]})
## rename-table
`:rename-table` accepts a pair of the "from" table name
and the "to" table names:
```clojure
user=> (sql/format {:rename-table [:fruit :vegetable]})
["RENAME TABLE fruit TO vegetable"]
```
## nest
This is pseudo-syntax that lets you wrap a substatement

View file

@ -4,6 +4,8 @@ This section lists the function-like expressions that
HoneySQL supports out of the box which are formatted
as special syntactic forms.
The first group are used for SQL expressions. The second (last group) are used primarily in column definitions (as part of `:with-columns` and `:add-column` / `:modify-column`).
## array
Accepts a single argument, which is expected to evaluate to
@ -58,12 +60,6 @@ expression (comma-separated, wrapped in parentheses):
;;=> ["(a, b, ?, x + ?)" "red" 1]
```
## default
Takes no arguments and produces the SQL keyword `DEFAULT`.
_[I expect this to be expanded for PostgreSQL]_
## inline
Accepts a single argument and tries to render it as a
@ -186,3 +182,58 @@ parameters from them:
(sql/format {:select [:a [[:raw ["@var := " ["foo"]]]]]})
;;=> ["SELECT a, @var := ?" "foo"]
```
## Column Descriptors
There are three types of descriptors that vary
in how they treat their first argument. All three
descriptors automatically try to inline any parameters
(and will throw an exception if they can't, since these
descriptors are meant to be used in column or index
specifications).
### foreign-key, primary-key
If no arguments are provided, these render as just SQL
keywords (uppercase):
```clojure
[:foreign-key] ;=> FOREIGN KEY
[:primary-key] ;=> PRIMARY KEY
```
Otherwise, these render as regular function calls:
```clojure
[:foreign-key :a] ;=> FOREIGN KEY(a)
[:primary-key :x :y] ;=> PRIMARY KEY(x,y)
```
## constraint, default, references
Although these are grouped together, they are generally
used differently. This group renders as SQL keywords if
no arguments are provided. If a single argument is
provided, this renders as a SQL keyword followed by the
argument. If two or more arguments are provided, this
renders as a SQL keyword followed by the first argument,
followed by the rest as a regular argument list:
```clojure
[:default] ;=> DEFAULT
[:default 42] ;=> DEFAULT 42
[:default "str"] ;=> DEFAULT 'str'
[:constraint :name] ;=> CONSTRAINT name
[:references :foo :bar] ;=> REFERENCES foo(bar)
```
## index, unique
These behave like the group above except that if the
first argument is `nil`, it is omitted:
```clojure
[:index :foo :bar :quux] ;=> INDEX foo(bar,quux)
[:index nil :bar :quux] ;=> INDEX(bar,quux)
[:unique :a :b] ;=> UNIQUE a(b)
```

View file

@ -37,7 +37,7 @@
(def ^:private default-clause-order
"The (default) order for known clauses. Can have items added and removed."
[;; DDL comes first (these don't really have a precedence):
:alter-table :add-column :drop-column :rename-column
:alter-table :add-column :drop-column :modify-column :rename-column
:add-index :drop-index :rename-table
:create-table :with-columns :create-view :drop-table
;; then SQL clauses in priority order:
@ -469,11 +469,12 @@
(format-set-exprs k x)))
(defn- format-simple-clause [c]
(let [[x & y] (format-dsl c)]
(when (seq y)
(throw (ex-info "column/index operations must be simple clauses"
{:clause c :params y})))
x))
(binding [*inline* true]
(let [[x & y] (format-dsl c)]
(when (seq y)
(throw (ex-info "column/index operations must be simple clauses"
{:clause c :params y})))
x)))
(defn- format-alter-table [k x]
(if (sequential? x)
@ -500,16 +501,16 @@
(str/join ", " (map #'format-entity tables)))]))
(defn- format-simple-expr [e]
(let [[x & y] (format-expr e)]
(when (seq y)
(throw (ex-info "column elements must be simple expressions"
{:expr e :params y})))
x))
(binding [*inline* true]
(let [[x & y] (format-expr e)]
(when (seq y)
(throw (ex-info "column elements must be simple expressions"
{:expr e :params y})))
x)))
(defn- format-single-column [xs]
(binding [*inline* true]
(str/join " " (let [[id & spec] (map #'format-simple-expr xs)]
(cons id (map upper-case spec))))))
(str/join " " (let [[id & spec] (map #'format-simple-expr xs)]
(cons id (map upper-case spec)))))
(defn- format-table-columns [k xs]
[(str "(\n "
@ -540,8 +541,10 @@
(atom {:alter-table #'format-alter-table
:add-column #'format-add-item
:drop-column #'format-selector
:modify-column #'format-add-item
:rename-column #'format-rename-item
:add-index #'format-add-item
;; so :add-index works with both [:index] and [:unique]
:add-index (fn [_ x] (format-on-expr :add x))
:drop-index #'format-selector
:rename-table #'format-rename-item
:create-table #'format-create-table
@ -676,9 +679,46 @@
(into params-x)
(into params-y)))))
(defn- function-0 [k xs]
[(str (sql-kw k)
(when (seq xs)
(str "(" (str/join "," (map #'format-simple-expr xs)) ")")))])
(defn- function-1 [k xs]
[(str (sql-kw k)
(when (seq xs)
(str " " (format-simple-expr (first xs))
(when-let [args (next xs)]
(str "(" (str/join "," (map #'format-simple-expr args)) ")")))))])
(defn- function-1-opt [k xs]
[(str (sql-kw k)
(when (seq xs)
(str (when-let [e (first xs)]
(str " " (format-simple-expr e)))
(when-let [args (next xs)]
(str "(" (str/join "," (map #'format-simple-expr args)) ")")))))])
(def ^:private special-syntax
(atom
{:array
{;; these "functions" are mostly used in column
;; descriptions so they generally have one of two forms:
;; function-0 - with zero arguments, renders as a keyword,
;; otherwise renders as a function call
;; function-1 - with zero arguments, renders as a keyword,
;; with one argument, as a keyword followed by an entity,
;; otherwise renders as a keyword followed by a function
;; call using the first entity as the function
;; function-1-opt - like function-1 except if the first
;; argument is nil, it is omitted
:constraint #'function-1
:default #'function-1
:foreign-key #'function-0
:index #'function-1-opt
:primary-key #'function-0
:references #'function-1
:unique #'function-1-opt
:array
(fn [_ [arr]]
(let [[sqls params] (format-expr-list arr)]
(into [(str "ARRAY[" (str/join ", " sqls) "]")] params)))
@ -720,9 +760,6 @@
(fn [_ [& args]]
(let [[sqls params] (format-expr-list args)]
(into [(str "(" (str/join ", " sqls) ")")] params)))
:default
(fn [_ []]
["DEFAULT"])
:inline
(fn [_ [x]]
(if (sequential? x)

View file

@ -46,6 +46,7 @@
(defn alter-table [& args] (generic :alter-table args))
(defn add-column [& args] (generic :add-column args))
(defn drop-column [& args] (generic-1 :drop-column args))
(defn modify-column [& args] (generic :modify-column args))
(defn rename-column [& args] (generic :rename-column args))
(defn add-index [& args] (generic :add-index args))
(defn drop-index [& args] (generic-1 :drop-index args))