Обязательные macros для adapter
Production-grade adapter имеет ~15 обязательных macros в dbt/include/<name>/macros/adapters.sql. Эти macros — specification для adapter conformance. Each used by dbt-core в specific operations.
В этом уроке — полный checklist с примерами implementations.
Определение macros: синтаксис, аргументы, return (dbt I) adapter.dispatch: multi-warehouse macros (dbt II)
Главные macros и их роли
Schema management macros
<adapter>__list_schemas(database)
Purpose: Return list of schemas в database.
Default (ANSI SQL):
{% macro default__list_schemas(database) %}
{% call statement('list_schemas', fetch_result=True, auto_begin=False) %}
SELECT DISTINCT schema_name
FROM {{ information_schema_name(database) }}.SCHEMATA
{% endcall %}
{{ return(load_result('list_schemas').table) }}
{% endmacro %}
OceanBase override:
{% macro oceanbase__list_schemas(database) %}
{% call statement('list_schemas', fetch_result=True, auto_begin=False) %}
SHOW DATABASES
{% endcall %}
{{ return(load_result('list_schemas').table) }}
{% endmacro %}
BigQuery override:
{% macro bigquery__list_schemas(database) %}
{% set sql %}
SELECT schema_name FROM `{{ database }}`.INFORMATION_SCHEMA.SCHEMATA
{% endset %}
{% set result = run_query(sql) %}
{{ return(result) }}
{% endmacro %}
<adapter>__create_schema(relation)
Purpose: Create schema if not exists.
{% macro default__create_schema(relation) %}
{%- call statement('create_schema') -%}
CREATE SCHEMA IF NOT EXISTS {{ relation.without_identifier() }}
{%- endcall -%}
{% endmacro %}
relation.without_identifier() returns database.schema (drops identifier).
OceanBase:
{% macro oceanbase__create_schema(relation) %}
{%- call statement('create_schema') -%}
CREATE DATABASE IF NOT EXISTS {{ relation.without_identifier() }}
{%- endcall -%}
{% endmacro %}
OceanBase uses CREATE DATABASE instead of CREATE SCHEMA.
<adapter>__drop_schema(relation)
{% macro default__drop_schema(relation) %}
{%- call statement('drop_schema') -%}
DROP SCHEMA IF EXISTS {{ relation.without_identifier() }} CASCADE
{%- endcall -%}
{% endmacro %}
CASCADE — drops all relations в schema. Used by dbt clean.
<adapter>__check_schema_exists(information_schema, schema)
Purpose: Check if schema exists.
{% macro default__check_schema_exists(information_schema, schema) %}
{% set sql -%}
SELECT COUNT(*)
FROM {{ information_schema }}.SCHEMATA
WHERE SCHEMA_NAME = '{{ schema }}'
AND CATALOG_NAME = '{{ information_schema.database }}'
{%- endset %}
{% set result = run_query(sql) %}
{{ return(result) }}
{% endmacro %}
Relation introspection macros
<adapter>__list_relations_without_caching(schema_relation)
Purpose: List all tables/views в schema. Critical для cache initialization.
{% macro default__list_relations_without_caching(schema_relation) %}
{% call statement('list_relations_without_caching', fetch_result=True) %}
SELECT
'{{ schema_relation.database }}' AS database,
tablename AS name,
schemaname AS schema,
'table' AS type
FROM {{ information_schema_name(schema_relation.database) }}.TABLES
WHERE schemaname = '{{ schema_relation.schema }}'
UNION ALL
SELECT
'{{ schema_relation.database }}' AS database,
viewname AS name,
schemaname AS schema,
'view' AS type
FROM {{ information_schema_name(schema_relation.database) }}.VIEWS
WHERE schemaname = '{{ schema_relation.schema }}'
{% endcall %}
{{ return(load_result('list_relations_without_caching').table) }}
{% endmacro %}
Result format: 4-column table — database, name, schema, type.
OceanBase:
{% macro oceanbase__list_relations_without_caching(schema_relation) %}
{% call statement('list_relations_without_caching', fetch_result=True) %}
SELECT
'{{ schema_relation.database }}' AS database,
table_name AS name,
table_schema AS schema,
CASE table_type
WHEN 'BASE TABLE' THEN 'table'
WHEN 'VIEW' THEN 'view'
END AS type
FROM information_schema.tables
WHERE table_schema = '{{ schema_relation.schema }}'
{% endcall %}
{{ return(load_result('list_relations_without_caching').table) }}
{% endmacro %}
<adapter>__get_columns_in_relation(relation)
Purpose: Get columns с types для relation.
{% macro default__get_columns_in_relation(relation) %}
{% call statement('get_columns_in_relation', fetch_result=True) %}
SELECT
column_name,
data_type,
character_maximum_length,
numeric_precision,
numeric_scale
FROM {{ information_schema_name(relation.database) }}.columns
WHERE table_schema = '{{ relation.schema }}'
AND table_name = '{{ relation.identifier }}'
ORDER BY ordinal_position
{% endcall %}
{% set table = load_result('get_columns_in_relation').table %}
{{ return(sql_convert_columns_in_relation(table)) }}
{% endmacro %}
sql_convert_columns_in_relation is helper from base — converts table rows to Column instances.
Snowflake:
{% macro snowflake__get_columns_in_relation(relation) %}
{%- set sql -%}
DESCRIBE TABLE {{ relation }}
{%- endset -%}
{%- set result = run_query(sql) -%}
-- Parse Snowflake-specific output format
...
{% endmacro %}
DDL macros
<adapter>__rename_relation(from_relation, to_relation)
{% macro default__rename_relation(from_relation, to_relation) -%}
{% call statement('rename_relation') -%}
ALTER TABLE {{ from_relation }} RENAME TO {{ to_relation }}
{%- endcall %}
{% endmacro %}
Note: RENAME TO {{ to_relation }} requires то-relation WITHOUT schema prefix (just identifier). Adapter-specific:
{% macro postgres__rename_relation(from_relation, to_relation) -%}
ALTER TABLE {{ from_relation }} RENAME TO {{ to_relation.identifier }}
{% endmacro %}
{% macro snowflake__rename_relation(from_relation, to_relation) -%}
ALTER TABLE {{ from_relation }} RENAME TO {{ to_relation }}
{% endmacro %}
Subtle differences.
<adapter>__truncate_relation(relation)
{% macro default__truncate_relation(relation) -%}
{% call statement('truncate_relation') -%}
TRUNCATE TABLE {{ relation }}
{%- endcall %}
{% endmacro %}
Some warehouses не support TRUNCATE:
{% macro myadapter__truncate_relation(relation) -%}
-- MyAdapter has no TRUNCATE — use DELETE
{% call statement('truncate_relation') -%}
DELETE FROM {{ relation }}
{%- endcall %}
{% endmacro %}
<adapter>__drop_relation(relation)
{% macro default__drop_relation(relation) -%}
{% call statement('drop_relation', auto_begin=False) -%}
DROP {{ relation.type }} IF EXISTS {{ relation }} CASCADE
{%- endcall %}
{% endmacro %}
relation.type — ‘table’, ‘view’, ‘cte’. CASCADE drops dependent objects.
<adapter>__alter_column_type(relation, column_name, new_column_type)
{% macro default__alter_column_type(relation, column_name, new_column_type) -%}
{%- set tmp_column = column_name + "__dbt_alter" -%}
{% call statement('alter_column_type') %}
ALTER TABLE {{ relation }}
ADD COLUMN {{ tmp_column }} {{ new_column_type }};
UPDATE {{ relation }}
SET {{ tmp_column }} = {{ column_name }};
ALTER TABLE {{ relation }}
DROP COLUMN {{ column_name }};
ALTER TABLE {{ relation }}
RENAME COLUMN {{ tmp_column }} TO {{ column_name }};
{% endcall %}
{% endmacro %}
For warehouses без direct ALTER COLUMN TYPE (e.g., SQLite, older Snowflake).
For warehouses с direct ALTER:
{% macro postgres__alter_column_type(relation, column_name, new_column_type) -%}
ALTER TABLE {{ relation }}
ALTER COLUMN {{ column_name }} TYPE {{ new_column_type }}
USING {{ column_name }}::{{ new_column_type }};
{% endmacro %}
DML helpers
<adapter>__create_table_as(temporary, relation, sql)
{% macro default__create_table_as(temporary, relation, sql) -%}
{%- set sql_header = config.get('sql_header', none) -%}
{{ sql_header if sql_header is not none }}
CREATE {% if temporary %}TEMPORARY {% endif %}TABLE
{{ relation.include(database=(not temporary), schema=(not temporary)) }}
AS (
{{ sql }}
);
{%- endmacro %}
Temporary tables — created без db/schema (just identifier).
Snowflake имеет TRANSIENT tables:
{% macro snowflake__create_table_as(temporary, relation, sql) -%}
{%- set transient = config.get('transient', default=false) -%}
CREATE OR REPLACE
{% if transient %}TRANSIENT {% endif %}
{% if temporary %}TEMPORARY {% endif %}
TABLE {{ relation }} AS ({{ sql }})
{%- endmacro %}
<adapter>__create_view_as(relation, sql)
{% macro default__create_view_as(relation, sql) -%}
CREATE OR REPLACE VIEW {{ relation }} AS (
{{ sql }}
);
{%- endmacro %}
Standard.
BigQuery не supports OR REPLACE:
{% macro bigquery__create_view_as(relation, sql) -%}
CREATE VIEW {{ relation }} AS ({{ sql }})
{%- endmacro %}
Timestamp macros
<adapter>__current_timestamp()
Purpose: Used by snapshots, audit.
{% macro default__current_timestamp() %}
CURRENT_TIMESTAMP
{% endmacro %}
Snowflake:
{% macro snowflake__current_timestamp() %}
current_timestamp::TIMESTAMP_NTZ
{% endmacro %}
BigQuery:
{% macro bigquery__current_timestamp() %}
CURRENT_TIMESTAMP()
{% endmacro %}
<adapter>__current_timestamp_in_utc()
{% macro default__current_timestamp_in_utc() %}
{{ adapter.dispatch('current_timestamp_in_utc_backcompat', 'dbt')() }}
{% endmacro %}
UTC-specific. Used когда need timezone-aware timestamps.
information_schema macros
<adapter>__information_schema_name(database)
Purpose: Return location of information_schema (varies by warehouse).
{% macro default__information_schema_name(database) %}
{%- if database -%}
{{ adapter.quote_as_configured(database, 'database') }}.information_schema
{%- else -%}
information_schema
{%- endif -%}
{% endmacro %}
Snowflake:
{% macro snowflake__information_schema_name(database) %}
{%- if database -%}
{{ database }}.information_schema
{%- else -%}
information_schema
{%- endif -%}
{% endmacro %}
BigQuery: information_schema is per-dataset:
{% macro bigquery__information_schema_name(database) %}
-- BigQuery doesn't have a global information_schema
-- This may not be applicable
{% endmacro %}
Grants macros (optional)
If adapter supports GRANT/REVOKE:
<adapter>__copy_grants()
Purpose: Return true if warehouse supports COPY GRANTS clause (Snowflake-specific).
{% macro default__copy_grants() %}
{{ return(False) }}
{% endmacro %}
{% macro snowflake__copy_grants() %}
{%- set copy_grants_config = config.get('copy_grants', false) -%}
{{ return(copy_grants_config) }}
{% endmacro %}
<adapter>__support_multiple_grantees_per_dcl_statement()
{% macro default__support_multiple_grantees_per_dcl_statement() %}
{{ return(True) }}
{% endmacro %}
True if can do GRANT SELECT TO role1, role2. False if must be separate statements.
<adapter>__get_show_grant_sql(relation)
{% macro default__get_show_grant_sql(relation) %}
show grants on {{ relation }}
{% endmacro %}
Special: snapshot_get_time
For snapshots:
{% macro default__snapshot_get_time() -%}
{{ current_timestamp() }}
{%- endmacro %}
get_catalog macros
For dbt docs generate:
{% macro default__get_catalog(information_schema, schemas) %}
{% set query %}
WITH tables AS (
SELECT
table_catalog AS table_database,
table_schema,
table_name,
...
FROM {{ information_schema }}.tables
WHERE table_schema IN ({% for schema in schemas %}'{{ schema }}'{%- if not loop.last %}, {% endif -%}{% endfor %})
),
columns AS (
SELECT
...
FROM {{ information_schema }}.columns
)
SELECT * FROM tables JOIN columns USING (...)
{% endset %}
{{ return(run_query(query)) }}
{% endmacro %}
Generates metadata catalog для documentation site.
Complete adapter macros file template
-- dbt/include/myadapter/macros/adapters.sql
{% macro myadapter__list_schemas(database) %}
...
{% endmacro %}
{% macro myadapter__create_schema(relation) %}
...
{% endmacro %}
{% macro myadapter__drop_schema(relation) %}
...
{% endmacro %}
{% macro myadapter__check_schema_exists(information_schema, schema) %}
...
{% endmacro %}
{% macro myadapter__list_relations_without_caching(schema_relation) %}
...
{% endmacro %}
{% macro myadapter__get_columns_in_relation(relation) %}
...
{% endmacro %}
{% macro myadapter__rename_relation(from_relation, to_relation) %}
...
{% endmacro %}
{% macro myadapter__truncate_relation(relation) %}
...
{% endmacro %}
{% macro myadapter__drop_relation(relation) %}
...
{% endmacro %}
{% macro myadapter__current_timestamp() %}
...
{% endmacro %}
{% macro myadapter__create_table_as(temporary, relation, sql) %}
...
{% endmacro %}
{% macro myadapter__create_view_as(relation, sql) %}
...
{% endmacro %}
-- Optional: grants
{% macro myadapter__copy_grants() %}
...
{% endmacro %}
-- Optional: catalog
{% macro myadapter__get_catalog(information_schema, schemas) %}
...
{% endmacro %}
15+ macros minimum. Production adapter — 30-50.
Попробуй сам
- Find dbt-postgres macros file:
find $(pip show dbt-postgres | grep Location | cut -d' ' -f2) -name "adapters.sql"
- Open и read. Compare к default__ versions в dbt-adapters:
find $(pip show dbt-adapters | grep Location | cut -d' ' -f2) -name "adapters.sql"
-
Identify which macros override vs inherit.
-
Pick one macro (e.g.,
list_relations_without_caching). Compare implementation across 3 adapters (postgres, snowflake, duckdb). -
For your custom adapter, start с минимума:
- list_schemas
- check_schema_exists
- create_schema, drop_schema
- list_relations_without_caching
- get_columns_in_relation
- drop_relation
- rename_relation
- current_timestamp
8 macros = dbt run works для basic models.
Ключевые выводы
-
15+ обязательных macros для production adapter. 8 minimum для basic dbt run.
-
Schema management: list_schemas, create_schema, drop_schema, check_schema_exists.
-
Relation introspection: list_relations_without_caching, get_columns_in_relation.
-
DDL operations: rename_relation, truncate_relation, drop_relation, alter_column_type.
-
DML helpers: create_table_as, create_view_as.
-
Misc: current_timestamp, information_schema_name.
-
Optional: grants macros, get_catalog (для docs generate).
-
Default versions в
dbt-adapters/dbt/include/global_project/macros/adapters/— use ANSI SQL. Override only when warehouse-specific. -
Adapter-specific через
<adapter>__macro_name. Dispatch finds it automatically. -
Reference: всегда смотрите dbt-postgres, dbt-snowflake, dbt-bigquery sources как inspiration.