Skip to content

fix(r): schema-qualify CTE table refs to avoid circular references - #285

Closed
cpsievert wants to merge 2 commits into
mainfrom
fix/tblsqlsource-cte-circular-ref
Closed

fix(r): schema-qualify CTE table refs to avoid circular references#285
cpsievert wants to merge 2 commits into
mainfrom
fix/tblsqlsource-cte-circular-ref

Conversation

@cpsievert

Copy link
Copy Markdown
Contributor

Problem

Four TblSqlSource tests fail with a "circular reference to CTE" error on DuckDB 1.3.0+ (and SQLite):

Error: Binder Error: Circular reference to CTE "test_table", There are two possible solutions. 
1. use WITH RECURSIVE to use recursive CTEs. 
2. If you want to use the TABLE name "test_table" the same as the CTE name, please explicitly add "SCHEMA" before table name. You can try "main.test_table"

This happens when a TblSqlSource is created from a transformed tbl_sql (e.g., dplyr::filter(), dplyr::select()). The class wraps the transformed tbl's SQL as a CTE named after the table:

WITH "test_table" AS (
  SELECT test_table.* FROM test_table WHERE ...
)
SELECT * FROM test_table

DuckDB 1.3.0+ and SQLite reject this because the CTE name ("test_table") shadows the physical table (test_table) referenced in the CTE body, creating a circular reference.

Fix

Schema-qualify the table references in the CTE body so they resolve to the physical table, not the CTE:

WITH "test_table" AS (
  SELECT test_table.* FROM main.test_table WHERE ...
)
SELECT * FROM test_table

Two helper functions are added to TblSqlSource.R:

  • get_current_schema(conn) — tries SELECT current_schema() (DuckDB, PostgreSQL) and falls back to "main" for databases that don't support it (SQLite).
  • qualify_cte_table_refs(conn, cte_body) — uses a regex to prefix FROM/JOIN table references with the schema name. Skips already-qualified names and subquery FROM clauses.

Verification

All 64 test-TblSqlSource.R tests pass (previously 4 failed). Full devtools::test() shows no new failures or regressions.

ellmer 0.5.0 moves model details from Provider into a new Model class:
Provider() no longer accepts model as its second positional argument,
and Chat$new() requires a separate model argument. Gate the mock chat
client construction on whether ellmer::Model exists so tests pass on
both old and new ellmer.

Fixes #283
DuckDB 1.3.0+ and SQLite reject a CTE whose name matches a table it
references (e.g., WITH t AS (SELECT ... FROM t) SELECT * FROM t),
raising a 'circular reference' error. This broke 4 TblSqlSource tests
that use transformed tbls (CTE mode).

The fix schema-qualifies table references in the CTE body (e.g.,
FROM main.test_table instead of FROM test_table), which disambiguates
the CTE name from the physical table. The schema name is obtained via
current_schema() with a fallback to 'main' for databases that don't
support it (e.g., SQLite).
@cpsievert
cpsievert requested a review from gadenbuie September 3, 2026 22:19
@cpsievert
cpsievert marked this pull request as draft September 3, 2026 22:20
@cpsievert
cpsievert removed the request for review from gadenbuie September 3, 2026 22:20
@cpsievert

Copy link
Copy Markdown
Contributor Author

Closing this PR — the 4 TblSqlSource test failures it addresses are local-only, caused by an outdated local DuckDB (1.3.0).

CI uses DuckDB 1.5.5, which already fixed this circular CTE reference bug upstream in DuckDB 1.5.0 (PR #19116), resolving issue #6389. The schema-qualification workaround in this PR adds complexity to work around a DuckDB bug that no longer exists in the versions CI and most users run.

If support for older DuckDB (< 1.5.0) becomes a requirement, this approach could be revisited. Thanks to @cpsievert for the investigation.

@cpsievert cpsievert closed this Sep 3, 2026
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.

1 participant