Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/install-prerequisites.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ ENGINE_DEPENDENCIES=""

if [ "$ENGINE" == "spark" ]; then
ENGINE_DEPENDENCIES="default-jdk"
elif [ "$ENGINE" == "db2" ]; then
ENGINE_DEPENDENCIES="libxml2-dev build-essential"
elif [ "$ENGINE" == "fabric" ]; then
echo "Installing Microsoft package repository"

Expand Down
14 changes: 14 additions & 0 deletions .github/scripts/wait-for-db.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ risingwave_ready() {
probe_port 4566
}

db2_ready() {
probe_port 50001

echo "Waiting for Db2 to finish initialising (this can take 2-4 minutes)..."
while true; do
if docker exec db2 su - db2inst1 -c "db2 connect to TESTDB" > /dev/null 2>&1; then
echo "Db2 is accepting connections"
break
fi
echo "Db2 not yet ready; sleeping 15s..."
sleep 15
done
}

echo "Waiting for $ENGINE to be ready..."

READINESS_FUNC="${ENGINE}_ready"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ jobs:
fail-fast: false
matrix:
engine:
[duckdb, postgres, mysql, mssql, trino, spark, clickhouse, risingwave, starrocks]
[duckdb, postgres, mysql, mssql, trino, spark, clickhouse, risingwave, starrocks, db2]
env:
PYTEST_XDIST_AUTO_NUM_WORKERS: 2
SQLMESH__DISABLE_ANONYMIZED_ANALYTICS: '1'
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ else
endif

install-dev:
$(PIP) install -e ".[dev,web,slack,dlt,lsp]" ./examples/custom_materializations
$(PIP) install -e ".[dev,web,slack,dlt,lsp,db2]" ./examples/custom_materializations

install-doc:
$(PIP) install -r ./docs/requirements.txt
Expand Down Expand Up @@ -217,6 +217,9 @@ risingwave-test: engine-risingwave-up

starrocks-test: engine-starrocks-up
pytest -n auto -m "starrocks" --reruns 3 --junitxml=test-results/junit-starrocks.xml

db2-test: engine-db2-up
pytest -n auto -m "db2" --reruns 3 --junitxml=test-results/junit-db2.xml

#################
# Cloud Engines #
Expand Down
1 change: 1 addition & 0 deletions docs/guides/connections.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ default_gateway: local_db

* [BigQuery](../integrations/engines/bigquery.md)
* [Databricks](../integrations/engines/databricks.md)
* [Db2](../integrations/engines/db2.md)
* [DuckDB](../integrations/engines/duckdb.md)
* [MotherDuck](../integrations/engines/motherduck.md)
* [MySQL](../integrations/engines/mysql.md)
Expand Down
75 changes: 75 additions & 0 deletions docs/integrations/engines/db2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Db2

This page provides information about how to use SQLMesh with [IBM Db2](https://www.ibm.com/products/db2).

!!! info
The Db2 engine adapter is a community contribution. Due to this, only limited community support is available.

## Local/Built-in Scheduler

**Engine Adapter Type**: `db2`

### Installation

```
pip install "sqlmesh[db2]"
```

### Connection options

| Option | Description | Type | Required |
|---------------------|------------------------------------------------------------------------------------------------|:------:|:--------:|
| `type` | Engine type name - must be `db2` | string | Y |
| `host` | The hostname of the Db2 server | string | Y |
| `port` | The port number of the Db2 server. Default: `50000` | int | N |
| `database` | The name of the Db2 database to connect to | string | Y |
| `username` | The username to use for authentication with the Db2 server | string | Y |
| `password` | The password to use for authentication with the Db2 server | string | Y |
| `db2_schema` | Sets `CURRENTSCHEMA` on the connection. Controls the default schema for unqualified references. Typically set to the same value as `username`. | string | Y |
| `ssl` | Enable TLS/SSL encryption. Default: `false` | bool | N |
| `connect_timeout` | The number of seconds to wait for the connection to the server. Default: `30` | int | N |
| `concurrent_tasks` | Maximum number of tasks to run concurrently. Default: `4` | int | N |

## Important Notes

**State connection:** Db2 is **not supported** as a SQLMesh `state_connection`. Use DuckDB (recommended) or another supported engine for SQLMesh state storage:

```yaml linenums="1"
gateways:
db2:
connection:
type: db2
host: localhost
port: 50000
database: TESTDB
username: db2inst1
password: your_password
db2_schema: db2inst1
state_connection:
type: duckdb
database: ./state/sqlmesh_state.db

default_gateway: db2

model_defaults:
dialect: db2
```

**Table naming:** Db2 rejects table names that start with an underscore (`_`). SQLMesh's default physical table naming convention can generate names beginning with `_`. To avoid this, set `physical_table_naming_convention` to `hash_md5` in your project config:

```yaml
physical_table_naming_convention: hash_md5
```

## Limitations

- **Single catalog only**: Db2 operates in single-catalog mode; cross-catalog queries are not supported.
- **No inline column comments**: Column-level comments cannot be set inline during table creation.
- **No atomic table replacement**: Db2 does not support `CREATE OR REPLACE TABLE`, so full model refreshes are not atomic. There is a brief window during which the table may be empty or partially populated.
- **Identifier length**: Maximum identifier length is 128 characters.
- **No `SELECT ... FOR UPDATE`**: Db2 does not support `SELECT ... FOR UPDATE` in the same way as OLTP databases; SQLMesh removes this clause when executing queries.

## Resources

- [IBM Db2 Documentation](https://www.ibm.com/docs/en/db2)
- [IBM Db2 SQL Reference](https://www.ibm.com/docs/en/db2/11.5?topic=db2-sql)
1 change: 1 addition & 0 deletions docs/integrations/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ SQLMesh supports the following execution engines for running SQLMesh projects (e
* [BigQuery](./engines/bigquery.md) (bigquery)
* [ClickHouse](./engines/clickhouse.md) (clickhouse)
* [Databricks](./engines/databricks.md) (databricks)
* [Db2](./engines/db2.md) (db2)
* [DuckDB](./engines/duckdb.md) (duckdb)
* [Fabric](./engines/fabric.md) (fabric)
* [MotherDuck](./engines/motherduck.md) (motherduck)
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ nav:
- integrations/engines/bigquery.md
- integrations/engines/clickhouse.md
- integrations/engines/databricks.md
- integrations/engines/db2.md
- integrations/engines/duckdb.md
- integrations/engines/fabric.md
- integrations/engines/motherduck.md
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ dev = [
]
dbt = ["dbt-core<2"]
dlt = ["dlt"]
db2 = [
"ibm_db",
"db2-sqlglot-dialect;python_version>=\"3.10\""
]
duckdb = []
fabric = ["pyodbc>=5.0.0"]
fabric-mssql-python = ["mssql-python>=1.1.0;python_version>=\"3.10\""]
Expand Down Expand Up @@ -266,6 +270,7 @@ markers = [
"clickhouse: test for Clickhouse (standalone mode / cluster mode)",
"clickhouse_cloud: test for Clickhouse (cloud mode)",
"databricks: test for Databricks",
"db2: test for Db2",
"duckdb: test for DuckDB",
"fabric: test for Fabric",
"motherduck: test for MotherDuck",
Expand Down
1 change: 0 additions & 1 deletion sqlmesh/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,6 @@ def diff(ctx: click.Context, environment: t.Optional[str] = None) -> None:
)
@click.option(
"--min-intervals",
type=int,
default=None,
help="For every model, ensure at least this many intervals are covered by a missing intervals check regardless of the plan start date",
)
Expand Down
88 changes: 88 additions & 0 deletions sqlmesh/core/config/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
"mssql",
"azuresql",
}
# Note: Db2 is excluded because it doesn't allow table names starting with underscore (_)
# which SQLMesh uses for state tables (_versions, _snapshots, _environments, _intervals).
# Use a separate state_connection (e.g., DuckDB) for Db2 gateways.
FORBIDDEN_STATE_SYNC_ENGINES = {
# Do not support row-level operations
"spark",
Expand Down Expand Up @@ -2602,6 +2605,91 @@ def _connection_factory(self) -> t.Callable:
BaseDuckDBConnectionConfig, # type: ignore[type-abstract]
}


class Db2ConnectionConfig(ConnectionConfig):
host: str
port: int = 50000
database: str
db2_schema: str
username: str
password: str
ssl: bool = False
ssl_cert: t.Optional[str] = None
ssl_key: t.Optional[str] = None
ssl_ca: t.Optional[str] = None
connect_timeout: int = 30

concurrent_tasks: int = 4
register_comments: bool = True
pre_ping: bool = True

type_: t.Literal["db2"] = Field(alias="type", default="db2")
DIALECT: t.ClassVar[t.Literal["db2"]] = "db2"
DISPLAY_NAME: t.ClassVar[t.Literal["Db2"]] = "Db2"
DISPLAY_ORDER: t.ClassVar[t.Literal[19]] = 19

_engine_import_validator = _get_engine_import_validator("ibm_db", "db2")

@property
def _connection_kwargs_keys(self) -> t.Set[str]:
return {
"host",
"port",
"database",
"db2_schema",
"username",
"password",
}

@property
def _engine_adapter(self) -> t.Type[EngineAdapter]:
# DB2 adapter requires Python 3.10+ for db2-sqlglot-dialect
# Use getattr to avoid mypy errors on Python 3.9
return t.cast(
t.Type[EngineAdapter], getattr(engine_adapter, "Db2EngineAdapter", EngineAdapter)
)

def get_catalog(self) -> t.Optional[str]:
"""Db2 stores catalog names in uppercase; normalise here so the default_catalog
passed to the adapter matches what get_current_catalog() returns at runtime."""
catalog = super().get_catalog()
return catalog.upper() if catalog else None

@property
def _connection_factory(self) -> t.Callable:
import ibm_db_dbi # type: ignore

ssl = self.ssl
ssl_cert = self.ssl_cert
ssl_key = self.ssl_key
ssl_ca = self.ssl_ca
connect_timeout = self.connect_timeout

def connect_db2(**kwargs: t.Any) -> t.Any:
conn_str_parts = [
f"DATABASE={kwargs['database']}",
f"HOSTNAME={kwargs['host']}",
f"PORT={kwargs['port']}",
"PROTOCOL=TCPIP",
f"UID={kwargs['username']}",
f"PWD={kwargs['password']}",
f"CURRENTSCHEMA={kwargs['db2_schema']}",
f"CONNECTTIMEOUT={connect_timeout}",
]
if ssl:
conn_str_parts.append("SECURITY=SSL")
if ssl_cert:
conn_str_parts.append(f"SSLClientCertificate={ssl_cert}")
if ssl_key:
conn_str_parts.append(f"SSLClientKey={ssl_key}")
if ssl_ca:
conn_str_parts.append(f"SSLServerCertificate={ssl_ca}")
conn_str = ";".join(conn_str_parts) + ";"
return ibm_db_dbi.connect(conn_str, "", "")

return connect_db2


CONNECTION_CONFIG_TO_TYPE = {
# Map all subclasses of ConnectionConfig to the value of their `type_` field.
tpe.all_field_infos()["type_"].default: tpe
Expand Down
9 changes: 9 additions & 0 deletions sqlmesh/core/engine_adapter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import sys
import typing as t

from sqlmesh.core.engine_adapter.base import (
Expand All @@ -22,6 +23,10 @@
from sqlmesh.core.engine_adapter.risingwave import RisingwaveEngineAdapter
from sqlmesh.core.engine_adapter.fabric import FabricEngineAdapter

# DB2 adapter requires Python 3.10+ for db2-sqlglot-dialect
if sys.version_info >= (3, 10):
from sqlmesh.core.engine_adapter.db2 import Db2EngineAdapter

DIALECT_TO_ENGINE_ADAPTER = {
"hive": SparkEngineAdapter,
"spark": SparkEngineAdapter,
Expand All @@ -41,6 +46,10 @@
"starrocks": StarRocksEngineAdapter,
}

# Add DB2 only on Python 3.10+
if sys.version_info >= (3, 10):
DIALECT_TO_ENGINE_ADAPTER["db2"] = Db2EngineAdapter

DIALECT_ALIASES = {
"postgresql": "postgres",
}
Expand Down
Loading