fix #570 by adding :at

there is also :.:.

Signed-off-by: Sean Corfield <sean@corfield.org>
This commit is contained in:
Sean Corfield 2025-03-12 14:48:09 -07:00
parent 44494e61c0
commit d74046c658
No known key found for this signature in database
4 changed files with 58 additions and 9 deletions

View file

@ -1,7 +1,7 @@
# Changes # Changes
* 2.7.next in progress * 2.7.next in progress
* Address #570 by adding `:.:.` as special syntax for Snowflake's JSON path syntax. * Address #570 by adding `:.:.` as special syntax for Snowflake's JSON path syntax, and `:at` as special syntax for general `[`..`]` path syntax.
* 2.6.1281 -- 2025-03-06 * 2.6.1281 -- 2025-03-06
* Address [#568](https://github.com/seancorfield/honeysql/issues/568) by adding `honey.sql/semicolon` to merge multiple SQL+params vectors into one (with semicolons separating the SQL statements). * Address [#568](https://github.com/seancorfield/honeysql/issues/568) by adding `honey.sql/semicolon` to merge multiple SQL+params vectors into one (with semicolons separating the SQL statements).

View file

@ -88,6 +88,29 @@ In the subquery case, produces `ARRAY(subquery)`:
;;=> ["SELECT ARRAY(SELECT * FROM table) AS arr"] ;;=> ["SELECT ARRAY(SELECT * FROM table) AS arr"]
``` ```
## at
If addition to dot navigation (for JSON) -- see the `.` and `.:.` syntax below --
HoneySQL also supports bracket notation for JSON navigation.
The first argument to `:at` is treated as an expression that identifies
the column, and subsequent arguments are treated as field names or array
indices to navigate into that document.
```clojure
user=> (sql/format {:select [[[:at :col :field1 :field2]]]})
["SELECT col.field1.field2"]
user=> (sql/format {:select [[[:at :table.col 0 :field]]]})
["SELECT table.col[0].field"]
```
If you want an array index to be a parameter, use `:lift`:
```clojure
user=> (sql/format {:select [[[:at :col [:lift 0] :field]]]})
["SELECT col[?].field" 0]
```
## at time zone ## at time zone
Accepts two arguments: an expression (assumed to be a date/time of some sort) Accepts two arguments: an expression (assumed to be a date/time of some sort)
@ -235,6 +258,9 @@ Can be used with `:nest` for field selection from composites:
;;=> ["SELECT (v).*, (MYFUNC(x)).y"] ;;=> ["SELECT (v).*, (MYFUNC(x)).y"]
``` ```
See also [`get-in`](xtdb.md#object-navigation-expressions)
and [`at`](#at) for additional path navigation functions.
## entity ## entity
Accepts a single keyword or symbol argument and produces a Accepts a single keyword or symbol argument and produces a

View file

@ -1936,28 +1936,34 @@
(defn- get-in-navigation (defn- get-in-navigation
"[:get-in expr key-or-index1 key-or-index2 ...]" "[:get-in expr key-or-index1 key-or-index2 ...]"
[_ [expr & kix]] [wrap [expr & kix]]
(let [[sql & params] (format-expr expr) (let [[sql & params] (format-expr expr)
[sqls params'] [sqls params']
(reduce-sql (map #(cond (number? %) (reduce-sql (map #(cond (number? %)
[(str "[" % "]")] [(str "[" % "]")]
(string? %)
[(str "[" (sqlize-value %) "]")]
(ident? %) (ident? %)
[(str "." (format-entity %))] [(str "." (format-entity %))]
:else :else
(let [[sql' & params'] (format-expr %)] (let [[sql' & params'] (format-expr %)]
(cons (str "[" sql' "]") params'))) (cons (str "[" sql' "]") params')))
kix))] kix))]
(into* [(str "(" sql ")" (join "" sqls))] params params'))) (into* [(str (if wrap (str "(" sql ")") sql)
(join "" sqls))]
params
params')))
(defn ignore-respect-nulls [k [x]] (defn- ignore-respect-nulls [k [x]]
(let [[sql & params] (format-expr x)] (let [[sql & params] (format-expr x)]
(into [(str sql " " (sql-kw k))] params))) (into [(str sql " " (sql-kw k))] params)))
(defn dot-navigation [sep [expr col & subcols]] (defn- dot-navigation [sep [expr col & subcols]]
(let [[sql & params] (format-expr expr)] (let [[sql & params] (format-expr expr)]
(into [(str sql sep (format-entity col) (into [(str sql sep (format-simple-expr col "dot navigation")
(when (seq subcols) (when (seq subcols)
(str "." (join "." (map format-entity subcols)))))] (str "." (join "." (map #(format-simple-expr % "dot navigation")
subcols)))))]
params))) params)))
(def ^:private special-syntax (def ^:private special-syntax
(atom (atom
@ -2006,6 +2012,7 @@
(let [[sqls params] (format-expr-list arr) (let [[sqls params] (format-expr-list arr)
type-str (when type (str "::" (sql-kw type) "[]"))] type-str (when type (str "::" (sql-kw type) "[]"))]
(into [(str "ARRAY[" (join ", " sqls) "]" type-str)] params)))) (into [(str "ARRAY[" (join ", " sqls) "]" type-str)] params))))
:at (fn [_ data] (get-in-navigation false data))
:at-time-zone :at-time-zone
(fn [_ [expr tz]] (fn [_ [expr tz]]
(let [[sql & params] (format-expr expr {:nested true}) (let [[sql & params] (format-expr expr {:nested true})
@ -2038,7 +2045,7 @@
[sql-e & params-e] (format-expr escape-chars)] [sql-e & params-e] (format-expr escape-chars)]
(into* [(str sql-p " " (sql-kw :escape) " " sql-e)] params-p params-e))) (into* [(str sql-p " " (sql-kw :escape) " " sql-e)] params-p params-e)))
:filter expr-clause-pairs :filter expr-clause-pairs
:get-in #'get-in-navigation :get-in (fn [_ data] (get-in-navigation true data))
:ignore-nulls ignore-respect-nulls :ignore-nulls ignore-respect-nulls
:inline :inline
(fn [_ xs] (fn [_ xs]

View file

@ -1225,7 +1225,23 @@ ORDER BY id = ? DESC
(sut/format '{select (((.:. (nest v) *)) (sut/format '{select (((.:. (nest v) *))
((.:. (nest w) x)) ((.:. (nest w) x))
((.:. (nest (y z)) *)))} ((.:. (nest (y z)) *)))}
{:dialect :mysql}))))) {:dialect :mysql}))))
(testing "bracket selection"
(is (= ["SELECT a['b'], c['b'], a['d'].x, a:e[0].name"]
(sut/format {:select [[[:at :a [:inline "b"]]]
[[:at :c "b"]]
[[:at :a [:inline "d"] :x]]
[[:.:. :a [:at :e [:inline 0]] :name]]]})))
(is (= ["SELECT a[?].name" 0]
(sut/format '{select (((at a (lift 0) name)))})))
;; sanity check, compare with get-in:
(is (= ["SELECT (a)[?].name" 0]
(sut/format '{select (((get-in a (lift 0) name)))})))
(is (= ["SELECT (a)['b'], (c)['b'], (a)['d'].x, a:(e)[0].name"]
(sut/format {:select [[[:get-in :a [:inline "b"]]]
[[:get-in :c "b"]]
[[:get-in :a [:inline "d"] :x]]
[[:.:. :a [:get-in :e [:inline 0]] :name]]]})))))
(deftest issue-476-raw (deftest issue-476-raw
(testing "single argument :raw" (testing "single argument :raw"