Middleware that formats the results of a query. Currently, the only thing this does is convert datetime types to ISO-8601 strings in the appropriate timezone. | (ns metabase.query-processor.middleware.format-rows (:require [java-time.api :as t] [metabase.query-processor.timezone :as qp.timezone] [metabase.util.date-2 :as u.date] [metabase.util.log :as log] [metabase.util.performance :as perf] [potemkin.types :as p.types]) (:import (java.time Instant LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime ZoneId))) |
Protocol for determining how QP results of various classes are serialized. Drivers can add implementations to support custom driver types as needed. | (p.types/defprotocol+ ^:private FormatValue (format-value [v ^ZoneId timezone-id] "Serialize a value in the QP results. You can add impementations for driver-specific types as needed.")) |
(extend-protocol FormatValue nil (format-value [_ _] nil) Object (format-value [v _] v) LocalTime (format-value [t timezone-id] (t/format :iso-offset-time (u.date/with-time-zone-same-instant t timezone-id))) OffsetTime (format-value [t timezone-id] (t/format :iso-offset-time (u.date/with-time-zone-same-instant t timezone-id))) LocalDate (format-value [t timezone-id] (t/format :iso-offset-date-time (u.date/with-time-zone-same-instant t timezone-id))) LocalDateTime (format-value [t timezone-id] (t/format :iso-offset-date-time (u.date/with-time-zone-same-instant t timezone-id))) ;; convert to a ZonedDateTime Instant (format-value [t timezone-id] (format-value (t/zoned-date-time t (t/zone-id "UTC")) timezone-id)) OffsetDateTime (format-value [t, ^ZoneId timezone-id] (t/format :iso-offset-date-time (u.date/with-time-zone-same-instant t timezone-id))) ZonedDateTime (format-value [t timezone-id] (t/format :iso-offset-date-time (u.date/with-time-zone-same-instant t timezone-id)))) | |
(defn- format-rows-xform [rf metadata] {:pre [(fn? rf)]} (log/debugf "Formatting rows with results timezone ID %s" (qp.timezone/results-timezone-id)) (let [timezone-id (t/zone-id (qp.timezone/results-timezone-id)) ;; a column will have `converted_timezone` metadata if it is the result of `convert-timezone` expression ;; in that case, we'll format the results with the target timezone. ;; Otherwise format it with results-timezone cols-zone-id (perf/mapv #(t/zone-id (get % :converted_timezone timezone-id)) (:cols metadata))] (fn ([] (rf)) ([result] (rf result)) ([result row] (rf result (perf/mapv format-value row cols-zone-id)))))) | |
Format individual query result values as needed. Ex: format temporal values as ISO-8601 strings w/ timezone offset. | (defn format-rows [{{:keys [format-rows?] :or {format-rows? true}} :middleware, :as _query} rff] (fn format-rows-rff* [metadata] ;; always assoc `:format-rows?` into the metadata so that ;; the `qp.si/streaming-results-writer` implmementations can apply/not-apply formatting based on the key's value (let [metadata (assoc metadata :format-rows? format-rows?)] (if format-rows? (format-rows-xform (rff metadata) metadata) (rff metadata))))) |