
Metabase 数据库驱动开发基础从四大核心职责到模块化插件化落地【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabaseMetabase 的数据库驱动Driver是其开放架构的根基——它让 Metabase 能以统一的方式连接 SQLite、ClickHouse、MongoDB 乃至自定义数据源。本文以官方《Database driver basics》为骨架结合当前仓库源码系统讲解驱动的四大核心职责、模块/插件二段式组织方式、插件清单manifest语法、multimethod 实现要点与测试扩展机制帮助你从零搭建并打包一个可运行的 Metabase 驱动。读完本文你将掌握Metabase 驱动究竟承担哪些工作、驱动模块的文件组织与依赖声明、如何把驱动构建为插件 JAR 并放入plugins目录、以及如何通过测试扩展让驱动通过 Metabase 的共享测试套件。一个 Metabase 驱动到底做了什么四大核心职责Metabase 官方文档将驱动定义为四个核心职责的总和仓库源码src/metabase/driver.clj中的一系列 multimethod 正是这四大职责的落点为 Metabase 提供数据库的基本信息——包括数据库的能力capabilities、连接属性connection properties等。 对应实现display-namesrc/metabase/driver.clj#L206与connection-propertiessrc/metabase/driver.clj#L618等 multimethod。为 Metabase 提供数据库的 schema 信息——表或等价物、表中的字段、外键关系对支持外键的数据库而言。 该能力服务于 Metabase 的sync 过程详见 数据库同步与扫描同步结果存入应用数据库可视化查询构建器Query Builder等界面正是基于这些元数据向用户展示可用的表/列。把 Metabase 自研的查询语言 MBQLMetabase BI Query Language编译为原生查询。 可视化查询构建器生成 MBQL 查询Metabase 的query processor负责把 MBQL 翻译成原生查询。对应 multimethodmbql-nativesrc/metabase/driver.clj#L1135其 docstring 明确要求返回符合:metabase.query-processor.compile/compiledschema 的编译结果例如 PostgreSQL 驱动返回{:query SELECT * FROM my_table}形式的 SQL 文本。执行原生查询并返回结果。 对应 multimethodexecute-reducible-querysrc/metabase/driver.clj#L641docstring 要求返回可通过transduce/reduce消费的行数据流并借助metabase.query-processor.reducible/reducible-rows实现流式结果。值得一提的是这四个职责并非每个驱动都要从零实现——绝大多数逻辑由共享的父驱动如:sql-jdbc预先完成驱动作者只需按需覆盖详见后文父驱动一节。把驱动写成模块、打包成插件Metabase 驱动采用模块 插件的二段式组织方式模块module是源码。一个驱动模块就是一个独立的 Clojure 项目目录包含自己的deps.edn、源码、资源文件与测试。插件plugin是从源码构建出的 JAR。一个 Metabase 插件 JAR 内包含编译后的 class 文件以及一份声明插件元数据的 Metabase 插件清单 metabase-plugin.yaml。懒加载机制在绝大多数情况下插件是**懒加载lazily loaded**的Metabase 启动时不会立即初始化驱动而是等到第一次有人尝试连接使用该驱动的数据库时才完成初始化。这能显著缩短启动时间并减少内存占用——这也是官方建议保持lazy-load: true的原因。安装插件放入 plugins 目录要让 Metabase 使用你的驱动只需把构建好的驱动 JAR 放入/plugin目录——该目录位于你运行metabase.jar的同一位置目录结构类似/Users/cam/metabase/metabase.jar /Users/cam/metabase/plugins/my-plugin.jar注意官方文档此处写作/plugin而实际默认目录名为plugins。你可以通过设置环境变量MB_PLUGINS_DIR修改插件目录。MB_PLUGINS_DIR 环境变量关于MB_PLUGINS_DIR仓库的环境变量文档docs/configuring-metabase/environment-variables.md#L3444给出了精确定义类型string默认值plugins语义存放 Metabase 数据库驱动的 plugins 目录路径。运行 JAR 时默认目录是plugins创建于 JAR 文件同一位置以 Docker 方式运行时默认目录为/plugins。运行 Metabase 的用户应拥有对该目录的写权限。用途自定义第三方驱动应放置于此Metabase 启动时会加载该目录下的驱动可在日志中确认加载结果。从当前仓库看真实的驱动模块布局当前仓库中核心的 SQLite 驱动位于 src/metabase/driver/sqlite.clj以(driver/register! :sqlite, :parent #{:sql-jdbc})注册而modules/drivers目录下则聚合了以模块形式发布的第三方/独立驱动athena、bigquery-cloud-sdk、clickhouse、databricks、druid-jdbc、hive-like、mongo、oracle、presto-jdbc、redshift、snowflake、sparksql、sqlserver、starburst、vertica。这些模块通过 modules/drivers/deps.edn 统一聚合每个模块以:local/root形式声明为依赖例如metabase/clickhouse {:local/root clickhouse}。当你把新驱动作为模块合入时同样需要在modules/drivers/deps.edn中登记。移除一个驱动若要从 Metabase 中移除某个驱动模块官方给出了两个步骤删除modules/drivers下对应的驱动文件夹从modules/drivers/deps.edn中删除该驱动的依赖条目。特别注意Postgres、H2、MySQL 三个驱动不可移除——Metabase 需要它们作为应用数据库application database的连接驱动。一个驱动模块的目录解剖以 SQLite 为例官方文档以 SQLite 驱动为例给出了模块的标准目录结构|-- deps.edn |-- resources | -- metabase-plugin.yaml |-- src | -- metabase | -- driver | -- sqlite.clj -- test -- metabase |-- driver | -- sqlite_test.clj -- test -- data -- sqlite.clj对照当前仓库SQLite 驱动的核心实现在 src/metabase/driver/sqlite.clj共 607 行而 ClickHouse 驱动则完整地演示了模块化布局modules/drivers/clickhouse。该目录中三个关键文件值得展开说明deps.edn声明驱动依赖deps.edn指定驱动自身的依赖。以 modules/drivers/clickhouse/deps.edn 为例它声明了{:paths [src resources] :deps {com.clickhouse/clickhouse-jdbc {:mvn/version 0.9.8 :exclusions [org.apache.commons/commons-lang3 org.lz4/lz4-java]} ;; pinned: clickhouse-jdbc pulls httpcore5-h2 5.3.4 org.apache.httpcomponents.core5/httpcore5-h2 {:mvn/version 5.4.3} ;; pinned: clickhouse-jdbc 0.9.8 pulls httpclient5 5.4.4 org.apache.httpcomponents.client5/httpclient5 {:mvn/version 5.6.4} ;; original org.lz4:lz4-java project is discontinued, this is the maintained fork at.yawk.lz4/lz4-java {:mvn/version 1.11.1}}}从中可以看到 JDBC 驱动的典型做法引入对应的 JDBC 客户端依赖此处为com.clickhouse/clickhouse-jdbc并根据需要固定被间接引入的传递依赖版本。:paths [src resources]声明了源码目录与资源目录后者即metabase-plugin.yaml所在位置。resources/metabase-plugin.yaml驱动清单你的驱动的插件清单包含关于驱动的详细信息名称、版本、父驱动、连接属性、初始化步骤等Metabase 启动时遍历插件目录下每个 JAR 并读取这份清单。其完整语法将在下文专节展开。src/metabase/driver/sqlite.clj驱动核心文件这是驱动的主文件。当前仓库中的 src/metabase/driver/sqlite.clj 是一个教科书级的例子(driver/register! :sqlite, :parent #{:sql-jdbc}) (defmethod driver/display-name :sqlite [_driver] SQLite) (defmethod driver/connection-properties :sqlite [_driver] (into [] (mapcat u/one-or-many) [{:name db :display-name (tru Filename) :placeholder /path/to/toucan_sightings.sqlite :required true} driver.common/advanced-options-start driver.common/default-advanced-options]))关键点driver/register!注册驱动关键字:sqlite并声明父驱动:sql-jdbcdisplay-name返回管理界面展示的名称connection-properties返回连接表单需要用户填写的属性列表此处是db文件名必填并追加公共的高级选项分节。驱动文件内还通过doseq批量声明能力开关例如(doseq [[feature supported?] {:right-join false :full-join false :regex false :percentile-aggregations false :schemas false :datetime-diff true :expression-literals true :now true ...}] (defmethod driver/database-supports? [:sqlite feature] [_driver _feature _db] supported?))这对应 multimethoddatabase-supports?src/metabase/driver.clj#L1037SQLite 不支持的功能如 right join、regex、schemas显式置为false避免在界面上暴露不可用的选项。插件清单metabase-plugin.yaml详解插件 JAR 的根目录包含一份名为metabase-plugin.yaml的插件清单。Metabase 启动时会遍历 plugins 目录下每个 JAR寻找其中的清单据此得知插件提供了什么以及如何初始化它。以下官方示例完整保留info: name: Metabase SQLite Driver version: 1.0.0-SNAPSHOT-3.25.2 description: Allows Metabase to connect to SQLite databases. contact-info: name: Toucan McBird address: toucan.mcbirdexample.com driver: name: sqlite display-name: SQLite lazy-load: true parent: sql-jdbc connection-properties: - name: db display-name: Filename placeholder: /home/camsaul/toucan_sightings.sqlite required: true init: - step: load-namespace namespace: metabase.driver.sqlite - step: register-jdbc-driver class: org.sqlite.JDBCdriver一节告诉 Metabase插件定义了一个名为:sqlite、父驱动为:sql-jdbc的驱动。插件系统据此调用driver/register!并利用display-name与connection-properties自动为驱动生成对应 multimethod 的实现——这正是 src/metabase/driver.clj#L206 的 docstring 中所说lazy-loaded driver 会在插件清单中声明、由lazy-loaded-driver自动创建实现的机制。懒加载上例中驱动被标记为lazy-load: trueMetabase 启动时只创建方法实现真正的初始化加载命名空间、注册 JDBC 驱动等推迟到第一次连接使用该驱动的数据库时才发生。你可以但不应该把驱动设为lazy-load: false代价是 Metabase 启动更慢、占用更多内存。初始化步骤initMetabase 会按需自动初始化插件流程为把驱动加入 classpath然后按顺序执行清单中每个init步骤。常见步骤有两种load-namespace以 Clojure 标准require方式加载驱动命名空间namespace: metabase.driver.sqlite。如果你的驱动实现分散在多个命名空间需要确保它们一并被加载——可以在主命名空间的:require中引用也可以添加多个load-namespace步骤。register-jdbc-driver为基于 JDBC 的驱动注册底层 JDBC 驱动类class: org.sqlite.JDBC。register-jdbc-driver 背后的原理官方文档解释了register-jdbc-driver存在的深层原因Java 的 JDBCDriverManager只使用由系统ClassLoader加载的 JDBC 驱动而系统 classloader 不允许在运行时加载新的 classpathMetabase 因此使用自定义ClassLoader初始化插件。为了解决这个限制Metabase 内置了一个 JDBC 代理驱动类可以包装其他 JDBC 驱动——调用register-jdbc-driver时Metabase 实际注册的是该代理类的新实例它把方法调用转发给真正的 JDBC 驱动而DriverManager对此完全兼容。依赖声明dependencies清单可选地声明插件依赖只有全部依赖满足时插件才会被初始化class依赖检查某个类是否存在于 classpath不初始化类仅做可用性检查。不要用它检查插件自身打包的类只用于外部依赖。可附带message用于日志提示。plugin依赖检查某个插件是否可用值为目标插件清单中的name必须完全匹配。若依赖的插件尚未加载Metabase 会在后续插件加载完成后重试——例如 BigQuery 驱动依赖共享的 Google 驱动即使 BigQuery 先被尝试加载等 Google 驱动就绪后 Metabase 也会检测到依赖满足并完成初始化。完整带注释的清单参考官方文档提供了一份带详细注释的完整清单逐字段说明写法和默认值# 面向用户的基础信息放在 info: 下 info: name: Metabase SQLite Driver # 插件名称 version: 1.0.0-SNAPSHOT-3.25.2 # 建议遵循语义化版本可在 patch 位附带主要依赖版本 description: Allows Metabase to connect to SQLite databases. dependencies: # 可选全部满足才初始化插件 - class: oracle.jdbc.OracleDriver # 检查 classpath 中是否存在该类 message: # 可选的提示信息写入日志 Metabase requires the Oracle JDBC driver to connect to JDBC databases. - plugin: Metabase SQLHeavy Driver # 检查同名插件是否已加载 driver: # 插件定义的驱动 name: sqlite # 驱动关键字如 :sqlite display-name: SQLite # 管理员连接数据库时看到的名称 lazy-load: true # 默认 true除非必要不要设为 false parent: sql-jdbc # 父驱动也可用列表声明多父 # parent: # - google # - sql abstract: false # 是否抽象驱动默认 false connection-properties: # 连接时向用户询问的属性 - dbname # 引用 metabase.driver.common 中的默认属性按名引用 - host - name: db # 或使用完整 map 自定义属性 display-name: Filename placeholder: /home/camsaul/toucan_sightings.sqlite required: true - merge: # 用 merge: 合并多个 map便于覆盖默认属性的细节 - port - placeholder: 1433 init: # 插件初始化步骤懒加载驱动会延迟到首次连接 - step: load-namespace # require 一个 JAR 内的命名空间 namespace: metabase.driver.sqlite - step: register-jdbc-driver # 注册将被该驱动使用的 JDBC 驱动实际注册代理驱动 class: org.sqlite.JDBC关于connection-properties的更多细节src/metabase/driver.clj#L618 的 docstring 指出每个属性必须符合ConnectionDetailsPropertyschemaname、display-name、placeholder、required?、options等可选键并建议优先复用metabase.driver.common中预定义的公共属性如default-host-details、default-port-details。实现驱动 multimethod驱动本质上只是一个关键字实现 multimethod 让你得以复用 Metabase 现成的驱动代码只针对你的数据库做差异化的扩展。以官方示例的 Visual Fox Pro 98 驱动为例核心文件src/metabase/driver/foxpro98.clj的内容如下;; 为驱动定义命名空间 (ns com.mycompany.metabase.driver.foxpro98 (:require [metabase.driver :as driver])) ;; 实现 driver/display-name 这个 multimethod (defmethod driver/display-name :foxpro98 [_] Visual FoxPro 98)驱动命名空间规范每个 Metabase 驱动都位于独立的命名空间中。上述例子的命名空间是com.mycompany.metabase.driver.foxpro98核心驱动统一位于metabase.driver.驱动名命名空间如 src/metabase/driver/sqlite.clj 的metabase.driver.sqlite。建议遵循 Java 包命名规范。较大型的驱动常拆出多个命名空间常见的做法是独立的query-processor命名空间如metabase.driver.foxpro98.query-processor存放 MBQL → 原生查询的转换逻辑——查询处理器往往是驱动最复杂的部分单独成文件更易维护部分驱动还有独立的sync命名空间实现数据库同步相关的方法。驱动初始化所有驱动都可以通过metabase.driver/initialize!挂载一段只执行一次的初始化代码发生在驱动被初始化时即首次连接数据库之前。Metabase 正是借助initialize!实现驱动的懒加载。官方建议仅在确有需要时使用例如分配资源或设置某些系统属性——注意 src/metabase/driver.clj#L204 中:default实现是一个 no-op。metabase.driver 命名空间中的 multimethodmetabase.driver命名空间定义了一系列 multimethod驱动通过defmethod为它们提供实现并按驱动的关键字上述例子中是:foxpro98进行 dispatch。前文提到的四大核心职责全部由这些 multimethod 实现。事实上一个 Metabase 驱动本质上就是一个关键字keyword——没有类、没有对象只有一个关键字加上针对该关键字的若干 multimethod 实现。metabase.driver中绝大多数方法都是可选的阅读每个方法的 docstring 再决定是否需要实现。列出可用的驱动 multimethod快速查看所有驱动 multimethod 列表clojure -M:run driver-methods该命令会打印所有驱动命名空间与 multimethod包括sql、sql-jdbc的方法以及测试扩展方法。若要连同 docstring 一起查看clojure -M:run driver-methods docs父驱动复用现成实现很多驱动共享实现细节若每个驱动都完整实现 sync 等方法会产生大量重复代码因此大量高层功能已在共享的父驱动中部分或全部实现其中最常用的父驱动是:sql-jdbc。父驱动可以类比面向对象编程中的超类superclass。在插件清单中列出父驱动即可声明父子关系。几个重要的父驱动:sql-jdbc适用于底层使用 JDBC 驱动的 SQL 数据库。它实现了大部分核心功能例如driver/execute-prepared-statement!但你需要实现metabase.driver.sql-jdbc.*命名空间中的sql-jdbcmultimethod以及metabase.driver.sql.*命名空间中的部分方法。:sql:sql-jdbc自身的父驱动适用于没有JDBC 驱动的 SQL 数据库如 BigQuery。它实现了大量驱动功能但使用它需要实现metabase.driver.sql.*中的一些方法。具体驱动作为父驱动部分驱动以其他具体驱动为父例如:redshift以:postgres为父只需在需要覆盖的地方提供实现。当前仓库中 SQLite 与 ClickHouse 均以:sql-jdbc为父见 src/metabase/driver/sqlite.clj#L34 与 modules/drivers/clickhouse/src/metabase/driver/clickhouse.clj#L33。调用父驱动实现get-method可以用get-method获取父驱动对某方法的实现等价于 OOP 中的super.someMethod()(defmethod driver/mbql-native :bigquery [driver query] ((get-method driver/mbql-native :sql) driver query))注意必须把 driver 参数原样传给父实现否则父实现内部调用其他方法时会用错实现。以下是两种应避免的写法(defmethod driver/mbql-native :bigquery [_ query] ;; 错误:sql 的 mbql-native 实现若调用其他方法将不会使用 :bigquery 的实现 ((get-method driver/mbql-native :sql) :sql query))(defmethod driver/mbql-native :bigquery [_ query] ;; 错误若有人以 :bigquery 为父创建新驱动:sql 实现内部调用的方法会用 :bigquery 的实现 ;; 而不是新驱动自己的实现 ((get-method driver/mbql-native :sql) :bigquery query))多父驱动BigQuery 同时以:sql和:google为父这种多继承是被允许且有帮助的。多父驱动可以通过driver/register!定义(driver/register! :bigquery, :parent #{:sql :google})若两个父驱动对同一方法都有实现解决歧义的办法是为你的驱动提供自己的实现并按上文方式转交给你偏好的父驱动实现。以插件形式发布的驱动则在插件清单中完成注册。在 REPL 与 CIDER 中调试驱动无需每次改动都重新构建 uberjar可以像处理单个巨型项目一样直接启动 REPLclojure -A:dev:drivers:drivers-dev但要注意对驱动代码的修改仍需要重建驱动、安装到./plugins目录并重启 Metabase 才能生效。构建与安装驱动插件驱动的构建脚本说明见 bin/build-drivers.md。三个主要入口均需要先安装 Clojure CLI 工具build-drivers按需构建所有驱动。clojure -X:build:drivers:build/drivers # 或指定版本 clojure -X:build:drivers:build/drivers :edition :ee # 或使用 shell 包装脚本 ./bin/build-drivers.shbuild-driver按需构建单个驱动会先构建所需的父驱动。clojure -X:build:drivers:build/driver :driver :sqlserver # 或 clojure -X:build:drivers:build/driver :driver :sqlserver :edition :oss # 或 ./bin/build-driver.sh redshiftverify-driver验证构建出的驱动是否结构正确。clojure -X:build:build/verify-driver :driver :mongo将构建得到的 JAR 放入 Metabase 的plugins目录可通过MB_PLUGINS_DIR环境变量调整即可被 Metabase 加载。测试驱动测试扩展Test Extensions机制Metabase 内置了一套庞大的、会自动对所有驱动运行的共享测试套件包括你的新驱动。要让自己的驱动通过这套测试需要编写针对特殊测试扩展multimethod 的实现。测试扩展负责创建新数据库、为数据库定义Database Definition装载数据并告诉 Metabase 可以从创建的数据库中期望到什么。文件组织与命名约定测试扩展通常放在metabase.test.data.driver命名空间中。以 SQLite 为例metabase/modules/drivers/sqlite/deps.edn ; - deps 放在这里 metabase/modules/drivers/sqlite/resources/metabase-plugin.yaml ; - 插件清单 metabase/modules/drivers/sqilte/src/metabase/driver/sqlite.clj ; - 驱动主命名空间 metabase/modules/drivers/sqlite/test/metabase/test/data/sqlite.clj ; - 测试扩展测试扩展的接口定义在metabase.test.data.interface命名空间中。:sql与:sql-jdbc自身实现了部分测试扩展但定义了额外的你必须实现的方法见metabase.test.data.sql与metabase.test.data.sql-jdbc命名空间。需要按如下别名 require 相关命名空间(require [metabase.test.data.interface :as tx]) ; tx test extensions (require [metabase.test.data.sql :as sql.tx]) ; sql test extensions (require [metabase.test.data.sql-jdbc :as sql-jdbc.tx])注册测试扩展与驱动本身一样你需要声明驱动拥有测试扩展避免 Metabase 重复加载。根据父驱动类型选择其一调用只需调用一次# 非 SQL 驱动 (tx/add-test-extensions! :mongo) # 非 JDBC 的 SQL 驱动 (sql/add-test-extensions! :bigquery) # JDBC SQL 驱动 (sql-jdbc.tx/add-test-extensions! :mysql)该调用应位于测试扩展命名空间的开头(ns metabase.test.data.mysql (:require [metabase.test.data.sql-jdbc :as sql-jdbc.tx])) (sql-jdbc.tx/register-test-extensions! :mysql)当前仓库中 ClickHouse 的测试扩展 modules/drivers/clickhouse/test/metabase/test/data/clickhouse.clj#L29 正是以(sql-jdbc.tx/add-test-extensions! :clickhouse)完成注册的。Metabase 测试的运行机制以如下命令启动测试为例DRIVERSmysql clojure -X:dev:drivers:drivers-dev:test执行流程为Metabase 检查:mysql的测试扩展是否已加载若未加载则(require metabase.test.data.mysql)检查默认的test-data数据库是否已为 MySQL 创建、装载数据并同步若未完成调用测试扩展方法tx/load-data!创建test-data数据库并装载数据随后同步测试数据库Metabase 对 MySQLtest-data库的venues表执行 MBQL 查询。run-mbql-query宏是编写测试的辅助工具$前缀符号会根据名称自动解析字段 ID。实际执行的查询形如{:database 100 ; MySQL test-data 数据库的 ID :type :query :query {:source-table 20 ; 表 20 MySQL test-data.venues :filter [:ends-with [:field-id 555] Restaurant] ; 字段 555 venues.name :order-by [[:asc [:field-id 556]]]}} ; 字段 556 venues.id结果经过rows与formatted-venues-rows等辅助函数处理后只保留关心的部分将实际结果与期望结果比对。一个真实的测试片段如下;; expect-with-non-timeseries-dbs 针对 DRIVERS 环境变量中列出的所有驱动运行Druid 等时序数据库除外 (expect-with-non-timeseries-dbs ;; 期望结果 [[ 5 Brite Spot Family Restaurant 20 34.0778 -118.261 2] [ 7 Don Day Korean Restaurant 44 34.0689 -118.305 2] [17 Ruen Pair Thai Restaurant 71 34.1021 -118.306 2] [45 Tu Lan Restaurant 4 37.7821 -122.41 1] [55 Dal Rae Restaurant 67 33.983 -118.096 4]] ;; 实际结果 (- (data/run-mbql-query venues {:filter [:ends-with $name Restaurant] :order-by [[:asc $id]]}) rows formatted-venues-rows))装载数据数据库定义Database Definition为保证各驱动行为一致Metabase 测试套件从一组共享的数据库定义创建新数据库并装载数据。也就是说无论测试跑在 MySQL、Postgres、SQL Server 还是 MongoDB 上同一个测试都能验证得到完全一致的结果。绝大多数数据库定义存放在 EDN 文件中多数测试针对名为 test data 的测试数据库其定义可在test/metabase/test/data/dataset_definitions/test-data.edn中找到——本质上就是一组表名、列名与类型外加数千行待装载的数据。DatabaseDefinition的 schema 定义在metabase.test.data.interface中。作为测试扩展的编写者你最大的任务是实现把数据库定义变成真实数据库含表、列并装载数据所需的方法。非 SQL 驱动需要实现tx/load-data!:sql与:sql-jdbc为子驱动提供了共享实现但定义了自己的测试扩展方法——例如:sql/:sql-jdbc负责建表的 DDL 语句却需要你告诉它主键用什么类型因此要实现sql.tx/pk-sql-type(defmethod sql.tx/pk-sql-type :mysql [_] INTEGER NOT NULL AUTO_INCREMENT)同样类型映射也需要按驱动定制ClickHouse 测试扩展中的例子modules/drivers/clickhouse/test/metabase/test/data/clickhouse.clj#L62(defmethod sql.tx/field-base-type-sql-type [:clickhouse :type/Boolean] [_ _] Boolean) (defmethod sql.tx/field-base-type-sql-type [:clickhouse :type/Integer] [_ _] Int32) (defmethod sql.tx/field-base-type-sql-type [:clickhouse :type/DateTime] [_ _] DateTime64(3, GMT0)) (defmethod sql.tx/field-base-type-sql-type [:clickhouse :type/Float] [_ _] Float64) (defmethod sql.tx/field-base-type-sql-type [:clickhouse :type/Text] [_ _] String)连接详情dbdef-connection-detailsMetabase 还需要知道如何连接你新建的数据库——具体而言在把新建数据库保存为Database对象时:detailsmap 中应写入什么。所有带测试扩展的驱动都必须实现tx/dbdef-connection-details针对给定数据库定义返回合适的:details。MySQL 的官方示例(defmethod tx/dbdef-connection-details :mysql [_ context {:keys [database-name]}] (merge {:host (tx/db-test-env-var :mysql :host localhost) :port (tx/db-test-env-var :mysql :port 3306) :user (tx/db-test-env-var :mysql :user root) :serverTimezone UTC} (when-let [password (tx/db-test-env-var :mysql :password)] {:password password}) (when ( context :db) {:db database-name})))连接上下文contexttx/dbdef-connection-details会在两种上下文中被调用创建数据库时向数据库装载数据并同步时。大多数数据库不允许连接一个尚未创建的数据库例如CREATE DATABASE test-data;必须在不指定test-data作为连接目标的情况下执行。因此context参数取值为:server——给我连接 DBMS 服务器而非某个具体数据库的连接信息:db——给我连接某个具体数据库的连接信息。MySQL 的例子在context为:db时追加:db连接属性。ClickHouse 测试扩展中的真实实现modules/drivers/clickhouse/test/metabase/test/data/clickhouse.clj#L77展示了同样的模式并在:db上下文下追加:db-filters-type/:db-filters-patterns等驱动特有参数。从环境变量获取连接参数测试几乎总是运行在本地 Docker 容器中与其把用户名、主机、端口等连接细节硬编码不如通过环境变量提供灵活性。tx/db-test-env-var用于从环境变量读取连接参数(tx/db-test-env-var :mysql :user root)这会让 Metabase 查找环境变量MB_MYSQL_TEST_USER未找到时回退到默认值root。环境变量命名遵循MB_driver_TEST_property模式前两个参数分别对应driver与property。tx/db-test-env-var可以不指定默认值——如果该参数是可选参数且对应环境变量未设置就不必出现在连接详情中。该函数的实现位于 test/metabase/test/data/interface.clj#L1111。对于必须提供、又没有合理默认值的参数使用tx/db-test-env-var-or-throw——对应的环境变量未设置时会抛出异常最终导致测试失败;; 若 MB_SQLSERVER_TEST_USER 未设置测试套件会以类似 ;; MB_SQLSERVER_TEST_USER is required to run tests against :sqlserver 的信息退出 (tx/db-test-env-var-or-throw :sqlserver :user)注意tx/dbdef-connection-details根本不会对未列入DRIVERS环境变量的驱动被调用因此在跑 Mongo 测试时不会看到 SQL Server 的报错。db-test-env-var-or-throw的实现见 test/metabase/test/data/interface.clj#L1144。除了tx/db-test-env-varmetabase.test.data.interface还有其他实用工具函数如果数据库基于 SQL 可查阅metabase.test.data.sql使用 JDBC 驱动可查阅metabase.test.data.sql-jdbc。其他测试扩展命名差异与特殊 DBMS比较测试结果时 Metabase 还需知道一些额外信息。例如不同数据库对表和列的命名方式不同——有些数据库会把全部名称大写如venues变成VENUES此时需要实现tx/format-name之类的方法告诉 Metabase 这类命名差异仍视为同一对象。对于不允许编程式创建数据库的 DBMS常见解法是用不同的schema代替不同的数据库或给表名加数据库名前缀并在同一个数据库中创建。对 SQL 数据库可以实现sql.tx/qualified-name-components让测试使用不同的标识符例如用shared_db.test-data_venues.id代替test-data.venues.id。SQL Server 与 Oracle 的测试扩展就是这类技巧的范例。ClickHouse 的实现也演示了这一点modules/drivers/clickhouse/test/metabase/test/data/clickhouse.clj#L92。搭建 CI 运行驱动测试所有测试通过后需要在 GitHub Actions 中运行针对你的驱动的测试即在.github/workflows/drivers.yml中新增一个 job。PostgreSQL 的官方配置示例be-tests-postgres-latest-ee: needs: files-changed if: github.event.pull_request.draft false needs.files-changed.outputs.backend_all true runs-on: ${{ vars.DEFAULT_RUNNER_KEY }} timeout-minutes: 40 env: CI: true DRIVERS: postgres MB_DB_TYPE: postgres MB_DB_PORT: 5432 MB_DB_HOST: localhost MB_DB_DBNAME: circle_test MB_DB_USER: circle_test MB_POSTGRESQL_TEST_USER: circle_test MB_POSTGRES_SSL_TEST_SSL: true MB_POSTGRES_SSL_TEST_SSL_MODE: verify-full MB_POSTGRES_SSL_TEST_SSL_ROOT_CERT_PATH: test-resources/certificates/us-east-2-bundle.pem services: postgres: image: circleci/postgres:latest ports: - 5432:5432 env: POSTGRES_USER: circle_test POSTGRES_DB: circle_test POSTGRES_HOST_AUTH_METHOD: trust steps: - uses: actions/checkoutv6 - name: Test Postgres driver (latest) uses: ./.github/actions/test-driver with: junit-name: be-tests-postgres-latest-ee注意其中DRIVERS: postgres指定了要测试的驱动集合而MB_POSTGRESQL_TEST_USER等环境变量正是上一节tx/db-test-env-var读取的MB_driver_TEST_property变量。驱动模块自带的docker-compose.yml如 modules/drivers/clickhouse/docker-compose.yml提供单节点、TLS、老版本与集群等多种测试拓扑则用于在本地一键拉起数据库环境。继续深入本文对应的驱动开发完整路径为驱动基础本文插件清单 plugins.md实现驱动的 multimethod为驱动提交 PR 与测试动手前请先确认是否已有现成驱动可供贡献官方支持的数据库与社区驱动列表并参照开发环境搭建指南与 Clojure 开发指南完成准备工作。【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考