为您的驱动程序实现多方法

实现多方法使您能够通过扩展 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 命名空间以获取您可以实现的多方法的完整列表。阅读每个方法的文档字符串并决定是否需要实现它。大多数方法是可选的。

列出可用的驱动程序多方法

要快速查找所有驱动程序多方法的列表,您可以运行以下命令:

clojure -M:run driver-methods

这将打印所有驱动程序命名空间和多方法的列表。这包括许多内容,例如 sqlsql-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。

阅读其他 Metabase 版本的文档。

© . All rights reserved.