Compare commits

...

2 Commits

Author SHA1 Message Date
lbrdnk
4cfadf51b4 cleanup 2025-05-23 11:13:09 +02:00
lbrdnk
3776ec5be4 RAW WINDOWS 2025-05-22 23:41:15 +02:00
8 changed files with 167 additions and 11 deletions

View File

@@ -58,6 +58,54 @@ const WINDOW = defineClauses(
],
docsPage: "offset",
},
"window-min": {
displayName: "WindowMin",
type: "any",
hasOptions: false,
multiple: true,
description: () => t`Computes window min`,
args: () => [
{
name: t`expression`,
type: "any",
description: t`Hello.`,
example: op("WindowMin", dimension(t`Total`)),
},
],
// docsPage: "offset",
},
"window-max": {
displayName: "WindowMax",
type: "any",
hasOptions: false,
multiple: true,
description: () => t`Computes window max`,
args: () => [
{
name: t`expression`,
type: "any",
description: t`Hello.`,
example: op("WindowMax", dimension(t`Total`)),
},
],
// docsPage: "offset",
},
"window-sum": {
displayName: "WindowSum",
type: "any",
hasOptions: false,
multiple: true,
description: () => t`Computes window sum`,
args: () => [
{
name: t`expression`,
type: "any",
description: t`Hello.`,
example: op("WindowSum", dimension(t`Total`)),
},
],
// docsPage: "offset",
},
},
);
@@ -1832,7 +1880,9 @@ export const AGGREGATION_FUNCTIONS = {
...WINDOW,
} as const;
// Custom column window functions should probably have dedicated object. This make Offset available in editor. No-no.
export const EXPRESSION_FUNCTIONS = {
...WINDOW,
...CONVERSION,
...STRING,
...DATE,

View File

@@ -894,6 +894,39 @@
(defmethod ->honeysql [:sql :min] [driver [_ field]] [:min (->honeysql driver field)])
(defmethod ->honeysql [:sql :max] [driver [_ field]] [:max (->honeysql driver field)])
(defn- window->honeysql
[driver [tag expr & args]]
(let [sanitized-tag (-> (name tag)
(str/replace-first #"^window-" "")
keyword)
[hsql-partition hsql-order-by] (->> (split-with (complement #{"order"})
args)
(map (partial drop 1))
(map #(vec (for [field %]
[(->honeysql driver field)]))))]
[:over [[sanitized-tag (->honeysql driver expr)]
(merge {}
(when (seq hsql-partition)
{:partition-by hsql-partition})
(when (seq hsql-order-by)
{:order-by hsql-order-by}))]]))
;; Following is tested only on postgres! If it turns out this structure is well suited for other drivers, it may live
;; here, else it should be moved to pg driver.
(defmethod ->honeysql [:sql :window-max]
[driver clause]
(window->honeysql driver clause))
(defmethod ->honeysql [:sql :window-min]
[driver clause]
(window->honeysql driver clause))
(defmethod ->honeysql [:sql :window-sum]
[driver clause]
(window->honeysql driver clause))
(defmethod ->honeysql [:sql :percentile]
[driver [_ field p]]
(let [field (->honeysql driver field)

View File

@@ -421,7 +421,8 @@
;; extraction functions (get some component of a given temporal value/column)
:temporal-extract
;; SUGAR drivers do not need to implement
:get-year :get-quarter :get-month :get-week :get-day :get-day-of-week :get-hour :get-minute :get-second})
:get-year :get-quarter :get-month :get-week :get-day :get-day-of-week :get-hour :get-minute :get-second
:window-min :window-max :window-sum})
(def boolean-functions
"Functions that return boolean values. Should match [[BooleanExpression]]."
@@ -605,6 +606,22 @@
(defclause ^{:requires-features #{:expressions}} +
x Addable, y Addable, more (rest Addable))
;; :any -> FieldOrExpression?
(mr/def ::WinArg
[:or :string :any])
(def ^:private WinArg
[:ref ::WinArg])
(defclause ^{:requires-features #{:expressions}} window-min
x NumericExpressionArg, more (rest WinArg))
(defclause ^{:requires-features #{:expressions}} window-max
x NumericExpressionArg, more (rest WinArg))
(defclause ^{:requires-features #{:expressions}} window-sum
x NumericExpressionArg, more (rest WinArg))
(defclause ^{:requires-features #{:expressions}} -
x NumericExpressionArg, y Addable, more (rest Addable))
@@ -965,7 +982,8 @@
(mr/def ::NumericExpression
(one-of + - / * coalesce length floor ceil round abs power sqrt exp log case case:if datetime-diff integer float
temporal-extract get-year get-quarter get-month get-week get-day get-day-of-week
get-hour get-minute get-second))
get-hour get-minute get-second
window-min window-max window-sum))
(mr/def ::StringExpression
(one-of substring trim ltrim rtrim replace lower upper concat regex-match-first coalesce case case:if host domain

View File

@@ -349,6 +349,12 @@
{:pre [(= (count clause) 4)]}
[tag opts (->pMBQL expr) n])
(defmethod ->pMBQL :cc-window
[[tag expr & args]]
(into (lib.options/ensure-uuid [tag {} (->pMBQL expr)])
(map ->pMBQL)
args))
;; These four expressions have a different form depending on the number of arguments.
(doseq [tag [:contains :starts-with :ends-with :does-not-contain]]
(lib.hierarchy/derive tag ::string-comparison))
@@ -473,7 +479,8 @@
:get-minute :get-second :get-quarter
:datetime-add :datetime-subtract :date
:concat :substring :replace :regex-match-first :split-part
:length :trim :ltrim :rtrim :upper :lower :text :integer]]
:length :trim :ltrim :rtrim :upper :lower :text :integer
:cc-window]]
(lib.hierarchy/derive tag ::expression))
(defmethod ->legacy-MBQL ::aggregation-or-expression
@@ -559,6 +566,10 @@
{:pre [(= (count clause) 4)]}
[tag opts (->legacy-MBQL expr) n])
(defmethod ->legacy-MBQL :cc-window
[[tag _opts expr & args]]
(into [tag (->legacy-MBQL expr)] (map ->legacy-MBQL) args))
(defmethod ->legacy-MBQL ::string-comparison
[[tag opts & args]]
(if (> (count args) 2)

View File

@@ -326,6 +326,9 @@
(lib.common/defop text [x])
(lib.common/defop integer [x])
(lib.common/defop float [x])
(lib.common/defop window-min [x & args])
(lib.common/defop window-max [x & args])
(lib.common/defop window-sum [x & args])
(mu/defn value :- ::lib.schema.expression/expression
"Creates a `:value` clause for the `literal`. Converts bigint literals to strings for serialization purposes."

View File

@@ -227,3 +227,30 @@
(mbql-clause/define-tuple-mbql-clause :float :- :type/Float
[:schema [:ref ::expression/string]])
;; windows probably deserve dedicated ns
(lib.hierarchy/derive :cc-window ::expression/expression)
(def cc-windows-definitions
[{:tag :window-min
:args-schema :any}
{:tag :window-max
:args-schema :any}
{:tag :window-sum
:args-schema :any}])
(doseq [{:keys [tag]} cc-windows-definitions]
(lib.hierarchy/derive tag :cc-window))
(doseq [{:keys [tag]} cc-windows-definitions]
(lib.hierarchy/derive tag :lib.type-of/type-is-type-of-arithmetic-args))
(doseq [{:keys [tag _args-schema]} cc-windows-definitions]
(mbql-clause/define-mbql-clause tag
[:and
{:error/message (str "valid" tag "clause")}
[:cat
[:= {:decode/normalize common/normalize-keyword} tag]
[:schema [:ref ::common/options]]
[:* :any]]
vector?]))

View File

@@ -44,7 +44,7 @@
(defn- process-query** [query rff]
(qp.debug/debug> (list `process-query query))
(let [preprocessed (qp.preprocess/preprocess query)
(let [preprocessed (qp.preprocess/preprocess query)
compiled (qp.compile/attach-compiled-query preprocessed)
rff (qp.postprocess/post-processing-rff preprocessed rff)]
(qp.execute/execute compiled rff)))

View File

@@ -179,6 +179,17 @@
(assoc :joins (mapv (fn [join]
(assoc join :qp/refs (:qp/refs query)))
joins))))))
(defn- cc-window?
[x]
(boolean (and (vector? x)
(#{:window-min :window-max :window-sum}
(first x)))))
(defn- contains-cc-window?
[x]
(boolean (seq (lib.util.match/match x
cc-window?
&match))))
(defn- should-nest-expressions?
"Whether we should nest the expressions in a inner query; true if
@@ -189,16 +200,19 @@
3. AND the breakouts/aggregations/order-bys contain at least one `:expression` reference."
[{:keys [expressions], breakouts :breakout, aggregations :aggregation, order-bys :order-by, :as _inner-query}]
(and
(or
(contains-cc-window? expressions)
(and
;; 1. has some expression definitions
(seq expressions)
(seq expressions)
;; 2. has some breakouts or aggregations or order-by
(or (seq breakouts)
(seq aggregations)
(seq order-bys))
(or (seq breakouts)
(seq aggregations)
(seq order-bys))
;; 3. contains an `:expression` ref
(lib.util.match/match-one (concat breakouts aggregations order-bys)
:expression)))
(lib.util.match/match-one
(concat breakouts aggregations order-bys)
:expression))))
(defn nest-expressions
"Pushes the `:source-table`/`:source-query`, `:expressions`, and `:joins` in the top-level of the query into a