Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
6 changes: 5 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,10 @@ 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
pytest -m "db2" -n 1 --reruns 0 -x -vv -o log_cli=true --log-cli-level=INFO

#################
# 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