# Отправка PR для нового драйвера Если вы хотите отправить запрос на добавление подключаемого модуля драйвера в [репозиторий Glarus BI](https://github.com/metabase/metabase) (а не хранить его в отдельном репозитории), вам необходимо: - Уметь запускать базу данных локально с помощью Docker. - Убедиться, что ваш драйвер проходит основной набор тестов Glarus BI. ## Проверка вашего драйвера Чтобы протестировать драйвер, вам необходимо: - Переместить свой плагин в каталог [`modules/drivers`](https://github.com/metabase/metabase/tree/master/modules/drivers) в репозитории Glarus BI. - Добавить _test extensions_ к вашему драйверу. - Отредактировать [`.github/workflows/drivers.yml`](https://github.com/metabase/metabase/blob/master/.github/workflows/drivers.yml), чтобы сообщить GitHub Actions, как настроить образ Docker для вашей базы данных и запустить тесты. ## Добавьте тестовые расширения в ваш драйвер Тестовые расширения создают новые базы данных и загружают данные для заданных _определений базы данных_. Glarus BI определяет огромный набор тестов, которые автоматически запускаются для всех драйверов, включая ваш новый драйвер. Чтобы запустить набор тестов с вашим драйвером, вам потребуется написать ряд реализаций методов для специальных мультиметодов _test extension_. Тестовые расширения выполняют такие действия, как создание новых баз данных и загрузку данных для _определений базы данных_. Эти тестовые расширения сообщат Glarus BI, как создавать новые базы данных, загружать в них тестовые данные, а также предоставлять информацию о том, что Glarus BI может ожидать от созданной базы данных. Расширения тестов — это просто дополнительные мультиметоды, используемые только тестами. Как и мультиметоды основного драйвера, они отправляют имя драйвера как ключевое слово, например `:mysql`. ## Организация структуры файлов Тестовые расширения для драйвера обычно находятся в пространстве имён `metabase.test.data.`. Если ваш драйвер предназначен для SQLite, ваши файлы должны выглядеть примерно так: ```clj 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 ``` Таким образом, вы создадите новый каталог и файл для размещения реализации методов расширения текста. ```clj metabase/modules/drivers/sqlite/test/metabase/test/data/sqlite.clj ; <- test extensions ``` ## Где определены методы расширения теста? Тестовые расширения Glarus BI находятся в пространстве имён [`metabase.test.data.interface`](https://github.com/metabase/metabase/blob/master/test/metabase/test/data/interface.clj). Как и основные методы драйвера, `:sql` и `:jdbc-sql` не только сами реализуют некоторые тестовые расширения, но также определяют дополнительные методы, которые вы должны реализовать для их использования; см. например пространства имён [`metabase.test.data.sql`](https://github.com/metabase/metabase/blob/master/test/metabase/test/data/sql.clj) и [`metabase.test.data.sql-jdbc`](https://github.com/metabase/metabase/blob/master/test/metabase/test/data/sql_jdbc.clj). Вам потребуются следующие пространства имён с такими псевдонимами: ```clj (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.`, поэтому вам нужно следовать этому шаблону именования.) `:sql` и драйверы `:sql-jdbc` имеют свои собственные наборы тестовых расширений, поэтому в зависимости от того, от чего вы унаследовали свой драйвер, зарегистрируйте тестовые расширения с помощью: ```clj # 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`. Этот вызов должен идти в начале пространства имён вашего тестового расширения, например: ```clj (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, чтобы мы могли понять, как он работает и что именно нам нужно сделать, чтобы включить его: ```clj ;; 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)) ``` Допустим, мы запускаем тесты с ```sh 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` помогает при написании тестов, которые ищут идентификаторы полей на основе имён символов, в которых есть `$`. Сейчас на этом не нужно заострять внимание; просто знайте, что фактический запрос, который выполняется, будет выглядеть примерно так: ```clj {: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/edn-format/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-тип`: ```clj (defmethod sql.tx/pk-sql-type :mysql [_] "INTEGER NOT NULL AUTO_INCREMENT") ``` Все методы задокументированы в самой кодовой базе; взгляните на соответствующие пространства имён тестовых расширений и посмотрите, какие методы вам нужно реализовать. Вы также можете обратиться к тестовым расширениям, написанным для других подобных драйверов, чтобы получить представление о том, что именно вам нужно делать. ## Сведения о соединении Конечно, Glarus BI также должна знать, как она может подключиться к вашей вновь созданной базе данных, что нужно сохранить как часть словаря подключения `:details`, когда он сохраняет вновь созданную базу данных как объект `Database`. Все драйверы с тестовыми расширениями должны реализовать `tx/dbdef->connection-details`, чтобы возвращать соответствующий набор `:details` для данного определения базы данных. Например: ```clj (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`, чтобы получить информацию из переменных окружения. Например, ```clj (tx/db-test-env-var :mysql :user "root") ``` сообщает Glarus BI искать переменную окружения `MB_MYSQL_TEST_USER`; если не найдено, по умолчанию `"root"`. Имя переменной среды соответствует шаблону `MB__TEST_`, переданному в функцию в качестве первого и второго аргументов соответственно. Вам не нужно указывать значение по умолчанию для `tx/db-test-env-var`; возможно, `user` является необязательным параметром; и если `MB_MYSQL_TEST_USER` не указан, вам не нужно указывать его в свойствах подключения. Но как насчёт свойств, которые вы хотите использовать, но не имеете разумных значений по умолчанию? В таких случаях вы можете использовать tx/db-test-env-var-or-throw. Если соответствующая переменная среды не установлена, они вызовут исключение, что в конечном итоге приведёт к сбою тестов. ```clj ;; 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`](https://github.com/metabase/metabase/blob/master/.github/workflows/drivers.yml), чтобы запустить тесты для вашей базы данных. Вот пример конфигурации для PostgreSQL. ```yaml 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](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions).