Skip to content
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

Feature/metabase 48 #21

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion resources/metabase-plugin.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
110 changes: 51 additions & 59 deletions src/metabase/driver/firebird.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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)

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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?))