# О статическом анализе

dbt v2 [полностью понимает SQL вашего проекта](https://docs.getdbt.tech/blog/the-levels-of-sql-comprehension), обеспечивая расширенные возможности, такие как проверка с учётом диалекта и точное отслеживание происхождения данных на уровне столбцов.

Это возможно благодаря тому, что его этап компиляции более комплексный, чем у движка dbt v1.x. Когда в dbt v1.x говорилось о *компиляции*, подразумевался только *рендеринг* — преобразование строк с шаблонами Jinja в SQL-запрос для отправки в базу данных.

dbt v2 также умеет выполнять рендеринг Jinja, но затем выполняет второй этап: *статический анализ*, создавая и проверяя логический план для каждого запроса проекта после рендеринга. Этот этап лежит в основе новых возможностей dbt v2.

## Принципы статического анализа

В разработке программного обеспечения понятие [статического анализа](https://en.wikipedia.org/wiki/Static_program_analysis) обозначает проверки, которые можно выполнить над кодом до его запуска (статический == не выполняющийся).

Самый строгий статический анализ позволяет быть уверенным: если анализ завершился успешно, код будет выполняться в производственной среде без ошибок компиляции.

Менее строгий статический анализ также предоставляет разработчикам полезную информацию во время работы. Но за всё приходится платить: выигрыш в скорости отклика оборачивается потерей гарантий корректности.

dbt v2 использует настройку [`static_analysis`](https://docs.getdbt.tech/reference/resource-configs/static-analysis.md), чтобы вы могли управлять выполнением статического анализа ваших моделей.

Уникальность dbt v2 заключается в том, что он может статически анализировать не только отдельную модель изолированно, но и каждый запрос по всей цепочке вашего DAG. Даже ваша база данных может проверить только тот запрос, который ей передали! Такие концепции, как [теория информационных потоков](https://roundup.getdbt.com/i/156064124/beyond-cll-information-flow-theory-and-metadata-propagation), — хотя они [пока](https://www.getdbt.com/blog/where-we-re-headed-with-the-dbt-fusion-engine) и не реализованы в платформе dbt — опираются на стабильные входные данные и возможность отслеживать столбцы по всему DAG.

### Режим baseline: плавный переход с dbt v1

dbt v2 по умолчанию использует режим `static_analysis: baseline`, созданный по аналогии с подходами инструментов проверки типов и линтинга, такими как [подход TypeScript к миграции](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html), [функция baseline в basedpyright](https://docs.basedpyright.com/latest/benefits-over-pyright/baseline/) и [строгий и нестрогий режимы Pydantic](https://docs.pydantic.dev/latest/why/#strict-lax).

В основе перечисленных инструментов и режима baseline в dbt v2 лежат следующие принципы:

* **Плавный переход**: обеспечить привычный опыт при первом использовании для пользователей, переходящих с dbt v1.
* **Постепенное подключение**: предложить понятный путь к освоению дополнительных возможностей dbt v2 со временем.
* **Практичная проверка**: выявлять большинство ошибок SQL без необходимости полностью перерабатывать проект.

Используйте такой подход к постепенной типизации, чтобы начать с облегчённой проверки, а затем по мере готовности проекта постепенно вводить строгие гарантии.

#### Что меняет режим baseline

Режим baseline вносит несколько принципиальных изменений в поведение по сравнению с прежним двоичным подходом (выключено/включено):

* **Без загрузки удалённых схем** — режим baseline не получает схемы из хранилища данных.
* **Модульные тесты работают без режима strict** — ранее для модульных тестов требовалось полностью включить статический анализ. В режиме baseline они работают сразу.
* **Без предупреждений о небезопасной интроспекции** — мы больше не предупреждаем о небезопасной интроспекции, хотя в будущем по-прежнему хотели бы помочь вам оценивать её.

Следующая таблица показывает, как режим baseline расширяет доступные возможности без необходимости использовать режим strict.

### Сравнение возможностей LSP

Режим baseline открывает значительный набор возможностей без необходимости использовать режим strict. Мы также работаем над тем, чтобы со временем сделать в baseline доступными и другие возможности.

Возможности расширения VS Code в зависимости от настройки статического анализа:

✅ = Доступно | ❌ = Недоступно

| Возможность                                          | off | baseline | strict |
| ---------------------------------------------------- | --- | -------- | ------ |
| Переход к определению/ссылке (кроме столбцов)        | ✅  | ✅       | ✅     |
| Отслеживание происхождения данных на уровне таблиц   | ✅  | ✅       | ✅     |
| Проверка YAML                                        | ✅  | ✅       | ✅     |
| Рендеринг и предварительный просмотр SQL             | ✅  | ✅       | ✅     |
| Модульные тесты                                      | ✅  | ✅       | ✅     |
| Обнаружение синтаксических ошибок                    | ❌  | ✅       | ✅     |
| Предварительный просмотр результатов CTE             | ❌  | ✅       | ✅     |
| Переход к определению/ссылке (столбцы)               | ❌  | ❌       | ✅     |
| Автоматический рефакторинг имён столбцов             | ❌  | ❌       | ✅     |
| Отслеживание происхождения данных на уровне столбцов | ❌  | ❌       | ✅     |
| Обнаружение ошибок типов данных и сигнатур функций   | ❌  | ❌       | ✅     |

* Возможности расширения dbt для VS Code в этой таблице доступны всем пользователям — их доступность зависит от режима статического анализа, а не от вашей учётной записи.

Поддерживаемые функции Snowflake

Чтобы узнать, какие функции Snowflake поддерживаются в dbt v2 в режиме `strict`, обратитесь к разделу [Поддержка функций Snowflake](https://docs.getdbt.tech/reference/resource-configs/snowflake-function-support.md)

Отображение CodeLens

Расширение VS Code и Studio IDE предоставляют CodeLens даже при выключенном статическом анализе, позволяя видеть, для каких моделей статический анализ отключён и почему.

В конечном счёте мы хотим, чтобы все разрабатывали в режиме strict для получения максимальных гарантий. Мы понимаем, что такой переход не может произойти мгновенно, — baseline существует, чтобы сделать его плавным. Многие запланированные возможности (например, локальные вычисления) требуют режима strict. Мы также изучаем возможность автоматически определять типы столбцов за вас: это позволит расширить функциональность режима baseline без необходимости вручную предоставлять информацию о типах.

### Обработка интроспекции в режиме baseline

В режиме `baseline` все результаты статического анализа считаются предупреждениями, а не ошибками — проект может продолжать выполняться, даже если компилятор обнаруживает некорректный или проблемный SQL. Этот раздел хорошо иллюстрирует, зачем нужен такой подход.

Ранее в режиме `strict` система предполагала, что локальные схемы скомпилированных моделей доступны. В режиме `baseline` мы больше не можем рассчитывать на наличие полной локальной схемы, поэтому `baseline` использует удалённую базу данных как источник достоверной информации — аналогично dbt v1.

На практике это означает, что компилятор dbt v2 иногда может отмечать некорректные запросы, возникшие из-за того, что интроспективные запросы вернули пустой результат. Если вы столкнулись с этим, можно:

1. Проигнорировать предупреждение
2. Построить модель локально
3. (Скоро) Использовать `warn_error_options`, чтобы отключить предупреждение

Например, рассмотрим запрос с использованием макроса `dbt_utils.unpivot`:

```sql
select * from (
{{
    dbt_utils.unpivot(
        relation=ref('example_model'),
        cast_to='integer',
        exclude=['order_id', 'customer_id'],
        field_name='product_type',
        value_name='quantity'
    )
}}
)
```

Если интроспективный запрос завершается с ошибкой или не возвращает результатов, после рендеринга получается:

```sql
select * from (

)
```

Это некорректный SQL. В режиме `baseline` dbt v2 показывает предупреждение, чтобы проект мог продолжать выполняться, а вы при этом знали о проблеме:

```bash
dbt0101: no viable alternative at input '(
    
)'
  --> models/example_model.sql:17:1
```

#### Сценарии миграции

Миграция на dbt v2 может потребовать не только перестановок в YAML. Вот некоторые сценарии, которые могут усложнить миграцию:

1. **Ограниченный доступ к источникам**: у вас нет доступа ко всем источникам и моделям большого проекта dbt.
2. **Сложные рабочие процессы Jinja**: ваш проект активно использует post-hooks и интроспекцию.
3. **Совместимость пакетов**: ваш проект зависит от пакетов, которые пока несовместимы с dbt v2.
4. **Неподдерживаемые возможности SQL**: ваши модели или источники используют сложные типы данных (`STRUCT`, `ARRAY`, `GEOGRAPHY`) или встроенные функции (`AI.PREDICT`, `JSON_FLATTEN`, `st_pointfromgeohash`), которые пока не поддерживаются в dbt v2.

Установка `static_analysis` в режим `baseline` позволяет сразу начать использовать dbt v2, постепенно решая перечисленные задачи. По мере устранения проблем совместимости вы можете переводить отдельные модели или весь проект в режим `strict` для получения максимальных гарантий проверки.

## Краткое сравнение движков

dbt v1:

* Выполняет рендеринг и запуск моделей по одной.
* Никогда не выполняет статический анализ.

dbt v2 (режим baseline — по умолчанию):

* Статически анализирует все модели, выявляя большинство ошибок SQL и обеспечивая привычный процесс миграции.

dbt v2 (режим strict):

* Выполняет рендеринг и статический анализ всех моделей до начала выполнения.
* Гарантирует, что ничего не запустится, пока не будет подтверждена корректность всего проекта.
* Разбирает `CREATE FUNCTION` в [`sql_header`](https://docs.getdbt.tech/reference/resource-configs/sql_header.md) и хуках [`on-run-start`](https://docs.getdbt.tech/reference/project-configs/on-run-start-on-run-end.md), а затем регистрирует эти UDF, чтобы при строгой компиляции можно было разрешить вызовы этих функций. Режимы `baseline` и `off` не регистрируют UDF таким способом. См. [Пользовательские функции (UDF) в режиме `strict`](https://docs.getdbt.tech/reference/resource-configs/static-analysis.md#user-defined-functions-udfs-in-strict-mode).

## Настройка `static_analysis`

Вы можете изменить способ применения статического анализа для отдельных моделей проекта. Настройка статического анализа распространяется каскадно от наиболее строгой к наименее строгой. Ниже по цепочке происхождения данных модель может сохранить тот же режим или ослабить его — она не может быть строже своей родительской модели.

Установка `static_analysis: strict` для модели не задаёт автоматически `strict` для нижележащих моделей; они сохраняют значение по умолчанию для проекта, если вы не настроите их явно. Правила и примеры приведены в разделах [Как режимы распространяются по цепочке происхождения данных](#how-modes-cascade-in-your-lineage) и [Наследование режима strict](#strict-mode-inheritance).

Модели, использующие пользовательские материализации, следуют тем же правилам, с одним исключением для `strict`. См. раздел [Пользовательские материализации](#custom-materializations).

Доступны следующие значения настройки [`static_analysis`](https://docs.getdbt.tech/reference/resource-configs/static-analysis.md):

* `baseline` (по умолчанию): выполнять статический анализ SQL. Это рекомендуемая отправная точка для пользователей, переходящих с dbt v1: она обеспечивает плавную миграцию и при этом выявляет большинство ошибок SQL.
* `strict`: выполнять статический анализ всего SQL до начала выполнения. Используйте этот режим для максимальных гарантий проверки — ничего не запустится, пока не будет подтверждена корректность всего проекта.
* `off`: пропустить анализ SQL для этой модели и всех её потомков.

При отключении статического анализа возможности расширения VS Code, зависящие от понимания SQL, будут недоступны.

Лучше всего задавать `static_analysis` как настройку отдельной модели или группы моделей. Для отладки также можно использовать флаги CLI [`--static-analysis strict`](https://docs.getdbt.tech/reference/global-configs/static-analysis-flag.md) или `--static-analysis off`, чтобы переопределить все настройки на уровне моделей.

### Постепенное внедрение режима strict

Освоившись с dbt v2 в режиме baseline, вы можете постепенно переводить модели или каталоги в режим `strict`:

dbt\_project.yml

```yml
name: jaffle_shop

models:
  jaffle_shop:
    # Начните со строгого анализа наиболее подготовленных моделей
    staging:
      +static_analysis: strict
    # Сохраните baseline для моделей, требующих доработки
    marts:
      +static_analysis: baseline
```

#### Как режимы распространяются по цепочке происхождения данных

Два правила определяют, какой режим может использовать модель:

* **Правило допустимости:** модель допускает статический анализ, только если все её вышележащие зависимости допускают его.
* **Правило строгости:** модель не может быть строже своих родительских моделей. Иерархия строгости: `strict` → `baseline` → `off`.

Ниже по цепочке модель может сохранить режим родительской модели или ослабить его, но не может сделать его строже:

| Вышележащая модель | Допустимый режим нижележащей модели |
| ------------------ | ----------------------------------- |
| `strict`           | `strict`, `baseline` или `off`      |
| `baseline`         | `baseline` или `off` (не `strict`)  |
| `off`              | только `off`                        |

Правило строгости существует потому, что `baseline` не формирует полную проанализированную схему, которая нужна режиму `strict` от вышележащих моделей. Без этой информации нижележащая модель не может выполнять проверку типов уровня strict. Полное описание см. в разделе [Как каскадно распространяются режимы статического анализа](https://docs.getdbt.tech/reference/resource-configs/static-analysis.md#how-static-analysis-modes-cascade).

#### Наследование режима strict

В отличие от `baseline` или `off`, режим `strict` не распространяется на нижележащие модели. Если вы зададите для модели `strict`, её нижележащие модели не унаследуют режим `strict`, если вы не настроите их явно. Чтобы сделать все модели `strict`, сначала нужно задать `+static_analysis: strict` для корневых моделей либо использовать общепроектную настройку из следующего раздела на уровне проекта.

Например, в цепочке A → B → C со значением по умолчанию `baseline` установка `strict` для A (корневого узла) делает строгой только A — B и C остаются в baseline, если не настроить их отдельно. Чтобы сделать всю цепочку `strict`, задайте `+static_analysis: strict` для каждой соответствующей модели или группы либо используйте общепроектную настройку.

Этот подход позволяет получать преимущества строгой проверки там, где это возможно, сохраняя гибкость анализа baseline для моделей, которые пока несовместимы со строгим режимом.

Подробнее о настройках см. в разделах [Параметры CLI](https://docs.getdbt.tech/reference/global-configs/command-line-options.md) и [Настройки и свойства](https://docs.getdbt.tech/reference/configs-and-properties.md).

### Пользовательские материализации

Модели, использующие [пользовательскую материализацию](https://docs.getdbt.tech/guides/create-new-materializations.md), следуют тем же правилам `static_analysis`, что и любые другие модели, включая режим `baseline` по умолчанию и обычные [правила каскадного распространения](#how-modes-cascade-in-your-lineage).

Это относится к обоим видам пользовательских материализаций:

* **Придуманное вами имя:** например, `materialized='my_custom_load'`. Их можно найти в настройках моделей.
* **Встроенное имя:** например, ваш собственный макрос с именем `materialization table, default`. Их сложнее обнаружить, потому что модели с `materialized='table'` выглядят стандартными, но выполняют ваш код вместо кода dbt.

#### Пользовательские материализации, изменяющие схему

dbt v2 определяет столбцы модели по её SQL, не анализируя код материализации. Пользовательские операторы `create`, логика слияния или выдача прав допустимы, *если* они меняют только способ построения модели и оставляют столбцы неизменными. Но если ваша материализация добавляет или переименовывает столбцы либо меняет их типы данных, схема, выведенная dbt, не будет соответствовать таблице в хранилище данных.

Это в первую очередь затрагивает `strict`, которому, в отличие от `baseline`, для проверки типов нужны точные схемы вышележащих моделей. Если вы используете `strict`, задайте `static_analysis: off` для моделей с материализацией, изменяющей схему:

models/my\_schema\_changing\_model.sql

```sql
{{ config(
    materialized='my_custom_load',
    static_analysis='off'
) }}

select ...
```

Поскольку `off` распространяется каскадно, все нижележащие модели также теряют статический анализ. Чтобы сохранить охват анализа по всему DAG, используйте материализации, изменяющие схему, для моделей, от которых больше ничего не зависит.

### Определение режима модели

Настроенный вами режим не всегда совпадает с действующим, поскольку фактический режим модели также зависит от её родительских моделей. И [расширение dbt для VS Code](https://docs.getdbt.tech/docs/about-dbt-extension.md), и Studio IDE показывают над каждой моделью CodeLens, который сообщает, когда статический анализ отключён и почему.

Помните, что `dbt ls --output json --output-keys config.static_analysis` сообщает режим, который вы *настроили* для каждой модели, а не режим, который v2 определяет после применения правил каскадного распространения.

### Примеры настроек

##### Настройка strict для всего проекта

Многие команды хотят включить режим `strict` для всего проекта и всех пакетов. Для этого задайте `+static_analysis: strict` для каждого типа ресурсов в `dbt_project.yml` в разделе с именем вашего проекта (и в разделах с именами пакетов, если вы хотите включить строгий режим и для них):

dbt\_project.yml

```yaml
models:
  my_project:
    +static_analysis: strict

seeds:
  my_project:
    +static_analysis: strict

snapshots:
  my_project:
    +static_analysis: strict

tests:
  my_project:
    +static_analysis: strict

unit_tests:
  my_project:
    +static_analysis: strict

sources:
  my_project:
    +static_analysis: strict

analyses:
  my_project:
    +static_analysis: strict
```

Вместо `my_project` используйте имя вашего проекта — то же значение, что указано в ключе `name:` в начале `dbt_project.yml` (например, `jaffle_shop`). Чтобы применить `strict` также к пакету, добавьте ещё одну запись для каждого типа ресурсов, используя имя пакета в качестве ключа; например, в разделе `models:` добавьте `your_package_name:`, а под ним — `+static_analysis: strict`.

##### Отключение статического анализа для всех моделей пакета:

dbt\_project.yml

```yml
name: jaffle_shop

models:
  jaffle_shop: 
    marts:
      +materialized: table
  
  a_package_with_introspective_queries:
    +static_analysis: off
```

##### Отключение статического анализа в YAML:

models/my\_udf\_using\_model.yml

```yml
models:
  - name: model_with_static_analysis_off
    config:
      static_analysis: off
```

##### Отключение статического анализа для модели, использующей пользовательскую UDF:

models/my\_udf\_using\_model.sql

```sql
{{ config(static_analysis='off') }}

select 
  user_id,
  my_cool_udf(ip_address) as cleaned_ip
from {{ ref('my_model') }}
```

### Можно ли использовать режим strict при разработке и baseline при развёртывании?

Да. Это допустимый и рекомендуемый подход: используйте `strict` во время разработки для более строгой проверки, а при развёртывании сохраняйте `baseline` для более быстрых запусков, которые с меньшей вероятностью будут остановлены из-за результатов анализа.

Дополнительные сведения, включая примеры CLI и необязательный подход с использованием переменной окружения, см. в разделе [Оптимизация статического анализа для разработки и развёртывания](https://docs.getdbt.tech/best-practices/optimize-static-analysis-for-development-and-deployment.md).

### Когда следует отключать статический анализ с помощью `off`?

Благодаря включённому по умолчанию режиму baseline статический анализ реже блокирует выполнение. Отключать его следует только в том случае, если dbt v2 не может разобрать SQL, корректный для выбранной вами базы данных.

Это происходит крайне редко. Если вы столкнулись с такой ситуацией, [создайте issue](https://github.com/dbt-labs/dbt/issues) с примером SQL, вызывающего ошибку, чтобы мы могли обновить наши парсеры.

## Дополнительная информация о dbt v2

* [Журнал изменений dbt v2](https://github.com/dbt-labs/dbt/blob/main/CHANGELOG-dbt.md)
* [О расширении dbt](https://docs.getdbt.tech/docs/about-dbt-extension.md)
* [Установка dbt v2](https://docs.getdbt.tech/docs/local/install-dbt.md)
* [Краткое руководство по началу работы с dbt v2](https://docs.getdbt.tech/guides/dbt.md?step=1)
* [Руководство по обновлению](https://docs.getdbt.tech/docs/dbt-versions/dbt-upgrade/upgrading-to-v2.md)
* [Лицензионное соглашение dbt v2](https://www.getdbt.com/dbt-fusion-engine-license-agreement)
