提交新驅動程式的 PR

如果您想要提交 PR 以新增驅動程式外掛程式到 Metabase 儲存庫 (而不是將其保留在個別的儲存庫中),您需要

  • 能夠使用 Docker 在本機執行您的資料庫。
  • 確保您的驅動程式通過 Metabase 的核心測試套件。

測試您的驅動程式

若要測試您的驅動程式,您需要

  • 將您的外掛程式移至 Metabase 儲存庫中的 modules/drivers 目錄。
  • 測試擴充功能新增至您的驅動程式。
  • 編輯 .github/workflows/drivers.yml 以告知 GitHub Actions 如何為您的資料庫設定 Docker 映像,並對其執行測試。

將測試擴充功能新增至您的驅動程式

測試擴充功能會執行諸如建立新資料庫和為給定的資料庫定義載入資料等操作。Metabase 定義了一套龐大的測試套件,可自動針對所有驅動程式 (包括您的新驅動程式) 執行。

若要使用您的驅動程式執行測試套件,您需要為特殊的測試擴充功能多方法撰寫一系列方法實作。測試擴充功能會執行諸如建立新資料庫和為資料庫定義載入資料等操作。

這些測試擴充功能將告訴 Metabase 如何建立新的資料庫並載入測試資料,並提供有關 Metabae 可以從建立的資料庫中預期的資訊。測試擴充功能只是僅供測試使用的其他多方法。與核心驅動程式多方法一樣,它們會根據驅動程式名稱作為關鍵字進行調度,例如 :mysql

檔案組織

驅動程式的測試擴充功能通常位於名為 metabase.test.data.<driver> 的命名空間中。如果您的驅動程式適用於 SQLite,您的檔案應如下所示

metabase/modules/drivers/sqlite/deps.edn                           ; <- deps go in here
metabase/modules/drivers/sqlite/resources/metabase-plugin.yaml     ; <- plugin manifest
metabase/modules/drivers/sqilte/src/metabase/driver/sqlite.clj     ; <- main driver namespace

因此,您將建立新的目錄和檔案來放置您的文字擴充功能方法實作。

metabase/modules/drivers/sqlite/test/metabase/test/data/sqlite.clj   ; <- test extensions

測試擴充功能方法在哪裡定義?

Metabase 測試擴充功能位於 metabase.test.data.interface 命名空間中。與核心驅動程式方法一樣,:sql:jdbc-sql 實作了一些測試擴充功能本身,但定義了您必須實作的其他方法才能使用它們;請參閱 metabase.test.data.sqlmetabase.test.data.sql-jdbc 命名空間。

您需要請求以下命名空間,並使用別名,如下所示

(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 知道它不需要嘗試第二次載入它們。(如果它們尚未載入,Metabase 會在需要時透過尋找名為 metabase.test.data.<driver> 的命名空間來載入它們,這就是為什麼您需要遵循該命名模式。) :sql:sql-jdbc 驅動程式有自己的測試擴充功能集,因此根據您驅動程式使用的父項,使用下列項目註冊測試擴充功能

# Non-SQL drivers
(tx/add-test-extensions! :mongo)

# non-JDBC SQL
(sql/add-test-extensions! :bigquery)

# JDBC SQL
(sql-jdbc.tx/add-test-extensions! :mysql)

您只需要一個呼叫 - 對於 :sql-jdbc 驅動程式,無需執行所有三個呼叫。此呼叫應位於您的測試擴充功能命名空間的開頭,如下所示

(ns metabase.test.data.mysql
  (:require [metabase.test.data.sql-jdbc :as sql-jdbc.tx]))

(sql-jdbc.tx/register-test-extensions! :mysql)

Metabase 測試的剖析

讓我們看看一個真實的 Metabase 測試,以便我們了解其運作方式,以及我們需要做些什麼來為其提供支援

;; expect-with-non-timeseries-dbs = run against all drivers listed in `DRIVERS` env var except timeseries ones like Druid
(expect-with-non-timeseries-dbs
  ;; expected results
  [[ 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]]
  ;; actual results
  (-> (data/run-mbql-query venues
        {:filter   [:ends-with $name "Restaurant"]
         :order-by [[:asc $id]]})
      rows formatted-venues-rows))

假設我們使用以下命令啟動測試

DRIVERS=mysql clojure -X:dev:drivers:drivers-dev:test`.
  1. Metabase 將檢查並查看是否已載入 :mysql 的測試擴充功能。如果沒有,它將 (require 'metabase.test.data.mysql)
  2. Metabase 將檢查以查看是否已為 MySQL 建立預設的 test-data 資料庫、載入資料並同步。如果沒有,它將呼叫測試擴充功能方法 tx/load-data! 以建立 test-data 資料庫並將資料載入其中。載入資料後,Metabase 會同步測試資料庫。(這將在下面更詳細地討論。)
  3. Metabase 針對 MySQL test-data 資料庫的 venues 表格執行 MBQL 查詢。run-mbql-query 巨集是一個用於撰寫測試的輔助工具,它會根據符號名稱 (符號名稱的前面有 $) 查閱欄位 ID。現在不要太擔心這個;只需知道實際執行的查詢看起來會像這樣
    {:database 100 ; ID of MySQL test-data database
     :type :query
     :query {:source-table 20 ; Table 20 = MySQL test-data.venues
             :filter [:ends-with [:field-id 555] "Restaurant"] ; Field 555 = MySQL test-data.venues.name
             :order-by [[:asc [:field-id 556]]]}} ; Field 556 = MySQL test-data.venues.id
    
  4. 結果會透過輔助函數 rowsformatted-venues-rows 執行,這些函數只會傳回我們關心的查詢結果部分
  5. 這些結果會與預期結果進行比較。

這幾乎是您需要了解的 Metabase 測試內部運作方式;既然我們已經介紹了這一點,讓我們看看如何讓 Metabase 能夠執行它需要執行的操作。

載入資料

為了確保不同驅動程式之間行為一致,Metabase 測試套件會建立新的資料庫,並從一組共用的資料庫定義將資料載入其中。這表示無論我們是針對 MySQL、Postgres、SQL Server 還是 MongoDB 執行測試,單一測試都可以檢查我們是否為每個驅動程式取得完全相同的結果!

這些資料庫定義大多位於 EDN 檔案中;大多數測試都是針對名為「test data」的測試資料庫執行的,其定義可以在這裡找到。查看該檔案 - 它只是一組簡單的表格名稱、欄名稱和類型,然後是要載入這些表格的幾千行資料。

與測試擴充功能方法定義一樣,DatabaseDefinition 的結構描述位於 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")

我想在這裡詳細記錄每個測試擴充功能方法,但在我找到時間執行此操作之前,這些方法都記錄在程式碼庫本身中;查看適當的測試擴充功能命名空間,看看您需要實作哪些方法。您也可以參考為其他類似驅動程式撰寫的測試擴充功能,以了解您到底需要做什麼。

連線詳細資訊

當然,Metabase 也需要知道如何連線到您新建立的資料庫。具體來說,它需要知道在將新建立的資料庫儲存為 Database 物件時,應該將哪些內容儲存為連線 :details 對應的一部分。具有測試擴充功能的所有驅動程式都需要實作 tx/dbdef->connection-details,以針對給定的資料庫定義傳回一組適當的 :details。例如

(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")
    ;; :timezone :America/Los_Angeles
    :serverTimezone "UTC"}
   (when-let [password (tx/db-test-env-var :mysql :password)]
     {:password password})
   (when (= context :db)
     {:db database-name})))

讓我們看看這裡發生了什麼事。

連線內容

tx/dbdef->connection-details 在兩種不同的內容中呼叫

  • 建立資料庫時,
  • 以及將資料載入資料庫並進行同步時。

大多數資料庫都不允許您連線到尚未建立的資料庫,這表示類似 CREATE DATABASE "test-data"; 的陳述式必須在指定 test-data 作為連線一部分的情況下執行。因此,context 參數。context:server (表示「給我連線到 DBMS 伺服器的詳細資訊,但不要連線到特定資料庫」) 或 :db (表示「給我連線到特定資料庫的詳細資訊」)。在 MySQL 的情況下,當內容為 :db 時,它會新增 :db 連線屬性。

從環境變數取得連線屬性

您幾乎肯定會在本機 Docker 容器中執行您的資料庫。我們希望具有彈性,而不是硬式編碼 Docker 容器的連線詳細資訊 (使用者名稱、主機、連接埠...),並讓使用者在環境變數中指定這些詳細資訊,以防他們針對不同的容器執行,或者只是在容器外部或完全在另一部電腦上執行資料庫。您可以使用 tx/db-test-env-var 從環境變數取得詳細資訊。例如,

(tx/db-test-env-var :mysql :user "root")

告訴 Metabase 尋找環境變數 MB_MYSQL_TEST_USER;如果找不到,則預設為 "root"。環境變數的名稱遵循模式 MB_<driver>_TEST_<property>,作為第一個和第二個引數傳遞到函數中。您不需要為 tx/db-test-env-var 指定預設值;或許 user 是一個選用參數;如果未指定 MB_MYSQL_TEST_USER,則您不需要在連線詳細資訊中指定它。

但是,如果您想要要求但沒有合理的預設值的屬性呢?在這些情況下,您可以使用 tx/db-test-env-var-or-throw。如果未設定對應的環境變數,這些變數將擲回例外狀況,最終導致測試失敗。

;; If MB_SQLSERVER_TEST_USER is unset, the test suite will quit with a message saying something like
;; "MB_SQLSERVER_TEST_USER is required to run tests against :sqlserver"
(tx/db-test-env-var-or-throw :sqlserver :user)

請注意,對於您未對其執行測試的驅動程式 (即,未在 DRIVERS 環境變數中列出的驅動程式),一開始就不會呼叫 tx/dbdef->connection-details,因此當針對 Mongo 執行測試時,您不會看到該 SQL Server 錯誤訊息,例如。

除了 tx/db-test-env-var 之外,metabase.test.data.interface 還有幾個其他有用的公用程式函數。如果您資料庫使用 SQL,請仔細查看該命名空間以及 metabase.test.data.sql,如果您的資料庫使用 JDBC 驅動程式,則請查看 metabase.test.data.sql-jdbc

其他測試擴充功能

在比較測試結果時,Metabase 還需要知道一些其他事項。例如,不同的資料庫命名表格和欄位的方式有所不同;有些方法可以讓 Metabase 知道,對於會將所有內容都轉為大寫的資料庫,它應該預期在 test-data 資料庫定義中看到的類似 venues 表格會回傳為 VENUES。 (我們認為在命名上的這種微小差異仍然代表相同的東西。)請查看 tx/format-name 和其他類似的方法,看看你需要實作哪些。

如果 DBMS 無法讓你以程式化的方式建立新的資料庫呢?

這實際上是一個常見的問題,幸運的是我們已經找到了解決方法。 解決方案通常是使用不同的結構描述來代替不同的資料庫,或是在表格名稱前加上資料庫名稱,並在同一個資料庫中建立所有內容。 對於基於 SQL 的資料庫,你可以實作 sql.tx/qualified-name-components,讓測試使用不同的識別符,而不是它們通常會使用的識別符,例如 "shared_db"."test-data_venues".id 而不是 "test-data".venues.id。 SQL Server 和 Oracle 測試擴充功能是此類黑魔法在實際應用中的良好範例。

設定 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: ubuntu-22.04
  timeout-minutes: 60
  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/checkout@v4
    - name: Test Postgres driver (latest)
      uses: ./.github/actions/test-driver
      with:
        junit-name: "be-tests-postgres-latest-ee"

如需更多關於你在此處所做的事情以及所有這些運作方式的資訊,請參閱 Workflow syntax for GitHub Actions

閱讀其他版本的 Metabase 文件。