-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Snowflake/Redshift filtering on date for timestamp with time zone columns #41864
Changes from 2 commits
f24d35e
994c6e2
b7e3d1c
e97ab7d
589d38a
a659256
2b10084
d8652ba
5aca3c3
3759a5a
11bfe63
3e0903e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,8 +41,8 @@ | |
[ring.util.codec :as codec]) | ||
(:import | ||
(java.io File) | ||
(java.sql Connection DatabaseMetaData ResultSet Types) | ||
(java.time OffsetDateTime OffsetTime ZonedDateTime) | ||
(java.sql Connection DatabaseMetaData ResultSet ResultSetMetaData Types) | ||
(java.time LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime) | ||
(net.snowflake.client.jdbc SnowflakeSQLException))) | ||
|
||
(set! *warn-on-reflection* true) | ||
|
@@ -194,7 +194,7 @@ | |
:DATETIME :type/DateTime | ||
:TIME :type/Time | ||
:TIMESTAMP :type/DateTime | ||
:TIMESTAMPLTZ :type/DateTime | ||
:TIMESTAMPLTZ :type/DateTimeWithLocalTZ | ||
:TIMESTAMPNTZ :type/DateTime | ||
:TIMESTAMPTZ :type/DateTimeWithTZ | ||
:VARIANT :type/* | ||
|
@@ -413,6 +413,31 @@ | |
[driver [_ amount unit]] | ||
(qp.relative-datetime/maybe-cacheable-relative-datetime-honeysql driver unit amount)) | ||
|
||
(defmethod sql.qp/->honeysql [:snowflake LocalDate] | ||
[_driver t] | ||
[:raw (format "'%s'::date" (t/local-date t))]) | ||
|
||
(defmethod sql.qp/->honeysql [:snowflake LocalTime] | ||
[_driver t] | ||
[:raw (format "'%s'::time" (t/local-time t))]) | ||
|
||
;;; Snowflake doesn't have `timetz`, so just convert to an equivalent local time. | ||
(defmethod sql.qp/->honeysql [:snowflake OffsetTime] | ||
[driver t] | ||
(sql.qp/->honeysql driver (t/local-time (t/with-offset-same-instant t (t/zone-offset 0))))) | ||
|
||
(defmethod sql.qp/->honeysql [:snowflake LocalDateTime] | ||
[_driver t] | ||
[:raw (format "'%s %s'::timestamp_ntz" (t/local-date t) (t/local-time t))]) | ||
|
||
(defmethod sql.qp/->honeysql [:snowflake OffsetDateTime] | ||
[_driver t] | ||
[:raw (format "'%s %s %s'::timestamp_ltz" (t/local-date t) (t/local-time t) (t/zone-offset t))]) | ||
|
||
(defmethod sql.qp/->honeysql [:snowflake ZonedDateTime] | ||
[driver t] | ||
(sql.qp/->honeysql driver (t/offset-date-time t))) | ||
|
||
(defmethod driver/table-rows-seq :snowflake | ||
[driver database table] | ||
(sql-jdbc/query driver database {:select [:*] | ||
|
@@ -576,65 +601,11 @@ | |
(m/dissoc-in [:details :regionid])) | ||
database)) | ||
|
||
(defmethod unprepare/unprepare-value [:snowflake OffsetDateTime] | ||
[_ t] | ||
(format "'%s %s %s'::timestamp_tz" (t/local-date t) (t/local-time t) (t/zone-offset t))) | ||
|
||
(defmethod unprepare/unprepare-value [:snowflake ZonedDateTime] | ||
[driver t] | ||
(unprepare/unprepare-value driver (t/offset-date-time t))) | ||
|
||
(defmethod unprepare/unprepare-value [:snowflake OffsetTime] | ||
[driver t] | ||
(unprepare/unprepare-value driver (t/local-time (t/with-offset-same-instant t (t/zone-offset 0))))) | ||
|
||
;; Like Vertica, Snowflake doesn't seem to be able to return a LocalTime/OffsetTime like everyone else, but it can | ||
;; return a String that we can parse | ||
|
||
(defmethod sql-jdbc.execute/read-column-thunk [:snowflake Types/TIMESTAMP_WITH_TIMEZONE] | ||
[_ ^ResultSet rs _ ^Integer i] | ||
(fn [] | ||
(when-let [s (.getString rs i)] | ||
(let [t (u.date/parse s)] | ||
(log/tracef "(.getString rs %d) [TIMESTAMP_WITH_TIMEZONE] -> %s -> %s" i (pr-str s) (pr-str t)) | ||
t)))) | ||
|
||
(defmethod sql-jdbc.execute/read-column-thunk [:snowflake Types/TIME] | ||
[_ ^ResultSet rs _ ^Integer i] | ||
(fn [] | ||
(when-let [s (.getString rs i)] | ||
(let [t (u.date/parse s)] | ||
(log/tracef "(.getString rs %d) [TIME] -> %s -> %s" i (pr-str s) (pr-str t)) | ||
t)))) | ||
|
||
(defmethod sql-jdbc.execute/read-column-thunk [:snowflake Types/TIME_WITH_TIMEZONE] | ||
[_ ^ResultSet rs _ ^Integer i] | ||
(fn [] | ||
(when-let [s (.getString rs i)] | ||
(let [t (u.date/parse s)] | ||
(log/tracef "(.getString rs %d) [TIME_WITH_TIMEZONE] -> %s -> %s" i (pr-str s) (pr-str t)) | ||
t)))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
;; TODO would it make more sense to use functions like `timestamp_tz_from_parts` directly instead of JDBC parameters? | ||
|
||
;; Snowflake seems to ignore the calendar parameter of `.setTime` and `.setTimestamp` and instead uses the session | ||
;; timezone; normalize temporal values to UTC so we end up with the right values | ||
(defmethod sql-jdbc.execute/set-parameter [::use-legacy-classes-for-read-and-set java.time.OffsetTime] | ||
[driver ps i t] | ||
(sql-jdbc.execute/set-parameter driver ps i (t/sql-time (t/with-offset-same-instant t (t/zone-offset 0))))) | ||
|
||
(defmethod sql-jdbc.execute/set-parameter [::use-legacy-classes-for-read-and-set java.time.OffsetDateTime] | ||
[driver ps i t] | ||
(sql-jdbc.execute/set-parameter driver ps i (t/sql-timestamp (t/with-offset-same-instant t (t/zone-offset 0))))) | ||
|
||
(defmethod sql-jdbc.execute/set-parameter [:snowflake java.time.ZonedDateTime] | ||
[driver ps i t] | ||
(sql-jdbc.execute/set-parameter driver ps i (t/sql-timestamp (t/with-zone-same-instant t (t/zone-id "UTC"))))) | ||
Comment on lines
-621
to
-635
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No longer needed since we're generating SQL literals instead |
||
|
||
;;; --------------------------------------------------- Query remarks --------------------------------------------------- | ||
|
||
; Snowflake strips comments prepended to the SQL statement (default remark injection behavior). We should append the | ||
; remark instead. | ||
;; Snowflake strips comments prepended to the SQL statement (default remark injection behavior). We should append the | ||
;; remark instead. | ||
(defmethod sql-jdbc.execute/inject-remark :snowflake | ||
[_ sql remark] | ||
(str sql "\n\n-- " remark)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
:prepared-statement-args [#t \"2017-01-01\"]}" | ||
(:require | ||
[clojure.string :as str] | ||
[honey.sql :as sql] | ||
[metabase.driver :as driver] | ||
[metabase.driver.common.parameters :as params] | ||
[metabase.driver.common.parameters.dates :as params.dates] | ||
|
@@ -58,7 +59,8 @@ | |
(defn- honeysql->prepared-stmt-subs | ||
"Convert X to a replacement snippet info map by passing it to HoneySQL's `format` function." | ||
[driver x] | ||
(let [[snippet & args] (sql.qp/format-honeysql driver x)] | ||
(let [honeysql (sql.qp/->honeysql driver x) | ||
[snippet & args] (sql.qp/format-honeysql driver honeysql)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parameter compilation code should call |
||
(make-stmt-subs snippet args))) | ||
|
||
(mu/defmethod ->prepared-substitution [:sql nil] :- PreparedStatementSubstitution | ||
|
@@ -81,14 +83,13 @@ | |
[driver kwd] | ||
(honeysql->prepared-stmt-subs driver kwd)) | ||
|
||
;; TIMEZONE FIXME - remove this since we aren't using `Date` anymore | ||
(mu/defmethod ->prepared-substitution [:sql Date] :- PreparedStatementSubstitution | ||
[_driver date] | ||
(make-stmt-subs "?" [date])) | ||
|
||
(mu/defmethod ->prepared-substitution [:sql Temporal] :- PreparedStatementSubstitution | ||
[_driver t] | ||
(make-stmt-subs "?" [t])) | ||
[driver t] | ||
(honeysql->prepared-stmt-subs driver t)) | ||
|
||
(defmulti align-temporal-unit-with-param-type | ||
"Returns a suitable temporal unit conversion keyword for `field`, `param-type` and the given driver. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,29 +60,45 @@ | |
(.setTimestamp ps i t cal))) | ||
|
||
(defmethod sql-jdbc.execute/read-column-thunk [::use-legacy-classes-for-read-and-set Types/TIME] | ||
[_ ^ResultSet rs _ ^Integer i] | ||
[_driver ^ResultSet rs _rsmeta ^Integer i] | ||
(fn [] | ||
(when-let [s (.getString rs i)] | ||
(let [t (u.date/parse s)] | ||
(log/tracef "(.getString rs i) [TIME] -> %s -> %s" (pr-str s) (pr-str t)) | ||
t)))) | ||
|
||
(defmethod sql-jdbc.execute/read-column-thunk [::use-legacy-classes-for-read-and-set Types/TIME_WITH_TIMEZONE] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved from Snowflake |
||
[_driver ^ResultSet rs _rsmeta ^Integer i] | ||
(fn [] | ||
(when-let [s (.getString rs i)] | ||
(let [t (u.date/parse s)] | ||
(log/tracef "(.getString rs i) [TIME_WITH_TIMEZONE] -> %s -> %s" (pr-str s) (pr-str t)) | ||
t)))) | ||
|
||
(defmethod sql-jdbc.execute/read-column-thunk [::use-legacy-classes-for-read-and-set Types/DATE] | ||
[_ ^ResultSet rs _ ^Integer i] | ||
[_driver ^ResultSet rs _rsmeta ^Integer i] | ||
(fn [] | ||
(when-let [s (.getString rs i)] | ||
(let [t (u.date/parse s)] | ||
(log/tracef "(.getString rs i) [DATE] -> %s -> %s" (pr-str s) (pr-str t)) | ||
t)))) | ||
|
||
(defmethod sql-jdbc.execute/read-column-thunk [::use-legacy-classes-for-read-and-set Types/TIMESTAMP] | ||
[_ ^ResultSet rs _ ^Integer i] | ||
[_driver ^ResultSet rs _rsmeta ^Integer i] | ||
(fn [] | ||
(when-let [s (.getString rs i)] | ||
(let [t (u.date/parse s)] | ||
(log/tracef "(.getString rs i) [TIMESTAMP] -> %s -> %s" (pr-str s) (pr-str t)) | ||
t)))) | ||
|
||
(defmethod sql-jdbc.execute/read-column-thunk [::use-legacy-classes-for-read-and-set Types/TIMESTAMP_WITH_TIMEZONE] | ||
[_driver ^ResultSet rs _rsmeta ^Integer i] | ||
(fn [] | ||
(when-let [s (.getString rs i)] | ||
(let [t (u.date/parse s)] | ||
(log/tracef "(.getString rs i) [TIMESTAMP_WITH_TIMEZONE] -> %s -> %s" (pr-str s) (pr-str t)) | ||
t)))) | ||
|
||
(doseq [dispatch-val (keys (methods sql-jdbc.execute/read-column-thunk)) | ||
:when (sequential? dispatch-val) | ||
:let [[driver jdbc-type] dispatch-val] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer needed since we don't parameterize these anymore.