diff --git a/resources/metabase-plugin.yaml b/resources/metabase-plugin.yaml index ea2ad0a..ebb0fff 100644 --- a/resources/metabase-plugin.yaml +++ b/resources/metabase-plugin.yaml @@ -1,6 +1,6 @@ info: name: Metabase FirebirdSQL Driver - version: 1.0.0 + version: 1.6.0 description: Allows Metabase to connect to FirebirdSQL databases. driver: name: firebird diff --git a/src/metabase/driver/firebird.clj b/src/metabase/driver/firebird.clj index 9ab5f5f..08ae181 100644 --- a/src/metabase/driver/firebird.clj +++ b/src/metabase/driver/firebird.clj @@ -3,8 +3,7 @@ [set :as set] [string :as str]] [clojure.java.jdbc :as jdbc] - [honeysql.core :as hsql] - [honeysql.format :as hformat] + [honey.sql :as hsql] [java-time :as t] [metabase.driver :as driver] [metabase.driver.common :as driver.common] @@ -15,13 +14,13 @@ [sync :as sql-jdbc.sync]] [metabase.driver.sql-jdbc.sync.common :as sql-jdbc.sync.common] [metabase.driver.sql.query-processor :as sql.qp] - [metabase.util - [honeysql-extensions :as hx]] + [metabase.util.honey-sql-2 :as hx] [metabase.util.ssh :as ssh]) (:import [java.sql DatabaseMetaData Time] [java.time LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime] [java.sql Connection DatabaseMetaData ResultSet])) +(set! *warn-on-reflection* true) (driver/register! :firebird, :parent :sql-jdbc) @@ -35,6 +34,11 @@ :subname (str "//" host ":" port "/" db jdbc-flags)} (dissoc opts :host :port :db :jdbc-flags))) +;; use Honey SQL 2 +(defmethod sql.qp/honey-sql-version :firebird + [_driver] + 2) + ;; Obtain connection properties for connection to a Firebird database. (defmethod sql-jdbc.conn/connection-details->spec :firebird [_ details] (-> details @@ -121,7 +125,7 @@ ;; Convert unix time to a timestamp (defmethod sql.qp/unix-timestamp->honeysql [:firebird :seconds] [_ _ expr] - (hsql/call :DATEADD (hsql/raw "SECOND") expr (hx/cast :TIMESTAMP (hx/literal "01-01-1970 00:00:00")))) + [:DATEADD [:raw "SECOND"] expr (hx/cast :TIMESTAMP (hx/literal "01-01-1970 00:00:00"))]) ;; Helpers for Date extraction ;; TODO: This can probably simplified a lot by using String concentation instead of @@ -147,7 +151,7 @@ 5 :YEAR)) ;; Replace the specified part of the timestamp (defn- replace-timestamp-part [input unit expr] - (hsql/call :replace input (hx/literal (get-unit-placeholder unit)) (hsql/call :extract unit expr))) + [:replace input (hx/literal (get-unit-placeholder unit)) [:extract unit expr]]) (defn- format-step [expr input step wanted-unit] (if (> step wanted-unit) @@ -168,64 +172,56 @@ (defmethod sql.qp/date [:firebird :default] [_ _ expr] expr) ;; Cast to TIMESTAMP if we need minutes or hours, since expr might be a DATE (defmethod sql.qp/date [:firebird :minute] [_ _ expr] (timestamp-trunc (hx/cast :TIMESTAMP expr) "YYYY-MM-DD hh:mm:00" 1)) -(defmethod sql.qp/date [:firebird :minute-of-hour] [_ _ expr] (hsql/call :extract :MINUTE (hx/cast :TIMESTAMP expr))) +(defmethod sql.qp/date [:firebird :minute-of-hour] [_ _ expr] [:extract :MINUTE (hx/cast :TIMESTAMP expr)]) (defmethod sql.qp/date [:firebird :hour] [_ _ expr] (timestamp-trunc (hx/cast :TIMESTAMP expr) "YYYY-MM-DD hh:00:00" 2)) -(defmethod sql.qp/date [:firebird :hour-of-day] [_ _ expr] (hsql/call :extract :HOUR (hx/cast :TIMESTAMP expr))) +(defmethod sql.qp/date [:firebird :hour-of-day] [_ _ expr] [:extract :HOUR (hx/cast :TIMESTAMP expr)]) ;; Cast to DATE to get rid of anything smaller than day (defmethod sql.qp/date [:firebird :day] [_ _ expr] (hx/cast :DATE expr)) ;; Firebird DOW is 0 (Sun) - 6 (Sat); increment this to be consistent with Java, H2, MySQL, and Mongo (1-7) -(defmethod sql.qp/date [:firebird :day-of-week] [_ _ expr] (hx/+ (hsql/call :extract :WEEKDAY (hx/cast :DATE expr)) 1)) -(defmethod sql.qp/date [:firebird :day-of-month] [_ _ expr] (hsql/call :extract :DAY expr)) +(defmethod sql.qp/date [:firebird :day-of-week] [_ _ expr] (hx/+ [:extract :WEEKDAY (hx/cast :DATE expr)] 1)) +(defmethod sql.qp/date [:firebird :day-of-month] [_ _ expr] [:extract :DAY expr]) ;; Firebird YEARDAY starts from 0; increment this -(defmethod sql.qp/date [:firebird :day-of-year] [_ _ expr] (hx/+ (hsql/call :extract :YEARDAY expr) 1)) +(defmethod sql.qp/date [:firebird :day-of-year] [_ _ expr] (hx/+ [:extract :YEARDAY expr] 1)) ;; Cast to DATE because we do not want units smaller than days -;; Use hsql/raw for DAY in dateadd because the keyword :WEEK gets surrounded with quotations -(defmethod sql.qp/date [:firebird :week] [_ _ expr] (hsql/call :dateadd (hsql/raw "DAY") (hx/- 0 (hsql/call :extract :WEEKDAY (hx/cast :DATE expr))) (hx/cast :DATE expr))) -(defmethod sql.qp/date [:firebird :week-of-year] [_ _ expr] (hsql/call :extract :WEEK expr)) +;; Use :raw for DAY in dateadd because the keyword :WEEK gets surrounded with quotations +(defmethod sql.qp/date [:firebird :week] [_ _ expr] [:dateadd [:raw "DAY"] (hx/- 0 [:extract :WEEKDAY (hx/cast :DATE expr)]) (hx/cast :DATE expr)]) +(defmethod sql.qp/date [:firebird :week-of-year] [_ _ expr] [:extract :WEEK expr]) (defmethod sql.qp/date [:firebird :month] [_ _ expr] (date-trunc expr "YYYY-MM-01" 4)) -(defmethod sql.qp/date [:firebird :month-of-year] [_ _ expr] (hsql/call :extract :MONTH expr)) -;; Use hsql/raw for MONTH in dateadd because the keyword :MONTH gets surrounded with quotations -(defmethod sql.qp/date [:firebird :quarter] [_ _ expr] (hsql/call :dateadd (hsql/raw "MONTH") (hx/* (hx// (hx/- (hsql/call :extract :MONTH expr) 1) 3) 3) (date-trunc expr "YYYY-01-01" 5))) -(defmethod sql.qp/date [:firebird :quarter-of-year] [_ _ expr] (hx/+ (hx// (hx/- (hsql/call :extract :MONTH expr) 1) 3) 1)) -(defmethod sql.qp/date [:firebird :year] [_ _ expr] (hsql/call :extract :YEAR expr)) +(defmethod sql.qp/date [:firebird :month-of-year] [_ _ expr] [:extract :MONTH expr]) +;; Use :raw for MONTH in dateadd because the keyword :MONTH gets surrounded with quotations +(defmethod sql.qp/date [:firebird :quarter] [_ _ expr] [:dateadd [:raw "MONTH"] (hx/* (hx// (hx/- [:extract :MONTH expr] 1) 3) 3) (date-trunc expr "YYYY-01-01" 5)]) +(defmethod sql.qp/date [:firebird :quarter-of-year] [_ _ expr] (hx/+ (hx// (hx/- [:extract :MONTH expr] 1) 3) 1)) +(defmethod sql.qp/date [:firebird :year] [_ _ expr] [:extract :YEAR expr]) ;; Firebird 2.x doesn't support TRUE/FALSE, replacing them with 1 and 0 (defmethod sql.qp/->honeysql [:firebird Boolean] [_ bool] (if bool 1 0)) +;; TODO / Help Wanted: ;; Firebird 2.x doesn't support SUBSTRING arugments seperated by commas, but uses FROM and FOR keywords -(defmethod sql.qp/->honeysql [:firebird :substring] - [driver [_ arg start length]] - (let [col-name (hformat/to-sql (sql.qp/->honeysql driver arg))] - (if length - (reify - hformat/ToSql - (to-sql [_] - (str "substring(" col-name " FROM " start " FOR " length ")"))) - (reify - hformat/ToSql - (to-sql [_] - (str "substring(" col-name " FROM " start ")")))))) +;(defmethod sql.qp/->honeysql [:firebird :substring] +; [driver [_ arg start length]] +; (let [col-name (hformat/to-sql (sql.qp/->honeysql driver arg))] +; (if length +; (reify +; hformat/ToSql +; (to-sql [_] +; (str "substring(" col-name " FROM " start " FOR " length ")"))) +; (reify +; hformat/ToSql +; (to-sql [_] +; (str "substring(" col-name " FROM " start ")")))))) (defmethod sql.qp/add-interval-honeysql-form :firebird [driver hsql-form amount unit] (if (= unit :quarter) (recur driver hsql-form (hx/* amount 3) :month) - (hsql/call :dateadd (hsql/raw (name unit)) amount hsql-form))) + [:dateadd [:raw (name unit)] amount hsql-form])) (defmethod sql.qp/current-datetime-honeysql-form :firebird [_] (hx/cast :timestamp (hx/literal :now))) -(defmethod driver.common/current-db-time-date-formatters :firebird [_] - (driver.common/create-db-time-formatters "yyyy-MM-dd HH:mm:ss.SSSS")) - -(defmethod driver.common/current-db-time-native-query :firebird [_] - "SELECT CAST(CAST('Now' AS TIMESTAMP) AS VARCHAR(24)) FROM RDB$DATABASE") - -(defmethod driver/current-db-time :firebird [& args] - (apply driver.common/current-db-time args)) - (defmethod sql.qp/->honeysql [:firebird :stddev] [driver [_ field]] - (hsql/call :stddev_samp (sql.qp/->honeysql driver field))) + [:stddev_samp (sql.qp/->honeysql driver field)]) ;; MEGA HACK based on sqlite driver @@ -262,20 +258,16 @@ (sql.qp/->honeysql driver (t/local-date t)) (hx/cast :TIMESTAMP (t/format "yyyy-MM-dd HH:mm:ss.SSSS" t)))) -(defmethod driver/supports? [:firebird :basic-aggregations] [_ _] true) - -(defmethod driver/supports? [:firebird :expression-aggregations] [_ _] true) - -(defmethod driver/supports? [:firebird :standard-deviation-aggregations] [_ _] true) - -(defmethod driver/supports? [:firebird :foreign-keys] [_ _] true) - -(defmethod driver/supports? [:firebird :nested-fields] [_ _] false) - -(defmethod driver/supports? [:firebird :set-timezone] [_ _] false) - -(defmethod driver/supports? [:firebird :nested-queries] [_ _] true) - -(defmethod driver/supports? [:firebird :binning] [_ _] false) - -(defmethod driver/supports? [:firebird :case-sensitivity-string-filter-options] [_ _] false) +(doseq [[feature supported?] {; supported + :basic-aggregations true + :expression-aggregations true + :foreign-keys true + :nested-queries true + :standard-deviation-aggregations true + ; not supported + :binning false + :case-sensitivity-string-filter-options false + :nested-fields false + :schemas false + :set-timezone false}] + (defmethod driver/database-supports? [:firebird feature] [_driver _feature _db] supported?))