How to use dbt-greengage adapter
Overview
The dbt-greengage adapter enables dbt to work with Greengage DB (based on Greenplum) and is included in the ADO bundle together with the DBT service.
The dbt-greengage adapter is based on dbt-postgres and adds support for Greengage-specific features, including:
-
Append-Optimized (AO) tables;
-
Column-Oriented (CO) storage;
-
distribution policies;
-
partitioning;
-
external tables;
-
materialized views and indexes.
The adapter provides:
-
compatibility with dbt Core versions 1.3-1.11;
-
support for Greengage-specific functionality;
-
backward compatibility with existing dbt-greenplum projects.
|
NOTE
The current release supports only Greengage DB 6 (based on PostgreSQL 9). When connecting to a Greengage DB 7 server, table creation operations may fail with a compilation error. Greengage DB 7 (based on PostgreSQL 12) support is planned for future releases.
|
Configuration
The adapter is included in ADO together with the DBT service. For more information on the DBT service, refer to the DBT service overview article.
To configure DBT for working with a Greengage DB database, create a profiles.yml configuration file that describes properties for the database connection.
Example profiles.yml:
my-greengage-project:
target: dev
outputs:
dev:
type: greengage
host: localhost
port: 5432
user: gpadmin
password: <password>
dbname: warehouse
schema: dbt_dev
threads: 4
The required parameters are:
-
host— Greengage DB host; -
user— database user; -
password— user password; -
dbname— target database name; -
schema— target schema for dbt objects.
The profiles.yml configuration file must be on the host with a DBT component. The path to the configuration file’s directory must be added as a value of the DBT_PROFILES_DIR property in DBT service configuration in ADCM.
Alternatively, you can specify the path in the Profiles path property when running a DBT command.
| Parameter | Description | Default value |
|---|---|---|
port |
Database port |
5432 |
threads |
Number of parallel dbt threads |
1 |
keepalives_idle |
Number of seconds of inactivity before a TCP keepalive check is sent |
0 |
connect_timeout |
Connection timeout in seconds |
10 |
search_path |
Overrides PostgreSQL |
— |
role |
Executes |
— |
sslmode |
SSL mode, for example: |
— |
kerberos_service_name |
Kerberos service name for GSSAPI authentication |
— |
The full list of connection parameters is identical to dbt-postgres.
DBT commands
The DBT service supports the standard dbt commands for Greengage DB. It provides execution of dbt models, tests, snapshots, documentation generation, and incremental transformations via ADCM actions and Airflow DAGs.
You can run a DBT command using ADCM service actions.
Materializations
dbt-greengage supports multiple materialization types. The adapter automatically selects Greengage-specific SQL generation paths depending on:
-
Greengage DB version;
-
materialization type;
-
storage configuration;
-
partitioning configuration.
The following materializations are supported:
table
The table materialization creates a physical table and fully manages its lifecycle.
Example:
{{ config(materialized='table') }}
|
NOTE
Greengage DB does not support CREATE OR REPLACE TABLE. Because of this limitation, dbt-greengage implements replacement through explicit drop-and-recreate logic. This is handled
transparently via GreengageRelation, which restricts replaceable_relations to views only.
|
During dbt run --full-refresh, the adapter performs:
-
for regular tables:
DROP TABLE IF EXISTS … CASCADE→CREATE TABLE; -
for external tables:
DROP EXTERNAL TABLE IF EXISTS … CASCADE; -
for views:
CREATE OR REPLACE VIEW(works directly).
Example:
DROP TABLE IF EXISTS users CASCADE;
CREATE TABLE users AS
SELECT 1 AS id, 'Alice' AS name;
The CASCADE keyword is required because schemas often contain dependent objects.
The table materialization fully supports Greengage DB storage parameters.
Example:
{{ config(
materialized='table',
appendoptimized=true,
orientation='column',
compresstype='ZSTD',
compresslevel=4,
blocksize=32768
) }}
Generated DDL:
CREATE TABLE users
WITH (
appendoptimized=true,
orientation=column,
compresstype=ZSTD,
compresslevel=4,
blocksize=32768
)
AS
SELECT ...
view
The view materialization creates a standard SQL view in Greengage DB. In dbt-greengage, the implementation is mostly inherited from dbt-postgres. However, Greengage DB introduces several MPP-specific behaviors related to object dependencies and relation cleanup, therefore the adapter provides its own relation drop implementation.
Unlike PostgreSQL, Greengage DB environments frequently contain deeply interconnected objects: partition hierarchies, dependent views, external tables, materialized views, and cross-schema analytical objects.
To ensure reliable cleanup and recreation, dbt-greengage overrides the greengage__drop_relation macro. All relation drops use CASCADE semantics where required.
Example lifecycle during model replacement:
DROP VIEW IF EXISTS analytics.products CASCADE;
CREATE VIEW analytics.products AS
SELECT ...
This behavior guarantees that dependent objects are removed correctly before recreation.
Example:
{{ config(
materialized='view'
) }}
select
order_id,
customer_id,
total_amount,
created_at
from {{ ref('stg_orders') }}
incremental
The incremental materialization is optimized for large analytical datasets and supports multiple loading strategies.
| Strategy | Status | Description |
|---|---|---|
append |
Inherited |
|
delete+insert |
Inherited |
|
truncate+insert |
Custom |
|
microbatch |
Custom |
|
|
NOTE
The adapter does not support the merge strategy because |
{{ config(
materialized='incremental',
incremental_strategy='truncate+insert'
) }}
select * from {{ ref('source_data') }}
This strategy is safe for heap tables because TRUNCATE removes all rows without affecting the table structure, indexes, permissions, or dependent objects. On partitioned tables, TRUNCATE removes data from all partitions.
It is recommended to use delete+insert with incremental_predicates filtered by the partition key.
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='created_at',
begin='2024-01-01',
batch_size='month'
) }}
select * from {{ ref('source_data') }}
The microbatch strategy uses batch configuration and the event_time column as described in dbt microbatch documentation.
For each batch, a DELETE is executed over the event_time window, followed by an INSERT from a temporary table.
The unique_key parameter is not required, batch boundaries are defined by the event_time window.
To reference the target table, use DBT_INTERNAL_DEST in incremental_predicates as follows:
{{ config(
materialized='incremental',
incremental_strategy='microbatch',
event_time='created_at',
begin='2024-01-01',
batch_size='month',
incremental_predicates=["DBT_INTERNAL_DEST.status != 'archived'"]
) }}
select * from {{ ref('source_data') }}
The adapter replaces the alias with the real table name (there is no USING clause in DELETE).
Constraints
The adapter supports dbt model contracts and constraint definitions.
| Constraint | Supported | Enforced |
|---|---|---|
check |
Yes |
Yes |
not_null |
Yes |
Yes |
unique |
Yes |
No |
primary_key |
Yes |
No |
foreign_key |
Yes |
No |
|
NOTE
When contract.enforced=true is configured, dbt-greengage emits a warning because full contract enforcement is not guaranteed for all constraint types.
|
Storage configuration (AO/heap)
Greengage DB supports both heap tables and Append-Optimized (AO) tables with columnar and row orientation.
AO-storage parameters are configured directly in model configuration and can be of two types:
-
Row-oriented AO tables are suitable for workloads with frequent row access or mixed OLTP/analytical patterns.
Example:
{{ config( materialized='table', appendoptimized=true, orientation='row' ) }} -
Column-oriented AO tables are recommended for analytical fact tables and aggregation-heavy workloads.
Example:
{{ config( materialized='table', appendoptimized=true, orientation='column', compresstype='ZSTD', compresslevel=4 ) }}
| Parameter | Description | Default value |
|---|---|---|
appendoptimized |
Creates an AO table |
true |
orientation |
Storage orientation: |
column |
compresstype |
Compression type |
ZSTD |
compresslevel |
Compression level from 1 to 9 |
4 |
blocksize |
Block size in bytes |
32768 |
Distribution
The adapter supports the following distribution policies:
-
DISTRIBUTED BY— distributes rows according to hash values of one or more columns.Example:
{{ config( materialized='table', distributed_by='customer_id' ) }} -
DISTRIBUTED REPLICATED— copies the full table to all segments.Example:
{{ config( materialized='table', distributed_replicated=true ) }} -
DISTRIBUTED RANDOMLY— If no distribution policy is configured, the adapter distributes randomly.Example:
{{ config(materialized='table') }}
Partitioning
DBT Greengage DB adapter supports:
-
raw partition definitions;
-
parameterized
RANGEpartitions; -
parameterized
LISTpartitions.
Greengage DB 6 does not support CREATE TABLE AS together with PARTITION BY. Because of this limitation, dbt-greengage automatically switches to a two-step process:
CREATE TABLE ...
INSERT INTO ...
Partitioned tables therefore require explicit column definitions using the fields_string parameter.
The raw_partition option allows passing a complete partition clause.
| Parameter | Config | Description |
|---|---|---|
fields_string |
config.get() |
Column definitions for |
raw_partition |
GreengageConfig |
Full |
partition_type |
GreengageConfig |
|
partition_column |
GreengageConfig |
Column to partition by |
partition_start |
config.get() |
Start value for |
partition_end |
config.get() |
End value for |
partition_every |
config.get() |
Interval for |
partition_values |
config.get() |
|
default_partition_name |
config.get() |
Name of the default partition (default: |
{% set fields_string %}
id int4 null,
event_date timestamp null
{% endset %}
{% set raw_partition %}
PARTITION BY RANGE (event_date)
(
START ('2024-01-01'::timestamp) INCLUSIVE
END ('2025-01-01'::timestamp) EXCLUSIVE
EVERY (INTERVAL '1 month'),
DEFAULT PARTITION other
)
{% endset %}
{{ config(
materialized='table',
distributed_by='id',
fields_string=fields_string,
raw_partition=raw_partition
) }}
{{ config(
materialized='table',
distributed_by='id',
fields_string=fields_string,
partition_type='RANGE',
partition_column='event_date',
partition_start='2024-01-01',
partition_end='2025-01-01',
partition_every='1 month'
) }}
{{ config(
materialized='table',
distributed_by='id',
fields_string=fields_string,
partition_type='LIST',
partition_column='region',
partition_values="PARTITION eu VALUES ('EU'), PARTITION us VALUES ('US')"
) }}
External tables and data loading
The adapter provides macros for creating and dropping Greengage DB external tables. It supports the gpfdist and PXF protocols.
gpfdist serves flat files from an ETL host (gpfdist://, gpfdists://), supports TEXT and CSV formats, and allows multiple URLs in a single LOCATION clause for parallel loading across segments.
PXF connects to external data sources (HDFS, HBase, Hive, S3, JDBC) via pxf:// URLs. It supports TEXT, CSV, and CUSTOM formats (with a FORMATTER). PXF requires exactly one URL per LOCATION clause, multiple locations are not allowed.
External tables are managed via utility macros and are typically used in:
-
pre-hook;
-
post-hook;
-
dbt run-operation.
| Parameter | Description | Default value |
|---|---|---|
relation |
Target external table relation (schema and table name) to create. This parameter is required |
— |
fields_string |
Column definitions for the external table, specified in SQL format (for example, |
— |
location |
Source location for the external data. Can be a single URL or a list of URLs. Multiple URLs are supported only for gpfdist sources. This parameter is required |
— |
format |
Data format in the external table. Supported values are |
TEXT |
formatter |
The name of the custom formatter used to process data in the |
— |
delimiter |
Field delimiter |
, |
null_string |
String representing |
— |
escape |
Escape character ( |
— |
header |
Specifies whether the first line is header |
false |
encoding |
Character encoding |
UTF8 |
on_clause |
Determines where data processing is performed: on all segments ( |
— |
log_errors |
Enables error logging |
false |
segment_reject_limit |
Reject limit value |
— |
segment_reject_limit_type |
Unit of measurement for the |
rows |
execute |
Determines whether the generated SQL query should be executed immediately ( |
true |
{% set ext_relation = api.Relation.create(schema='ext_schema', identifier='ext_orders') %}
{{ create_external_table(
relation=ext_relation,
fields_string='order_id INTEGER, order_date DATE, amount NUMERIC',
location="'gpfdist://etl-host:8080/orders*.csv'",
format='CSV',
delimiter=',',
header=true,
log_errors=true,
segment_reject_limit=100,
segment_reject_limit_type='rows'
) }}
{{ create_external_table(
relation=ext_relation,
fields_string='id INTEGER, name TEXT, value NUMERIC',
location=[
"'gpfdist://host1:8080/data.csv'",
"'gpfdist://host2:8081/data.csv'"
],
format='CSV',
header=true
) }}
{{ create_external_table(
relation=ext_relation,
fields_string='event_id INTEGER, payload TEXT',
location="'pxf://data/events?PROFILE=hdfs:text'",
format='TEXT',
delimiter='|'
) }}
{{ create_external_table(
relation=ext_relation,
fields_string='row_key TEXT, cf1_col1 TEXT, cf1_col2 TEXT',
location="'pxf://hbase_table?PROFILE=HBase'",
format='CUSTOM',
formatter='pxfwritable_import'
) }}
The adapter also supports writable external tables.
Example:
{% set ext_relation = api.Relation.create(schema='ext_schema', identifier='ext_export') %}
{{ create_writable_external_table(
relation=ext_relation,
fields_string='id INTEGER, name TEXT',
location="'gpfdist://etl-host:8080/output.csv'",
format='CSV',
distributed_by='id'
) }}
Materialized views
The dbt-greengage adapter provides a custom implementation of materialized_view with built-in index management.
Unlike regular views, materialized views physically store query results on disk. This requires the materialized view to be refreshed when source data changes.
When a materialized view is created, the adapter executes a CREATE MATERIALIZED VIEW … AS … statement and automatically creates all indexes defined in the model configuration.
During subsequent dbt run executions, the adapter compares the current materialized view definition with the desired configuration. If only index definitions have changed, the adapter performs a targeted update by dropping and recreating only the affected indexes. If the underlying query has changed or the materialized view must be recreated, the adapter replaces the entire object.
Operations such as refresh, rename, drop, describe, and configuration comparison are inherited from dbt-postgres.
Example:
{{ config(
materialized='materialized_view',
indexes=[
{'columns': ['user_id'], 'type': 'btree', 'unique': true},
{'columns': ['order_count'], 'type': 'bitmap'},
]
) }}
select
user_id,
count(*) as order_count,
sum(amount) as total_amount
from {{ ref('orders') }}
group by user_id
Indexes can also be configured in dbt_project.yml or schema.yml:
models:
- name: user_order_summary
config:
materialized: materialized_view
indexes:
- columns: ['user_id']
type: btree
unique: true
- columns: ['order_count']
type: bitmap
| Parameter | Type | Description | Default value |
|---|---|---|---|
columns |
list |
List of columns included in the index. This parameter is required |
— |
unique |
bool |
Creates a unique index |
false |
type |
string |
Index method to use |
btree |
|
NOTE
Greengage DB does not support CREATE OR REPLACE MATERIALIZED VIEW. As a result, materialized views are treated as renameable relations but not replaceable relations. When a full recreation is required, the adapter drops and recreates the materialized view rather than using an in-place replacement operation.
|
Indexes
The adapter provides native support for index creation through the greengage__get_create_index_sql macro.
When an index definition is supplied in a model configuration, the adapter automatically validates the configuration, generates the appropriate SQL statement, and creates the index as part of the materialization workflow. Index names are generated automatically.
Generated SQL follows the pattern:
CREATE [UNIQUE] INDEX "<generated_name>"
ON <relation>
[USING <index_type>]
(<columns>)
Indexes are used together with materialized views (via the indexes parameter) and are available for calling from macros via get_create_index_sql(relation, index_dict).
| Index type | Description |
|---|---|
btree |
Default index type. Recommended for equality predicates, sorting operations, and range queries |
bitmap |
Greengage-specific index type optimized for columns with low cardinality, such as status flags, categories, and other columns with a limited number of distinct values |
hash |
Optimized for exact-match lookups using equality operators |
gist |
Generalized search tree index suitable for geospatial data, full-text search, and other complex data types |
spgist |
Space-partitioned GiST index designed for IP addresses, phone numbers, and hierarchical data |
gin |
Generalized inverted index commonly used for arrays, JSONB documents, and full-text search |
brin |
Block range index designed for very large tables where data is naturally ordered, such as timestamp-based fact tables |
Extensions
The adapter provides a set of Jinja macros for managing database extensions in Greengage DB.
These macros can be executed from:
-
dbt models;
-
on-run-start and on-run-end hooks;
-
pre-hook and post-hook;
-
dbt run-operationcommands.
An example of creating a database extension:
{{ create_extension(
extension_name='hstore',
schema='public',
version='1.4',
cascade=true
) }}
Generated SQL:
CREATE EXTENSION "hstore"
WITH SCHEMA "public"
VERSION '1.4'
CASCADE
Create an extension only if it does not already exist:
{{ create_extension_if_not_exists(
extension_name='postgis'
) }}
Generated SQL:
CREATE EXTENSION IF NOT EXISTS "postgis"
This macro is idempotent and can be safely executed multiple times, making it suitable for automated deployment pipelines.
Remove an existing extension:
{{ drop_extension(
extension_name='hstore',
if_exists=true,
cascade=true
) }}
Generated SQL:
DROP EXTENSION IF EXISTS "hstore" CASCADE
| Parameter | Type | Description | Default value |
|---|---|---|---|
extension_name |
string |
Name of the extension to create or remove, for example |
— |
schema |
string |
Schema in which the extension should be installed. Generates the |
— |
version |
string |
Specific extension version to install |
— |
cascade |
boolean |
Automatically installs or removes dependent objects when supported by the extension |
false |
if_exists |
boolean |
Adds the |
true |
execute |
boolean |
Executes the generated SQL immediately. If set to |
true |
Snapshots
The dbt-greengage adapter supports snapshot-based change tracking for Slowly Changing Dimension (SCD) workloads. Snapshot processing uses the same implementation as dbt-postgres, based on UPDATE … FROM and INSERT statements rather than SQL MERGE, which is not available in Greengage DB.
The following snapshot strategies are supported:
-
timestamp— creates a new snapshot version when the value of a specified timestamp column changes (for example,updated_at); -
check— creates a new snapshot version when the values of one or more monitored columns change.
The adapter also supports the hard_deletes option (available in dbt 1.9 and later), which allows tracking records that have been removed from the source dataset.
{% snapshot orders_snapshot %}
{{
config(
target_schema='snapshots',
unique_key='order_id',
strategy='timestamp',
updated_at='updated_at'
)
}}
select * from {{ source('raw', 'orders') }}
{% endsnapshot %}
{% snapshot products_snapshot %}
{{
config(
target_schema='snapshots',
unique_key='product_id',
strategy='check',
check_cols=['name', 'price', 'status']
)
}}
select * from {{ source('raw', 'products') }}
{% endsnapshot %}
If hard_deletes is set to new_record, deleted records are captured as a new row with dbt_is_deleted = True.
{% snapshot customers_snapshot %}
{{
config(
target_schema='snapshots',
unique_key='customer_id',
strategy='timestamp',
updated_at='updated_at',
hard_deletes='new_record'
)
}}
select * from {{ source('raw', 'customers') }}
{% endsnapshot %}
Greengage-specific snapshot behavior
Greengage DB 6 is based on PostgreSQL 9 and has specific type resolution behavior for string literals in UNION ALL queries.
To avoid snapshot compilation issues, the adapter automatically injects explicit ::text casts into snapshot helper SQL.
This behavior ensures compatibility with future Greengage DB versions.
Migration
Migration from dbt-greenplum
The dbt-greengage adapter is designed to be largely compatible with existing dbt-greenplum projects. In most cases, migration requires only minimal configuration changes.
-
Update the adapter type in the profiles.yml configuration file as follows:
type: greengage -
Install the dbt-greengage adapter (either by installing the DBT service or manually) and remove the old adapter package:
$ pip uninstall dbt-greenplum $ pip install dbt-greengage -
Replace
appendonlywithappendoptimizedin model configurations (recommended). The legacyappendonlyparameter remains supported for backward compatibility, so existing projects continue to work without modification. However, usingappendoptimizedis recommended because it reflects Greengage DB terminology.{{ config( appendoptimized=true, orientation='column' ) }} -
Review incremental models and remove the
mergestrategy if it is configured. Greengage DB does not support SQLMERGE, therefore themergeincremental strategy is not available. If it is configured, the adapter raises a validation error during execution. Use one of the supported alternatives. -
Verify custom schema configuration. Unlike some other adapters, dbt-greengage does not automatically prepend
target.schemato custom schema names defined through theschema:property. If your project relies on schema name concatenation, review and adjust schema definitions after migration.
The adapter inherits its connection management from dbt-postgres, which means that all standard connection parameters remain unchanged. Existing settings can be reused without modification.
Migration from dbt-postgres
Migrating from dbt-postgres to dbt-greengage is generally straightforward because the dbt-greengage adapter is built on top of dbt-postgres and supports the same connection configuration model.
Update the adapter type in the profiles.yml configuration file as follows:
type: greengage