parent
e8ea9283cc
commit
95e50a930e
5 changed files with 86 additions and 22 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
* 2.3.next in progress
|
||||
* 2.4.next in progress
|
||||
* Fix [#439](https://github.com/seancorfield/honeysql/issues/439) by rewriting how DDL options are processed; also fixes [#386](https://github.com/seancorfield/honeysql/issues/386) and [#437](https://github.com/seancorfield/honeysql/issues/437); **Whilst this is intended to be purely a bug fix, it has the potential to be a breaking change -- hence the version jump to 2.4!**
|
||||
* Fix [#438](https://github.com/seancorfield/honeysql/issues/438) by
|
||||
supporting options on `TRUNCATE`.
|
||||
* Address [#435](https://github.com/seancorfield/honeysql/issues/435) by showing `CREATE TEMP TABLE` etc.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
[org.corfield.build :as bb]))
|
||||
|
||||
(def lib 'com.github.seancorfield/honeysql)
|
||||
(defn- the-version [patch] (format "2.3.%s" patch))
|
||||
(defn- the-version [patch] (format "2.4.%s" patch))
|
||||
(def version (the-version (b/git-count-revs nil)))
|
||||
(def snapshot (the-version "999-SNAPSHOT"))
|
||||
|
||||
|
|
|
|||
|
|
@ -316,14 +316,14 @@ user=> (-> (create-table :cities)
|
|||
;; default values for columns:
|
||||
user=> (-> (create-table :distributors)
|
||||
(with-columns [[:did :integer [:primary-key]
|
||||
;; "serial" is inlined as 'SERIAL':
|
||||
;; "serial" is inlined as 'serial':
|
||||
[:default [:nextval "serial"]]]
|
||||
[:name [:varchar 40] [:not nil]]])
|
||||
(sql/format {:pretty true}))
|
||||
;; newlines inserted for readability:
|
||||
["
|
||||
CREATE TABLE distributors
|
||||
(did INTEGER PRIMARY KEY DEFAULT NEXTVAL('SERIAL'), name VARCHAR(40) NOT NULL)
|
||||
(did INTEGER PRIMARY KEY DEFAULT NEXTVAL('serial'), name VARCHAR(40) NOT NULL)
|
||||
"]
|
||||
;; PostgreSQL CHECK constraint is supported:
|
||||
user=> (-> (create-table :products)
|
||||
|
|
@ -335,7 +335,7 @@ user=> (-> (create-table :products)
|
|||
(sql/format {:pretty true}))
|
||||
["
|
||||
CREATE TABLE products
|
||||
(product_no INTEGER, name TEXT, price NUMERIC CHECK(PRICE > 0), discounted_price NUMERIC, CHECK((discounted_price > 0) AND (price > discounted_price)))
|
||||
(product_no INTEGER, name TEXT, price NUMERIC CHECK(price > 0), discounted_price NUMERIC, CHECK((discounted_price > 0) AND (price > discounted_price)))
|
||||
"]
|
||||
;; conditional creation:
|
||||
user=> (-> (create-table :products :if-not-exists)
|
||||
|
|
|
|||
|
|
@ -875,6 +875,17 @@
|
|||
(str " " (str/join ", " (map #(format-simple-clause % "column/index operations") clauses)))))]
|
||||
[(str (sql-kw k) " " (format-entity x))]))
|
||||
|
||||
(def ^:private special-ddl-keywords
|
||||
"If these are found in DDL, they should map to the given
|
||||
SQL string instead of what sql-kw would do."
|
||||
{:auto-increment "AUTO_INCREMENT"})
|
||||
|
||||
(defn- sql-kw-ddl
|
||||
"Handle SQL keywords in DDL (allowing for special/exceptions)."
|
||||
[id]
|
||||
(or (get special-ddl-keywords (sym->kw id))
|
||||
(sql-kw id)))
|
||||
|
||||
(defn- format-ddl-options
|
||||
"Given a sequence of options for a DDL statement (the part that
|
||||
comes between the entity name being created/dropped and the
|
||||
|
|
@ -888,11 +899,14 @@
|
|||
(str/join " "
|
||||
(map (fn [e]
|
||||
(if (ident? e)
|
||||
(sql-kw e)
|
||||
(sql-kw-ddl e)
|
||||
(format-simple-expr e context)))
|
||||
opt))
|
||||
(ident? opt)
|
||||
(sql-kw-ddl opt)
|
||||
:else
|
||||
(sql-kw opt))))
|
||||
(throw (ex-info "expected symbol or keyword"
|
||||
{:unexpected opt})))))
|
||||
|
||||
(defn- destructure-ddl-item [table context]
|
||||
(let [params
|
||||
|
|
@ -908,7 +922,7 @@
|
|||
[(butlast (butlast coll)) (last (butlast coll)) ine]
|
||||
[(butlast coll) (last coll) nil])]
|
||||
(into [(str/join " " (map sql-kw prequel))
|
||||
(format-entity table)
|
||||
(when table (format-entity table))
|
||||
(when ine (sql-kw ine))]
|
||||
(when opts
|
||||
(format-ddl-options opts context)))))
|
||||
|
|
@ -925,7 +939,10 @@
|
|||
(comment
|
||||
(destructure-ddl-item [:foo [:abc [:continue :wibble] :identity]] "test")
|
||||
(destructure-ddl-item [:foo] "test")
|
||||
(format-truncate :truncate [:foo]))
|
||||
(destructure-ddl-item [:id [:int :unsigned :auto-increment]] "test")
|
||||
(destructure-ddl-item [[[:foreign-key :bar]] :quux [[:wibble :wobble]]] "test")
|
||||
(format-truncate :truncate [:foo])
|
||||
)
|
||||
|
||||
(defn- format-create [q k item as]
|
||||
(let [[pre entity ine & more]
|
||||
|
|
@ -968,13 +985,27 @@
|
|||
[(str/join " " (remove nil? (into [(sql-kw k) if-exists tables] more)))]))
|
||||
|
||||
(defn- format-single-column [xs]
|
||||
(reset! *formatted-column* true)
|
||||
(str/join " " (cons (format-simple-expr (first xs) "column operation")
|
||||
(map #(binding [*formatted-column* (atom false)]
|
||||
(cond-> (format-simple-expr % "column operation")
|
||||
(not @*formatted-column*)
|
||||
(upper-case)))
|
||||
(rest xs)))))
|
||||
(let [[col & options] (if (ident? (first xs)) xs (cons nil xs))
|
||||
[pre col ine & options]
|
||||
(destructure-ddl-item [col options] "column operation")]
|
||||
(when (seq pre) (throw (ex-info "column syntax error" {:unexpected pre})))
|
||||
(when (seq ine) (throw (ex-info "column syntax error" {:unexpected ine})))
|
||||
(str/join " " (filter seq (cons col options)))))
|
||||
|
||||
(comment
|
||||
(destructure-ddl-item [:foo [:abc [:continue :wibble] :identity]] "test")
|
||||
(destructure-ddl-item [:foo] "test")
|
||||
(destructure-ddl-item [:id [:int :unsigned :auto-increment]] "test")
|
||||
(format-single-column [:id :int :unsigned :auto-increment])
|
||||
(format-single-column [[:constraint :code_title] [:primary-key :code :title]])
|
||||
(destructure-ddl-item [[[:foreign-key :bar]] :quux [[:wibble :wobble]]] "test")
|
||||
|
||||
(format-truncate :truncate [:foo])
|
||||
|
||||
(destructure-ddl-item [:address [:text]] "test")
|
||||
(format-single-column [:address :text])
|
||||
(format-single-column [:did :uuid [:default [:gen_random_uuid]]])
|
||||
)
|
||||
|
||||
(defn- format-table-columns [_ xs]
|
||||
[(str "("
|
||||
|
|
@ -990,6 +1021,12 @@
|
|||
(let [items (if (and (sequential? spec) (sequential? (first spec))) spec [spec])]
|
||||
[(str/join ", " (for [item items] (format-add-single-item k item)))]))
|
||||
|
||||
(comment
|
||||
(format-add-item :add-column [:address :text])
|
||||
(format-add-single-item :add-column [:address :text])
|
||||
(format-single-column [:address :text])
|
||||
)
|
||||
|
||||
(defn- format-rename-item [k [x y]]
|
||||
[(str (sql-kw k) " " (format-entity x) " TO " (format-entity y))])
|
||||
|
||||
|
|
@ -1357,7 +1394,7 @@
|
|||
;; bigquery column types:
|
||||
:bigquery/array (fn [_ spec]
|
||||
[(str "ARRAY<"
|
||||
(str/join " " (map #(format-simple-expr % "column operation") spec))
|
||||
(str/join " " (map #(sql-kw %) spec))
|
||||
">")])
|
||||
:bigquery/struct (fn [_ spec]
|
||||
[(str "STRUCT<"
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@
|
|||
[:location :point]])
|
||||
sql/format))))
|
||||
(testing "create table with foreign key reference"
|
||||
(is (= ["CREATE TABLE weather (city VARCHAR(80) REFERENCES CITIES(CITY), temp_lo INT, temp_hi INT, prcp REAL, date DATE)"]
|
||||
(is (= ["CREATE TABLE weather (city VARCHAR(80) REFERENCES cities(city), temp_lo INT, temp_hi INT, prcp REAL, date DATE)"]
|
||||
(-> (create-table :weather)
|
||||
(with-columns [[:city [:varchar :80] [:references :cities :city]]
|
||||
[:temp_lo :int]
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
[:date :date]])
|
||||
sql/format))))
|
||||
(testing "creating table with table level constraint"
|
||||
(is (= ["CREATE TABLE films (code CHAR(5), title VARCHAR(40), did INTEGER, date_prod DATE, kind VARCHAR(10), CONSTRAINT code_title PRIMARY KEY(CODE, TITLE))"]
|
||||
(is (= ["CREATE TABLE films (code CHAR(5), title VARCHAR(40), did INTEGER, date_prod DATE, kind VARCHAR(10), CONSTRAINT code_title PRIMARY KEY(code, title))"]
|
||||
(-> (create-table :films)
|
||||
(with-columns [[:code [:char 5]]
|
||||
[:title [:varchar 40]]
|
||||
|
|
@ -204,7 +204,7 @@
|
|||
[[:constraint :code_title] [:primary-key :code :title]]])
|
||||
sql/format))))
|
||||
(testing "creating table with column level constraint"
|
||||
(is (= ["CREATE TABLE films (code CHAR(5) CONSTRAINT FIRSTKEY PRIMARY KEY, title VARCHAR(40) NOT NULL, did INTEGER NOT NULL, date_prod DATE, kind VARCHAR(10))"]
|
||||
(is (= ["CREATE TABLE films (code CHAR(5) CONSTRAINT firstkey PRIMARY KEY, title VARCHAR(40) NOT NULL, did INTEGER NOT NULL, date_prod DATE, kind VARCHAR(10))"]
|
||||
(-> (create-table :films)
|
||||
(with-columns [[:code [:char 5] [:constraint :firstkey] [:primary-key]]
|
||||
[:title [:varchar 40] [:not nil]]
|
||||
|
|
@ -213,13 +213,13 @@
|
|||
[:kind [:varchar 10]]])
|
||||
sql/format))))
|
||||
(testing "creating table with columns with default values"
|
||||
(is (= ["CREATE TABLE distributors (did INTEGER PRIMARY KEY DEFAULT NEXTVAL('SERIAL'), name VARCHAR(40) NOT NULL)"]
|
||||
(is (= ["CREATE TABLE distributors (did INTEGER PRIMARY KEY DEFAULT NEXTVAL('serial'), name VARCHAR(40) NOT NULL)"]
|
||||
(-> (create-table :distributors)
|
||||
(with-columns [[:did :integer [:primary-key] [:default [:nextval "serial"]]]
|
||||
[:name [:varchar 40] [:not nil]]])
|
||||
sql/format))))
|
||||
(testing "creating table with column checks"
|
||||
(is (= ["CREATE TABLE products (product_no INTEGER, name TEXT, price NUMERIC CHECK(PRICE > 0), discounted_price NUMERIC, CHECK((discounted_price > 0) AND (price > discounted_price)))"]
|
||||
(is (= ["CREATE TABLE products (product_no INTEGER, name TEXT, price NUMERIC CHECK(price > 0), discounted_price NUMERIC, CHECK((discounted_price > 0) AND (price > discounted_price)))"]
|
||||
(-> (create-table :products)
|
||||
(with-columns [[:product_no :integer]
|
||||
[:name :text]
|
||||
|
|
@ -228,6 +228,32 @@
|
|||
[[:check [:and [:> :discounted_price 0] [:> :price :discounted_price]]]]])
|
||||
sql/format)))))
|
||||
|
||||
(deftest references-issue-386
|
||||
(is (= ["CREATE TABLE IF NOT EXISTS user (id VARCHAR(255) NOT NULL PRIMARY KEY, company_id INT NOT NULL, name VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY(company_id) REFERENCES company(id))"]
|
||||
(-> {:create-table [:user :if-not-exists]
|
||||
:with-columns
|
||||
[[:id [:varchar 255] [:not nil] [:primary-key]]
|
||||
[:company-id :int [:not nil]]
|
||||
[:name [:varchar 255] [:not nil]]
|
||||
[:password [:varchar 255] [:not nil]]
|
||||
[:created-time :datetime [:default :CURRENT_TIMESTAMP]]
|
||||
[:updated-time :datetime [:default :CURRENT_TIMESTAMP]
|
||||
:on :update :CURRENT_TIMESTAMP]
|
||||
[[:foreign-key :company-id] [:references :company :id]]]}
|
||||
(sql/format)))))
|
||||
|
||||
(deftest create-table-issue-437
|
||||
(is (= ["CREATE TABLE bar (did UUID DEFAULT GEN_RANDOM_UUID(), foo_id VARCHAR NOT NULL, PRIMARY KEY(did, foo_id), FOREIGN KEY(foo_id) REFERENCES foo(id) ON DELETE CASCADE)"]
|
||||
(-> (create-table :bar)
|
||||
(with-columns
|
||||
[[:did :uuid [:default [:gen_random_uuid]]]
|
||||
[:foo-id :varchar [:not nil]]
|
||||
[[:primary-key :did :foo-id]]
|
||||
[[:foreign-key :foo-id]
|
||||
[:references :foo :id]
|
||||
:on-delete :cascade]])
|
||||
(sql/format)))))
|
||||
|
||||
(deftest over-test
|
||||
(testing "window function over on select statemt"
|
||||
(is (= ["SELECT id, AVG(salary) OVER (PARTITION BY department ORDER BY designation ASC) AS Average, MAX(salary) OVER w AS MaxSalary FROM employee WINDOW w AS (PARTITION BY department)"]
|
||||
|
|
|
|||
Loading…
Reference in a new issue