Skip to content

fix: handle zero-column DuckDB sources - #493

Merged
kevincostner17 merged 2 commits into
FreshCode-Org:mainfrom
alibro005:fix/duckdb-zero-column-source
Sep 20, 2026
Merged

kevincostner17 merged 2 commits into
FreshCode-Org:mainfrom
alibro005:fix/duckdb-zero-column-source

Conversation

@alibro005

@alibro005 alibro005 commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Description

Handle zero-column pandas DataFrames in the DuckDB backend without raising an error.

Fixes #457

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor / Style / Performance improvement

Tests Performed

  • pytest tests\test_execution\test_duckdb_engine.py --no-cov 12 passed
  • git diff --check passed
  • Added a regression test for zero-column pandas DataFrames
    • Input shape: (3, 0)
    • DuckDB output shape: (3, 0)
    • Confirms zero-column inputs are handled without raising duckdb.InvalidInputException

@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 4d8581e3-5e27-4ce1-9e96-57c805fbf666


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@JohnnyWilson16

Copy link
Copy Markdown
Contributor

LGTM ! Just need to sort out the failing CI. @kevincostner17 , could you review?

alibro005 and others added 2 commits September 20, 2026 23:59
…fallback

The zero-column short-circuit ran ahead of the backend's fallback chain, so it
bypassed both `pandas_ingest_fallback_reason` and `_pandas_index_forces_fallback`.
Three consequences:

- A zero-column frame with a non-RangeIndex worked before this change (the index
  guard routed it to pandas and the labels survived) and came back with a rebuilt
  RangeIndex after it, silently dropping the labels and breaking the parity the
  `native_config` fixture exists to enforce.
- `output_format="duckdb"` still raised, now a RuntimeError from
  `_require_recorded_fallback`, since a materialized frame may stand in for a
  native handle only after a fallback the report discloses.
- `rows_before=len(source)` is only meaningful for pandas: a zero-column
  LazyFrame raised TypeError and a parquet path would have passed its character
  count off as a row count.

A source DuckDB cannot register is an ingest limitation, so name it as one and
let the existing chain handle it. The pandas reference already keeps the rows and
the index on a zero-column frame, and the fallback is disclosed in the report and
still blocked by `fallback_policy="error"`.

Fixes FreshCode-Org#457

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kevincostner17
kevincostner17 force-pushed the fix/duckdb-zero-column-source branch from 3e2642b to c49cb46 Compare September 20, 2026 18:32
@kevincostner17

Copy link
Copy Markdown
Contributor

Thanks for this — the bug is real and your diagnosis of it was right. I've pushed a commit to your branch (and rebased onto current main, which had moved on ~115 commits) rather than send it back to you, since the change ended up being smaller than the original. Flagging what changed and why.

The short-circuit sat ahead of the backend's fallback chain, so it jumped over both pandas_ingest_fallback_reason and _pandas_index_forces_fallback. Three things fell out of that:

1. A zero-column frame with a non-RangeIndex regressed. This case wasn't broken before — the index guard already routed it to the pandas reference and the labels survived:

pd.DataFrame(index=["a","b","c"]), engine="duckdb" result
main (3,0), index ['a','b','c']
this PR as submitted (3,0), index [0,1,2]
engine="pandas" / "polars" reference (3,0), index ['a','b','c']

zero_column_frame rebuilds a RangeIndex, so the labels went away silently. That breaks the byte-for-byte agreement the native_config fixture exists to enforce. The suite didn't catch it because nothing covered a non-RangeIndex zero-column frame.

2. output_format="duckdb" still raised, just with a different exception — RuntimeError from _require_recorded_fallback, which permits a materialized frame in place of a native handle only after a fallback the report discloses. The short-circuit never recorded one.

3. rows_before=len(source) only holds for pandas. execute also takes parquet paths, Arrow tables and Polars frames — a zero-column LazyFrame raised TypeError: object of type 'LazyFrame' has no len(), and a str path would have passed its character count off as a row count.

The fix follows from (2): a source DuckDB cannot register is an ingest limitation, so it belongs in the reason chain rather than in front of it.

reason = plan.fallback_reason or pandas_ingest_fallback_reason(source, self.name)
if reason is None and not plan_cols:
    reason = "zero-column source"
if reason is None and self._pandas_index_forces_fallback(source):

Six lines with the comment, no new import, no CleanReport construction, no len(source). The pandas reference already keeps the rows and the index on a zero-column frame, so it just needs to be allowed to run — and the fallback is now disclosed in the report and still blocked by fallback_policy="error". It also fixes the LazyFrame case, which main got wrong too.

Your regression test is kept, with three added alongside it: the disclosed fallback, index preservation checked against engine="pandas", and the native-handle request.

Verified locally: tests/test_execution, test_backend_honesty, test_report, test_idempotency_modes, test_config_interactions and test_metamorphic all pass, and ruff check is clean (that was the red quality-fast — an I001 on the moved import). Merging once CI confirms.

@kevincostner17
kevincostner17 merged commit 313f586 into FreshCode-Org:main Sep 20, 2026
22 checks passed
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.

fd.clean(engine="duckdb") raises InvalidInputException on a zero-column frame

3 participants