diff --git a/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj b/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj index 3ff46512318da6e96d100d4541dadc7e3ae93955..0b31b7a805a468e40c4dc11dc275268478b4f2d3 100644 --- a/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj +++ b/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj @@ -555,14 +555,16 @@ (defmethod sql.qp/->honeysql [:bigquery-cloud-sdk :datetime-diff] [driver [_ x y unit]] - (let [x' (sql.qp/->honeysql driver x) - y' (sql.qp/->honeysql driver y) + (let [x (sql.qp/->honeysql driver x) + y (sql.qp/->honeysql driver y) disallowed-types (keep (fn [x] (when-not (contains? #{:timestamp :date :datetime} (temporal-type x)) (or (some-> (temporal-type x) name) (hx/type-info->db-type (hx/type-info x))))) - [x' y'])] + [x y]) + x (hx/->timestamp x) + y (hx/->timestamp y)] (when (seq disallowed-types) (throw (ex-info (tru "Only datetime, timestamp, or date types allowed. Found {0}" @@ -571,41 +573,47 @@ :found disallowed-types :type qp.error-type/invalid-query}))) (case unit - (:year :month) - (let [; timestamp_diff doesn't support months or years, so convert to datetime to use datetime_diff - x' (hx/->datetime (trunc :day (hx/->timestamp x'))) - y' (hx/->datetime (trunc :day (hx/->timestamp y'))) - raw-unit (hsql/raw (name unit)) - positive-diff (fn [a b] ; precondition: a <= b + :year + (let [positive-diff (fn [a b] ; precondition: a <= b (hx/- - (hsql/call :datetime_diff b a raw-unit) + (hx/- (extract :year b) (extract :year a)) + ;; decrement if a is later than b in the year calendar (hx/cast :integer (hsql/call - :> - (hsql/call :datetime_diff a (hsql/call :date_trunc a raw-unit) (hsql/raw "day")) - (hsql/call :datetime_diff b (hsql/call :date_trunc b raw-unit) (hsql/raw "day"))))))] - (hsql/call :case (hsql/call :<= x' y') (positive-diff x' y') :else (hx/* -1 (positive-diff y' x')))) + :or + (hsql/call :> (extract :month a) (extract :month b)) + (hsql/call + :and + (hsql/call := (extract :month a) (extract :month b)) + (hsql/call :> (extract :day a) (extract :day b)))))))] + (hsql/call :case (hsql/call :<= x y) (positive-diff x y) :else (hx/* -1 (positive-diff y x)))) + + :month + (let [positive-diff (fn [a b] ; precondition: a <= b + (hx/- + ;; timestamp_diff doesn't support months, so convert to datetime to use datetime_diff + (hsql/call :datetime_diff (hx/->datetime b) (hx/->datetime a) (hsql/raw (name unit))) + (hx/cast :integer (hsql/call :> (extract :day a) (extract :day b)))))] + (hsql/call :case (hsql/call :<= x y) (positive-diff x y) :else (hx/* -1 (positive-diff y x)))) :week - (let [x' (trunc :day (hx/->timestamp x')) - y' (trunc :day (hx/->timestamp y')) + (let [x (trunc :day x) + y (trunc :day y) positive-diff (fn [a b] (hx/cast :integer (hx/floor (hx// (hsql/call :timestamp_diff b a (hsql/raw "day")) 7))))] - (hsql/call :case (hsql/call :<= x' y') (positive-diff x' y') :else (hx/* -1 (positive-diff y' x')))) + (hsql/call :case (hsql/call :<= x y) (positive-diff x y) :else (hx/* -1 (positive-diff y x)))) :day - (let [x' (trunc :day (hx/->timestamp x')) - y' (trunc :day (hx/->timestamp y'))] - (hsql/call :timestamp_diff y' x' (hsql/raw (name unit)))) + (let [x (trunc :day x) + y (trunc :day y)] + (hsql/call :timestamp_diff y x (hsql/raw (name unit)))) (:hour :minute :second) - (let [x' (hx/->timestamp x') - y' (hx/->timestamp y')] - (hsql/call :timestamp_diff y' x' (hsql/raw (name unit))))))) + (hsql/call :timestamp_diff y x (hsql/raw (name unit)))))) (defmethod driver/escape-alias :bigquery-cloud-sdk [driver s] diff --git a/test/metabase/query_processor_test/date_time_zone_functions_test.clj b/test/metabase/query_processor_test/date_time_zone_functions_test.clj index 09444fbd5d4fcd6c91df15e9e3af25759f809190..f57c070278e8178a70a0198b969d507c3b3de955 100644 --- a/test/metabase/query_processor_test/date_time_zone_functions_test.clj +++ b/test/metabase/query_processor_test/date_time_zone_functions_test.clj @@ -665,6 +665,8 @@ first))] (doseq [[unit cases] [[:year [["2021-10-03T09:18:09" "2022-10-02T09:18:09" 0 "day under a year"] ["2021-10-03T09:19:09" "2022-10-03T09:18:09" 1 "ignores time"] + ["2016-02-03T09:19:09" "2017-02-02T09:19:09" 0 "starts in leap year before leap day"] + ["2016-10-03T09:19:09" "2017-10-03T09:19:09" 1 "starts in leap year after leap day"] ["2017-06-10T08:30:00" "2019-07-10T08:30:00" 2 "multiple years"]]] [:month [["2022-10-03T09:18:09" "2022-11-02T09:18:09" 0 "day under a month"] ["2022-10-02T09:19:09" "2022-11-02T09:18:09" 1 "minute under a month"]