Merge branch 'develop' into issue-561

Signed-off-by: Sean Corfield <sean@corfield.org>
This commit is contained in:
Sean Corfield 2025-03-12 14:51:18 -07:00
commit 30b5fabe58
No known key found for this signature in database
4 changed files with 80 additions and 9 deletions

View file

@ -1,7 +1,7 @@
# Changes
* 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.
* Drop support for Clojure 1.9 [#561](https://github.com/seancorfield/honeysql/issues/561).
* 2.6.1281 -- 2025-03-06

View file

@ -88,6 +88,29 @@ In the subquery case, produces `ARRAY(subquery)`:
;;=> ["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
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"]
```
See also [`get-in`](xtdb.md#object-navigation-expressions)
and [`at`](#at) for additional path navigation functions.
## entity
Accepts a single keyword or symbol argument and produces a

View file

@ -1929,29 +1929,36 @@
(defn- get-in-navigation
"[:get-in expr key-or-index1 key-or-index2 ...]"
[_ [expr & kix]]
[wrap [expr & kix]]
(let [[sql & params] (format-expr expr)
[sqls params']
(reduce-sql (map #(cond (number? %)
[(str "[" % "]")]
(string? %)
[(str "[" (sqlize-value %) "]")]
(ident? %)
[(str "." (format-entity %))]
:else
(let [[sql' & params'] (format-expr %)]
(cons (str "[" sql' "]") params')))
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)]
(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)]
(into [(str sql sep (format-entity col)
(into [(str sql sep (format-simple-expr col "dot navigation")
(when (seq subcols)
(str "." (join "." (map format-entity subcols)))))]
(str "." (join "." (map #(format-simple-expr % "dot navigation")
subcols)))))]
params)))
(def ^:private special-syntax
(atom
{;; these "functions" are mostly used in column
@ -1999,6 +2006,7 @@
(let [[sqls params] (format-expr-list arr)
type-str (when type (str "::" (sql-kw type) "[]"))]
(into [(str "ARRAY[" (join ", " sqls) "]" type-str)] params))))
:at (fn [_ data] (get-in-navigation false data))
:at-time-zone
(fn [_ [expr tz]]
(let [[sql & params] (format-expr expr {:nested true})
@ -2031,7 +2039,7 @@
[sql-e & params-e] (format-expr escape-chars)]
(into* [(str sql-p " " (sql-kw :escape) " " sql-e)] params-p params-e)))
:filter expr-clause-pairs
:get-in #'get-in-navigation
:get-in (fn [_ data] (get-in-navigation true data))
:ignore-nulls ignore-respect-nulls
:inline
(fn [_ xs]

View file

@ -1,4 +1,4 @@
;; copyright (c) 2021-2024 sean corfield, all rights reserved
;; copyright (c) 2021-2025 sean corfield, all rights reserved
(ns honey.sql-test
(:refer-clojure :exclude [format])
@ -1236,6 +1236,43 @@ ORDER BY id = ? DESC
((get-in (y z) *)))}
{:dialect :mysql})))))
(deftest issue-570-snowflake-dot-selection
(testing "basic colon selection"
(is (= ["SELECT a:b, c:d, a:d.x, a:d.x.y"]
(let [t :a c :d]
(sut/format {:select [[[:.:. t :b]] [[:.:. :c c]]
[[:.:. t c :x]] [[:.:. t c :x :y]]]}))))
(is (= ["SELECT [a]:[b], [c]:[d], [a]:[d].[x]"]
(let [t :a c :d]
(sut/format {:select [[[:.:. t :b]] [[:.:. :c c]] [[:.:. t c :x]]]}
{:dialect :sqlserver})))))
(testing "basic field selection from composite"
(is (= ["SELECT (v):*, (w):x, (Y(z)):*"]
(sut/format '{select (((.:. (nest v) *))
((.:. (nest w) x))
((.:. (nest (y z)) *)))})))
(is (= ["SELECT (`v`):*, (`w`):`x`, (Y(`z`)):*"]
(sut/format '{select (((.:. (nest v) *))
((.:. (nest w) x))
((.:. (nest (y z)) *)))}
{: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
(testing "single argument :raw"
(is (= ["@foo := 42"]