diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f07c4..11fa49c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ * 2.4.next in progress * Add `:create-or-replace-view` to support PostgreSQL's lack of `IF NOT EXISTS` for `CREATE VIEW`. * Add `:select` with function call and alias example to README (PR [#502](https://github.com/seancorfield/honeysql/pull/502) [@markbastian](https://github.com/markbastian)). - * Address [#497](https://github.com/seancorfield/honeysql/issues/497) by adding `:alias` special syntax. Documentation TBD. + * Address [#497](https://github.com/seancorfield/honeysql/issues/497) by adding `:alias` special syntax. * Attempt to clarify the formatting behavior of the `:values` clause when used to produce column names. * Update `tools.build` to 0.9.5 (and remove `:java-opts` setting from `build/run-task`) diff --git a/doc/clause-reference.md b/doc/clause-reference.md index ff2d179..97673b0 100644 --- a/doc/clause-reference.md +++ b/doc/clause-reference.md @@ -490,6 +490,16 @@ Here, `:select` has a three expressions as its argument. The first is a simple column name. The second is an expression and its alias. The third is a simple column name and its alias. +An alias can be a simple name (a keyword or a symbol) or a string. An alias +containing a dot (`.`) is treated as a single name for quoting purposes. +Otherwise, a simple name will be formatted using table and column name rules +(including `-` to `_` translation). An alias specified as a string will not get +the `-` to `_` translation. There may be other contexts where you need to +refer to an alias but don't want the table/column rules applied to it, e.g., +in an `:order-by` clause. You can use the special syntax `[:alias :some.thing]` +to tell HoneySQL to treat `:some.thing` as an alias instead of a table/column +name reference. + `:select-distinct` works the same way but produces `SELECT DISTINCT`. > Google BigQuery support: to provide `SELECT * EXCEPT ..` and `SELECT * REPLACE ..` syntax, HoneySQL supports a vector starting with `:*` or the symbol `*` followed by except columns and/or replace expressions as columns: @@ -967,6 +977,11 @@ user=> (sql/format {:select [:*] :from :table ["SELECT * FROM table ORDER BY CASE WHEN NOW() < expiry_date THEN created_date ELSE expiry_date END DESC"] ``` +You can `ORDER BY` column names (`:col1`), or table and column (`:table.col1`), +or aliases (`:some.alias`). Since there is ambiguity between the formatting +of those, you can use the special syntax `[:alias :some.thing]` to tell +HoneySQL to treat `:some.thing` as an alias instead of a table/column name. + ## limit, offset, fetch Some databases, including MySQL, support `:limit` and `:offset` diff --git a/doc/special-syntax.md b/doc/special-syntax.md index 4f1deb4..6f6e3de 100644 --- a/doc/special-syntax.md +++ b/doc/special-syntax.md @@ -6,6 +6,31 @@ 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` / `:alter-column`). +The examples in this section assume the following: + +```clojure +(require '[honey.sql :as sql]) +``` + +## alias + +Accepts a single argument which should be an alias name (from an `AS` clause +elsewhere in the overall SQL statement) and uses alias formatting rules rather +than table/column formatting rules (different handling of dots and hyphens). +This allows you to override HoneySQL's default assumption about entity names +and strings. + +```clojure +(sql/format {:select [[:column-name "some-alias"]] + :from :b + :order-by [[[:alias "some-alias"]]]}) +;;=> ["SELECT column_name AS \"some-alias\" FROM b ORDER BY \"some-alias\" ASC"] +(sql/format {:select [[:column-name :'some-alias]] + :from :b + :order-by [[[:alias :'some-alias]]]}) +;;=> ["SELECT column_name AS \"some-alias\" FROM b ORDER BY \"some-alias\" ASC"] +``` + ## array Accepts a single argument, which is expected to evaluate to a sequence, @@ -13,8 +38,6 @@ with an optional second argument specifying the type of the array, and produces `ARRAY[?, ?, ..]` for the elements of that sequence (as SQL parameters): ```clojure -(require '[honey.sql :as sql]) - (sql/format-expr [:array (range 5)]) ;;=> ["ARRAY[?, ?, ?, ?, ?]" 0 1 2 3 4] (sql/format-expr [:array (range 3) :text]) diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index 86ce746..b055a17 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -2108,21 +2108,10 @@ (sql/format {:insert-into :foo :output [:inserted.*] :values [{:bar 1}]}) (sql/format {:insert-into :foo :columns [:bar] :output [:inserted.*] :values [[1]]}) - ;; issue-497 - (honey.sql/format {:select [[:column-name "some-alias"]] - :from :b - :order-by [[[:raw "\"some-alias\""]]]}) - (honey.sql/format {:select [[:column-name "some-alias"]] - :from :b - :order-by [[[:alias "some-alias"]]]}) - (honey.sql/format {:select [[:column-name "some-alias"]] - :from :b - :order-by [[[:alias "some-alias"]]]} - {:quoted true}) - (honey.sql/format {:select [[:column-name "some-alias"]] - :from :b - :order-by [[[:alias "some-alias"]]]} - {:dialect :mysql}) + (sql/format {:select [[:a.b :c.d]]} {:dialect :mysql}) + (sql/format {:select [[:column-name :'some-alias]] + :from :b + :order-by [[[:alias :'some-alias]]]}) (sql/format {:select :f.* :from [[:foo [:f :FOR :SYSTEM-TIME]]] :where [:= :f.id 1]}) (sql/format {:using [[:source [:= :table.id :source.id]]]}) ) diff --git a/test/honey/sql_test.cljc b/test/honey/sql_test.cljc index 8df5d28..f581d60 100644 --- a/test/honey/sql_test.cljc +++ b/test/honey/sql_test.cljc @@ -1205,6 +1205,45 @@ ORDER BY id = ? DESC (is (= ["INNER JOIN (tbl1 LEFT JOIN tbl2 USING (id))"] (sut/format {:join [[[:join :tbl1 {:left-join [:tbl2 [:using :id]]}]]]}))))) +(deftest issue-497-alias + + (is (= ["SELECT column_name AS \"some-alias\" FROM b ORDER BY \"some-alias\" ASC"] + (sut/format {:select [[:column-name "some-alias"]] + :from :b + :order-by [[[:raw "\"some-alias\""]]]}))) + ;; likely illegal SQL, but shows quoted keyword escaping the -/_ replace: + (is (= ["SELECT column_name AS \"some-alias\" FROM b ORDER BY some-alias ASC"] + (sut/format {:select [[:column-name "some-alias"]] + :from :b + :order-by [[:'some-alias]]}))) + (is (= ["SELECT column_name AS \"some-alias\" FROM b ORDER BY \"some-alias\" ASC"] + (sut/format {:select [[:column-name "some-alias"]] + :from :b + :order-by [[[:alias "some-alias"]]]}))) + (is (= ["SELECT column_name AS \"some-alias\" FROM b ORDER BY some_alias ASC"] + (sut/format {:select [[:column-name "some-alias"]] + :from :b + :order-by [[[:alias :some-alias]]]}))) + (is (= ["SELECT column_name AS \"some-alias\" FROM b ORDER BY \"some-alias\" ASC"] + (sut/format {:select [[:column-name "some-alias"]] + :from :b + :order-by [[[:alias :'some-alias]]]}))) + (is (= ["SELECT column_name AS \"some-alias\" FROM b ORDER BY \"some-alias\" ASC"] + (sut/format {:select [[:column-name "some-alias"]] + :from :b + :order-by [[[:alias "some-alias"]]]}))) + (is (= ["SELECT \"column-name\" AS \"some-alias\" FROM \"b\" ORDER BY ? ASC" + "some-alias"] + (sut/format {:select [[:column-name "some-alias"]] + :from :b + :order-by ["some-alias"]} + {:quoted true}))) + (is (= ["SELECT `column-name` AS `some-alias` FROM `b` ORDER BY `some-alias` ASC"] + (sut/format {:select [[:column-name "some-alias"]] + :from :b + :order-by [[[:alias "some-alias"]]]} + {:dialect :mysql})))) + (comment ;; partial workaround for #407: (sut/format {:select :f.* :from [[:foo [:f :for :system-time]]] :where [:= :f.id 1]})