A dbt adapter for Denodo Platform 8 and 9 (Virtual DataPort).
dbt-denodo lets dbt Core connect directly to Denodo Virtual DataPort (VDP) and manage
derived views and materialized tables with dbt's modeling, testing, and documentation workflow.
Built and maintained by Hassan Abid as a personal open-source project for the dbt and Denodo community.
Status: alpha. The adapter follows the current dbt adapter architecture (decoupled
dbt-adaptersinterface, post-dbt-Core-1.8). It has a full unit test suite and has been verified working against a live Denodo 9 server; running the functional suite requires a live VDP server. See Assumptions to verify against a live server.
- Python >= 3.10 (tested on 3.14)
- dbt Core >= 1.11 (the adapter depends only on the decoupled
dbt-adapters/dbt-commoninterfaces and is forward-compatible with future dbt Core 1.x releases, including 1.12 when it ships — as of July 2026 the latest dbt Core is 1.11.x) - Denodo Platform 8 or 9 with the VDP server's ODBC (PostgreSQL-compatible) interface enabled (port 9996 by default)
- For
table,incremental, andseedmaterializations: the VDP cache engine must be configured (materialized tables store their data in the cache data source)
The adapter has no version-specific code: it only requires the PostgreSQL-compatible
interface accepting VQL, the GET_VIEWS() / GET_VIEW_COLUMNS() stored procedures,
CREATE OR REPLACE VIEW / MATERIALIZED TABLE, and (for materialized models) the
cache engine.
| Denodo version | Status |
|---|---|
| Platform 9 / 9.x | ✅ Supported — verified against a live server |
| Platform 8.0 | ✅ Supported — original design target |
| Platform 7.0 | |
| 6.0 and earlier | ❌ Not supported |
Denodo's officially supported way to reach VDP from Python is the
PostgreSQL-compatible interface on port 9996 via psycopg2 — the same driver and
interface used by Denodo's official
SQLAlchemy dialect
for Denodo 8 and 9. This interface accepts VQL, so the adapter issues native VQL statements.
The official Denodo JDBC driver is a Java artifact and cannot be loaded natively by CPython (it would require a JVM bridge such as JPype/jaydebeapi, which Denodo does not recommend for Python). Denodo's Arrow Flight SQL interface would be preferable for throughput but only exists in Denodo 9.1+; this adapter sticks to the pg-wire interface so a single code path covers both Platform 8 and 9.
dbt-core 1.11.x pins mashumaro<3.15, and mashumaro only gained Python 3.14
compatibility in 3.17 (older versions fail to import on 3.14 due to PEP 649 lazy
annotations). Until dbt-core relaxes its pin, running on Python 3.14 requires:
pip install "mashumaro[msgpack]==3.17" # after installing dbt-coremashumaro 3.17 satisfies dbt-adapters' own pin (<3.18) and passes this adapter's
full test suite. On Python 3.10–3.13 no override is needed.
pip install dbt-denodo # once published; from source: pip install .Denodo has a two-level namespace: a virtual database containing views. There is no
separate catalog/schema split, so — like dbt-spark — the dbt schema maps to the Denodo
virtual database, and database must be omitted (or equal to schema).
my_project:
target: dev
outputs:
dev:
type: denodo
host: vdp.example.com
port: 9996 # VDP ODBC (pg-wire) port
user: dbt_user
password: "{{ env_var('DENODO_PASSWORD') }}"
schema: analytics # the Denodo virtual database
threads: 4
# optional:
sslmode: prefer # any libpq sslmode value
connect_timeout: 10
retries: 1| Feature | Support | Implementation |
|---|---|---|
view models |
✅ | CREATE OR REPLACE VIEW (VQL derived view) |
table models |
✅ (needs cache engine) | CREATE OR REPLACE MATERIALIZED TABLE ... AS SELECT |
incremental models |
✅ append, delete+insert |
staging derived view + INSERT / DELETE on the materialized table |
ephemeral models |
✅ | CTEs |
| Seeds | ✅ (needs cache engine) | materialized table + row-by-row INSERT |
| Tests (generic + singular) | ✅ | plain SELECT |
Docs (dbt docs generate) |
✅ | GET_VIEWS() / GET_VIEW_COLUMNS() |
| Snapshots | ❌ | VQL has no MERGE and no RENAME |
Grants (grants: config) |
❌ | VDP privileges are administered separately; fails loudly |
persist_docs |
❌ | fails loudly if enabled |
| Constraints | ❌ | reported as not supported to dbt |
incremental_predicates |
❌ | fails loudly if configured |
- No RENAME in VQL. Denodo cannot rename views or tables, so this adapter's
materializations use
CREATE OR REPLACEinstead of dbt's default create → swap → drop pattern.rename_relationraises if anything calls it. - Transactions. VQL DDL is not transactional; connections run in autocommit and dbt's BEGIN/COMMIT hooks are no-ops.
- Schemas are virtual databases.
create_schemaissuesCREATE DATABASE(administrator privilege required). Most production setups should pre-create the virtual database and let dbt build inside it; customgenerate_schema_namelogic that fans out across many schemas will require databases to exist or admin rights. CREATE MATERIALIZED TABLE ... AS SELECTruns the query on the server and, per Denodo documentation, keeps the database in single-user mode while it runs. Prefer views for heavy models, or schedule table builds off-hours.- Folders. Set
folder: '/dbt'in a model config to place created elements in a VDP folder (FOLDER = '...'modifier).
- Denodo is a data virtualization layer:
viewmodels add zero storage cost, whiletable/incremental/seedmodels write into the cache data source. delete+insertincremental strategy requires a single-columnunique_key.truncateis implemented asDELETE FROM(VQL has noTRUNCATE).- Cross-database references (
databasedifferent from the profileschema) are rejected.
These follow documented Denodo behavior and have been exercised successfully against a live Denodo 9 server; end-to-end verification specifically on a Platform 8 instance is still welcome. Each is isolated in one macro for easy patching:
GET_VIEW_COLUMNS()output field names (column_name,column_vdp_type,column_size,column_decimals) — used bydenodo__get_columns_in_relation(macros/adapters.sql) and macros/catalog.sql.GET_VIEWS()fields (name,database_name,view_type) and theview_typecodes (0 base / 1 derived / 2 interface / 3 materialized / 4 summary) are documented.LIST DATABASESthrough the pg-wire interface returns one row per database (denodo__list_schemas). If unavailable for non-admin users, replace with aGET_ELEMENTS()-based query.INSERT INTO <materialized table> SELECT ... FROM <view>— used by the incremental materialization.FOLDER = '<path>'modifier position inCREATE OR REPLACE VIEW/CREATE OR REPLACE MATERIALIZED TABLE(only emitted when the optionalfolderconfig is set).- Qualified names (
db.view) in DDL statements. With the default single-database setup the qualified name always matches the connected database, which is safe.
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/unit # no server needed
cp test.env.example test.env # then edit
pytest tests/functional # needs a live Denodo VDP server (8 or 9)Contributions are welcome — see CONTRIBUTING.md for the development setup, test instructions, and PR guidelines. Bug reports and feature requests go through GitHub issues; security reports follow SECURITY.md.
If you have access to a live Denodo VDP server, running the functional suite and reporting results against the assumptions listed above is one of the most valuable contributions you can make right now.