Skip to content

Support DuckDB Dialect#539

Open
hadia206 wants to merge 13 commits into
mainfrom
Hadia/duckdb
Open

Support DuckDB Dialect#539
hadia206 wants to merge 13 commits into
mainfrom
Hadia/duckdb

Conversation

@hadia206

@hadia206 hadia206 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Linked ticket

Closes #541

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Docs / config

What changed and why?

Adds full DuckDB SQL support to PyDough

  • a new DatabaseDialect, connection handling,
  • dialect-specific function bindings,
  • Generated SQL reference files covering the existing test suite,
  • TPC-H and DEFOG e2e pipeline tests. NOTE: using data from sqlite,
  • TPC-H demo notebook,
  • CI workflow

How I tested this?

  • Locally uv run pytest -svvW ignore tests/ -m "duckdb"
  • Using CI DuckDB Github action workflow link (note: 4 BodoSQL failures are known failures not related to this PR)

Notes for reviewers

  • TPC-H data is loaded from the shared tpch.db SQLite file (via DuckDB's SQLite scanner + explicit column casts), not from DuckDB's built-in dbgen. This is because the DuckDB's embedded dbgen generated different random-text fields (address, comment) than the standard TPC-H used everywhere else which resulted in different output in a lot of our tests.
  • For consistency and to avoid creating a new SQL init file (or transpile script) DEFOG data is similarly loaded from a SQLite defog.db built by setup_defog.sh

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

return expr

return expr.this if isinstance(expr.this, (exp.Count, exp.CountIf)) else expr
return expr.this if isinstance(expr.this, exp.Count) else expr

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including CountIf here created an issue for DuckDB when a LEFT JOIN produces all-NULL rows for a group, DuckDB’s COUNT_IF(NULL_condition) returns NULL (not 0). The COALESCE is needed here.

Tested with all dialects and they worked fine.

SELECT
COUNT(*) AS n_rows
FROM main.publication
FROM defog.academic.publication

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most defog Databricks updates are related to using correct databricks defog graph metadata. In earlier version, it was using the Sqlite metadata.

- name: Run DEFOG refresh if stale (locked by date)
run: uv run python -m tests.gen_data.databricks_task
- name: Run DEFOG refresh
run: uv run python -m tests.gen_data.databricks_task --force

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force regenerating with each run. This was mainly because I had to stop running the testing mid running and that made the data incomplete.

try:
self.cursor.execute(sql)

# This is only for MyPy to pass and know about fetch_pandas_all()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used type: ignore ... as I needed to used a different method for DuckDB so that made that TYPE_CHECKING not really needed now.

raise pydough.active_session.error_builder.sql_runtime_failure(
sql, e, True
) from e
finally:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't close cursor until session is done.

The old pattern created and closed a fresh cursor per method call (execute_query_df, execute_ddl, get_table_columns). DuckDB's cursor() returns an independent sub-connection, so closing between calls made TEMPORARY TABLEs and TEMPORARY VIEWs invisible to subsequent queries. The cursor is now created lazily on first access and reused for the lifetime of the connection.

)
return expr

def _remap_zero_to_one(self, idx: SQLGlotExpression) -> SQLGlotExpression:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put in base and re-use in the needed dialects (Databricks, Postgres, DuckDB)

@hadia206 hadia206 marked this pull request as ready for review June 30, 2026 22:29

@john-sanchez31 john-sanchez31 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a some of comments below but overall LGTM!

Comment thread demos/README.md
- [Databricks_TPCH.ipynb](notebooks/Databricks_TPCH.ipynb) demonstrates how to connect a Databricks database with PyDough.
- [Trino_TPCH.ipynb](notebooks/Trino_TPCH.ipynb) demonstrates how to connect a Trino database with PyDough.

- [DuckDB_TPCH.ipynb](notebooks/DuckDB_TPCH.ipynb) demonstrates how to connect a DuckDB database with PyDough.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't add databricks and duckdb to our main README file on the root folder on how to run the tests section. Let's add them

Comment thread documentation/usage.md

- `databricks`: uses a Databricks database. [See here](https://docs.databricks.com/dev-tools/python-sql-connector.html) for details on the connection API and what keyword arguments can be passed in.

- `duckdb`: uses a DuckDB database. [See here](https://duckdb.org/docs/api/python) for details on the connection API and what keyword arguments can be passed in.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets also add Databricks and DuckDB on documentation/functions.md to the list list of reference links for the format strings

}
]
}
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add new line here

Comment thread tests/conftest.py
columns arrive as VARCHAR, then explicitly CAST every column to its
correct DuckDB type. See ``_DUCKDB_TPCH_TABLE_SELECTS`` for the casts.
"""
import duckdb

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the import is here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed in the duckdb functions only to be able to export sqlite data into the duckdb memory. If outside, will give an error when trying to import with other tests.

Comment thread tests/test_pipeline_duckdb.py Outdated
sqlite_defog_connection: DatabaseContext,
) -> None:
"""
Test executing the defog analytical questions on the sqlite database,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be duckdb?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for DuckDB databases & dialect in PyDough

2 participants