为你的驱动实现多方法
通过为你的特定数据库扩展现有方法,实现多方法可以让你利用 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 初始化驱动时(即在驱动首次建立数据库连接之前)使用 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
命名空间,以获取你可以实现的多方法的完整列表。阅读每个方法的 docstring 并决定是否需要实现它。大多数方法是可选的。
列出可用的驱动多方法
要快速查找所有驱动多方法的列表,你可以运行命令
clojure -M:run driver-methods
它将打印所有驱动命名空间和多方法的列表。这包括许多内容,如 sql
和 sql-jdbc
多方法,以及测试扩展多方法。
如果你也想查看方法的文档字符串,请运行
clojure -M:run driver-methods docs
父驱动
许多驱动共享实现细节,为同步方法等编写完整的实现会涉及大量重复代码。因此,**许多高级功能在共享的“父”驱动(例如最常见的父驱动 :sql-jdbc
)中部分或完全实现**。“父”驱动类似于面向对象编程中的超类。
你可以通过在插件清单中列出父驱动来定义驱动父级。
像 :sql-jdbc
这样的父级旨在作为可以共享大部分实现的驱动的通用抽象“基类”;对于 :sql-jdbc
的情况,它适用于在底层使用 JDBC 驱动的基于 SQL 的驱动。:sql-jdbc
和其他父级提供了驱动程序四个主要功能所需的大多数方法的实现。实际上,: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。
阅读其他版本的 Metabase 的文档。