This commit is contained in:
Sean Corfield 2022-11-05 17:15:32 -07:00
parent e8ea9283cc
commit 95e50a930e
5 changed files with 86 additions and 22 deletions

View file

@ -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.

View file

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

View file

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

View file

@ -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<"

View file

@ -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)"]