为您的驱动程序实现多方法
实现多方法让您可以通过扩展这些方法以适用于您的特定数据库,从而利用 Metabase 现有的驱动程序代码。
首先,让我们关注我们的 Fox Pro '98 的主驱动程序文件 src/metabase/driver/foxpro98.clj
。看看这段示例代码
;; Define a namespace for the driver
(ns com.mycompany.metabase.driver.foxpro98
(:require [metabase.driver :as driver]))
;; Can you include a different method here as an example?
(defmethod driver/display-name :foxpro98 [_]
"Visual FoxPro '98")
让我们逐步了解每个代码块。
驱动程序命名空间
;; Define a namespace for the driver
(ns com.mycompany.metabase.driver.foxpro98
(:require [metabase.driver :as driver]))
每个 Metabase 驱动程序都位于其自己的命名空间中
在这种情况下,命名空间是com.mycompany.metabase.driver.foxpro98
。所有核心 Metabase 驱动程序都位于 metabase.driver.<name-goes-here>
命名空间中。最好使用符合 Java 包命名约定的名称。
许多驱动程序进一步细分为其他命名空间
尤其是较大的驱动程序。通常,驱动程序会有一个 query-processor
命名空间(例如,com.mycompany.metabase.driver.foxpro98.query-processor
),其中包含将 MBQL 查询(使用 Metabase 的图形查询构建器构建的查询)转换为原生查询(如 SQL)的逻辑。查询处理器通常是驱动程序中最复杂的部分,因此将该逻辑分开可以使工作更容易。一些驱动程序还有一个单独的 sync
命名空间,其中包含 Metabase 的数据库同步使用的方法的实现。
驱动程序初始化
所有驱动程序都可以包含额外的代码,以便在使用 metabase.driver/initialize!
初始化驱动程序(即在驱动程序首次建立与数据库的连接之前)时执行一次(且仅执行一次)。(实际上,Metabase 使用 metabase.driver/initialize!
来延迟加载驱动程序。)只有少数情况下您应该使用 metabase.driver/initialize
,例如分配资源或设置某些系统属性。
metabase.driver
多方法
metabase.driver
命名空间定义了一系列多方法,驱动程序为它们提供实现,如我们的示例中所示
(defmethod driver/display-name :foxpro98 [_]
"Visual FoxPro '98")
上述 Metabase 驱动程序的四个主要功能都是通过多方法实现的。这些方法在驱动程序的关键字 :foxpro98
(在我们的例子中)上分派。实际上,这就是 Metabase 驱动程序的全部——一个关键字!看不到任何类或对象——只有一个关键字。
您可以浏览 metabase.driver
命名空间以获得您可以实现的多方法的完整列表。阅读每种方法的文档字符串,并决定是否需要实现它。大多数方法是可选的。
列出可用的驱动程序多方法
要快速查找所有驱动程序多方法的列表,您可以运行命令
clojure -M:run driver-methods
这将打印所有驱动程序命名空间和多方法的列表。这包括许多像 sql
和 sql-jdbc
多方法,以及测试扩展多方法。
如果您还想查看这些方法的文档字符串,请运行
clojure -M:run driver-methods docs
父驱动程序
许多驱动程序共享实现细节,并且为同步方法等编写完整的实现将涉及大量重复代码。因此,许多高级功能部分或全部在共享的“父”驱动程序中实现,例如最常见的父驱动程序 :sql-jdbc
。“父”驱动程序类似于面向对象编程中的超类。
您可以通过在插件清单中列出父驱动程序来定义驱动程序父驱动程序。
像 :sql-jdbc
这样的父驱动程序旨在作为驱动程序的通用抽象“基类”,这些驱动程序可以共享其大部分实现;对于 :sql-jdbc
,它适用于在底层使用 JDBC 驱动程序的基于 SQL 的驱动程序。:sql-jdbc
和其他父驱动程序为驱动 Metabase 驱动程序的四个主要功能所需的许多方法提供了实现。实际上,:sql-jdbc
提供了诸如 driver/execute-prepared-statement!
之类的实现,因此使用它作为父驱动程序的驱动程序不需要自己提供一个。但是,各种父驱动程序定义了自己的多方法来实现。
值得注意的父驱动程序
这些父驱动程序非常重要。
:sql-jdbc
可以用作具有 JDBC 驱动程序的基于 SQL 的数据库的父驱动程序。:sql-jdbc
实现了四个主要功能中的大部分,但您必须实现metabase.driver.sql-jdbc.*
命名空间中的sql-jdbc
多方法,以及metabase.driver.sql.*
命名空间中的一些方法。
:sql
本身是:sql-jdbc
的父驱动程序;它可以用于没有 JDBC 驱动程序的基于 SQL 的数据库,例如 BigQuery。:sql
实现了驱动程序功能的重要部分,但您必须实现metabase.driver.sql.*
命名空间中的一些方法才能使用它。
- 一些驱动程序使用其他“具体”驱动程序作为其父驱动程序——例如,
:redshift
使用:postgres
作为父驱动程序,仅提供方法实现来覆盖在需要时覆盖 postgres 的方法。
调用父驱动程序实现
您可以使用 get-method
获取父驱动程序的方法实现
(defmethod driver/mbql->native :bigquery [driver query]
((get-method driver/mbql-native :sql) driver query))
这相当于在面向对象编程中调用 super.someMethod()
。
您必须按原样将驱动程序参数传递给父实现,以便该方法调用的任何方法都使用正确的实现。以下是您应避免的两种调用父驱动程序的方式
(defmethod driver/mbql->native :bigquery [_ query]
;; BAD! If :sql's implementation of mbql->native calls any other methods, it won't use the :bigquery implementation
((get-method driver/mbql->native :sql) :sql query))
也要避免
(defmethod driver/mbql->native :bigquery [_ query]
;; BAD! If someone else creates a driver using :bigquery as a parent, any methods called by :sql's implementation
;; of mbql->native will use :bigquery method implementations instead of custom ones for that driver
((get-method driver/mbql->native :sql) :bigquery query))
多个父驱动程序
精明的读者可能已经注意到 BigQuery 被提到同时具有 :sql
和 :google
作为父驱动程序。这种多重继承是被允许且有帮助的!您可以按如下方式定义具有多个父驱动程序的驱动程序
(driver/register! :bigquery, :parent #{:sql :google})
在某些情况下,两个父驱动程序都可能为方法提供实现;要解决此歧义,只需为您的驱动程序提供实现,并将它们传递给首选的父驱动程序的实现,如上所述。
对于作为插件交付的驱动程序,您将在插件清单中注册方法。
从 REPL 和 CIDER 中使用驱动程序
必须在本地安装 metabase-core
并构建驱动程序 uberjar 会很麻烦,特别是如果您必须重复它来测试每个更改。幸运的是,您可以像所有内容都是一个大型项目的一部分一样运行命令
启动 REPL。
clojure -A:dev:drivers:drivers-dev
您需要重建驱动程序并将其安装在您的 ./plugins
目录中,并在进行更改时重新启动 Metabase。