Отправка PR для нового драйвера

Если вы хотите отправить запрос на добавление подключаемого модуля драйвера в репозиторий Glarus BI (а не хранить его в отдельном репозитории), вам необходимо:

  • Уметь запускать базу данных локально с помощью Docker.

  • Убедиться, что ваш драйвер проходит основной набор тестов Glarus BI.

Проверка вашего драйвера

Чтобы протестировать драйвер, вам необходимо:

  • Переместить свой плагин в каталог modules/drivers в репозитории Glarus BI.

  • Добавить test extensions к вашему драйверу.

  • Отредактировать .github/workflows/drivers.yml, чтобы сообщить GitHub Actions, как настроить образ Docker для вашей базы данных и запустить тесты.

Добавьте тестовые расширения в ваш драйвер

Тестовые расширения создают новые базы данных и загружают данные для заданных определений базы данных. Glarus BI определяет огромный набор тестов, которые автоматически запускаются для всех драйверов, включая ваш новый драйвер.

Чтобы запустить набор тестов с вашим драйвером, вам потребуется написать ряд реализаций методов для специальных мультиметодов test extension. Тестовые расширения выполняют такие действия, как создание новых баз данных и загрузку данных для определений базы данных.

Эти тестовые расширения сообщат Glarus BI, как создавать новые базы данных, загружать в них тестовые данные, а также предоставлять информацию о том, что Glarus BI может ожидать от созданной базы данных. Расширения тестов — это просто дополнительные мультиметоды, используемые только тестами. Как и мультиметоды основного драйвера, они отправляют имя драйвера как ключевое слово, например :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

Где определены методы расширения теста?

Тестовые расширения Glarus BI находятся в пространстве имён metabase.test.data.interface. Как и основные методы драйвера, :sql и :jdbc-sql не только сами реализуют некоторые тестовые расширения, но также определяют дополнительные методы, которые вы должны реализовать для их использования; см. например пространства имён metabase.test.data.sql и metabase.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])

Регистрация тестовых расширений

Как и в случае с самим драйвером, вам нужно зарегистрировать тот факт, что ваш драйвер имеет тестовые расширения, чтобы Glarus BI знал, что ему не нужно пытаться загрузить их во второй раз. (Если они ещё не загружены, Glarus BI загрузит их при необходимости, выполнив поиск пространства имён с именем 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)

Анатомия теста Glarus BI

Давайте посмотрим на реальный тест Glarus BI, чтобы мы могли понять, как он работает и что именно нам нужно сделать, чтобы включить его:

;; 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. Glarus BI проверит и увидит, загружены ли тестовые расширения для :mysql. Если нет, то исполнится (require 'metabase.test.data.mysql).

  2. Glarus BI проверит, была ли создана база данных test-data по умолчанию для MySQL, загружена ли она данными и синхронизирована ли она. Если нет, будет вызван метод расширения теста tx/load-data!, чтобы создать базу данных «test-data» и загрузить в неё данные. После загрузки данных Glarus BI синхронизирует тестовую базу данных. (Это обсуждается более подробно ниже).

  3. Glarus BI запускает запрос MBQL к таблице venues базы данных test-data MySQL. Макрос run-mbql-query помогает при написании тестов, которые ищут идентификаторы полей на основе имён символов, в которых есть $. Сейчас на этом не нужно заострять внимание; просто знайте, что фактический запрос, который выполняется, будет выглядеть примерно так:

    {: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. Результаты обрабатываются вспомогательными функциями rows и formatted-venues-rows, которые возвращают только те части результатов запроса, которые нас интересуют.

  5. Эти результаты сравниваются с ожидаемыми результатами.

Это примерно то, что вам нужно знать о том, как работают тесты Glarus BI; теперь, когда мы рассмотрели это, давайте посмотрим, как мы можем дать Glarus BI возможность делать то, что ей нужно делать.

Загрузка данных

Чтобы обеспечить согласованное поведение для разных драйверов, набор тестов Glarus BI создаёт новые базы данных и загружает в них данные из набора общих Database Definitions. Это означает, что независимо от того, проводим ли мы тест на MySQL, Postgres, SQL Server или MongoDB, один тест может убедиться, что мы получаем одинаковые результаты для каждого драйвера!

Большинство этих определений баз данных находятся в файлах EDN; большинство тестов выполняется с тестовой базой данных с именем «тестовые данные», определение которой можно найти [здесь](https://github.com/metabase/metabase/blob/master/test/metabase/test/data/dataset_definitions/ тест-данные.edn). Взгляните на этот файл — это просто набор имён таблиц, имён и типов столбцов, а затем несколько тысяч строк данных для загрузки в эти таблицы.

Как и определения метода расширения теста, схемы для DatabaseDefinition находятся в [metabase.test.data.interface](https://github.com/metabase/metabase/blob/master/test/metabase/test/data/interface .clj) — вы можете посмотреть, как именно должно выглядеть определение базы данных.

Ваша самая большая задача как автора тестов заключается в написании методов, необходимых для получения определения базы данных, создания новой базы данных с соответствующими таблицами и столбцами и загрузки в неё данных. Для драйверов, отличных от SQL, вам нужно реализовать tx/load-data!; :sql и :sql-jdbc имеют общую реализацию, используемую дочерними драйверами, но определяют свой собственный набор тестовых расширенных методов. Например, :sql:sql-jdbc) будут обрабатывать операторы DDL для создания таблиц, но вам нужно знать, какой тип следует использовать для первичного ключа, поэтому вам нужно будет реализовать sql.tx /pk-sql-тип:

(defmethod sql.tx/pk-sql-type :mysql [_] "INTEGER NOT NULL AUTO_INCREMENT")

Все методы задокументированы в самой кодовой базе; взгляните на соответствующие пространства имён тестовых расширений и посмотрите, какие методы вам нужно реализовать. Вы также можете обратиться к тестовым расширениям, написанным для других подобных драйверов, чтобы получить представление о том, что именно вам нужно делать.

Сведения о соединении

Конечно, Glarus BI также должна знать, как она может подключиться к вашей вновь созданной базе данных, что нужно сохранить как часть словаря подключения :details, когда он сохраняет вновь созданную базу данных как объект Database. Все драйверы с тестовыми расширениями должны реализовать 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 — это либо :server, что означает «предоставьте мне сведения для подключения к серверу СУБД, но не к конкретной базе данных», либо :db, что означает «предоставьте мне сведения для соединения с определённой базой данных». В случае MySQL он добавляет свойство подключения :db всякий раз, когда контекст имеет значение :db.

Получение свойств подключения из переменных окружения

Вы почти наверняка будете запускать свою базу данных в локальном контейнере Docker. Вместо того чтобы жёстко задавать детали подключения (имя пользователя, хост, порт…) для контейнера Docker, мы хотели бы быть гибкими и позволить людям указывать их в переменных среды на случай, если они работают с другим контейнером или просто запускают базу данных вне контейнера или полностью на другом компьютере. Вы можете использовать tx/db-test-env-var, чтобы получить информацию из переменных окружения. Например,

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

сообщает Glarus BI искать переменную окружения 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)

Обратите внимание, что tx/dbdef->connection-details не будет вызываться в первую очередь для драйверов, с которыми вы не проводите тесты.

Помимо tx/db-test-env-var, metabase.test.data.interface имеет несколько других полезных служебных функций. Внимательно посмотрите на это пространство имен, а также на metabase.test.data.sql, если ваша база данных использует SQL, и metabase.test.data.sql-jdbc, если ваша база данных использует драйвер JDBC.

Другие тестовые расширения

Есть ещё несколько вещей, которые необходимо знать о Glarus BI при сравнении результатов тестов. Например, разные базы данных называют таблицы и столбцы по-разному; существуют методы, чтобы сообщить Glarus BI, что следует ожидать, что что-то вроде venues таблицы в определении базы данных test-data вернётся как VENUES для базы данных, в которой все буквы являются прописными. (Мы считаем, что такие незначительные различия в именовании означают одно и то же.) Взгляните на tx/format-name и другие подобные методы и посмотрите, какие из них вам нужно реализовать.

Как насчёт СУБД, которые не позволяют программно создавать новые базы данных?

На самом деле это распространённая проблема, и, к счастью, мы выяснили, как её обойти. Решение обычно заключается в использовании разных схем вместо разных баз данных или добавлении префикса имён таблиц к имени базы данных и создании всего в одной базе данных. Для баз данных на основе 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, чтобы запустить тесты для вашей базы данных.

Вот пример конфигурации для 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@v3
  - name: Test Postgres driver (latest)
    uses: ./.github/actions/test-driver
    with:
      junit-name: 'be-tests-postgres-latest-ee'

Для получения дополнительной информации о том, что вы здесь делаете и как всё это работает, см. Синтаксис рабочего процесса для GitHub Actions.