Skip to content

fix: surface special-format conversion errors instead of swallowing them (#3988)#3989

Merged
jqnatividad merged 2 commits into
masterfrom
fix/3988-surface-special-format-conversion-errors
Jun 13, 2026
Merged

fix: surface special-format conversion errors instead of swallowing them (#3988)#3989
jqnatividad merged 2 commits into
masterfrom
fix/3988-surface-special-format-conversion-errors

Conversation

@jqnatividad

Copy link
Copy Markdown
Collaborator

Summary

Closes part of #3988. Config::resolve_converted previously swallowed polars-native special-format conversion failures (Avro, Parquet, Arrow IPC, JSON, JSONL, compressed CSV/TSV/SSV) and fell back to reading the original — often binary — bytes as delimited text. The command then emitted garbage with a success exit code. Only .zip surfaced the error.

For a data-wrangling tool, silently producing corrupt output that looks successful is the worst failure mode — it can flow downstream into joins, DB loads, etc. unnoticed. This PR makes a detected special format that fails to convert a hard error for all formats, consistent with the existing .zip behavior. The single escape hatch remains QSV_SKIP_FORMAT_CHECK, which opts into reading the raw bytes as-is.

Behavior change

⚠️ User-visible: an unconvertible special-format input now errors (non-zero exit) instead of emitting garbage. It only triggers where conversion was already failing (the user was already getting garbage), so this turns a silent corruption into a clear error. Documented in CHANGELOG.md under [Unreleased].

Masked failures this surfaced (and fixed)

These tests only ever passed because the asserted substring is physically embedded in the raw file bytes — so the substring check passed whether or not conversion actually worked:

  • slice_from_avro — the NYC311-5.avro fixture is unreadable by current polars (avro-error: OutOfSpec). Regenerated it from the canonical NYC311-5.csv via the same polars AvroWriter (qsv sqlp ... --format avro), verified it slices to the exact expected row, and strengthened the assertion to a full-row match.
  • slice_from_jsonl — strengthened to a full-object comparison, parsed as JSON (order-independent, since polars does not preserve JSONL column order).
  • slice_from_jsonl_with_decimal_precision{,_vs_float} — these used an invalid pschema shape (missing the metadata field) and targeted a capability polars doesn't have: its JSONL reader cannot apply a Decimal type (it refuses to coerce JSON numbers or strings into a Decimal column). Reworked both onto the compressed-CSV reader — the path that genuinely applies a Decimal pschema and preserves 25-digit precision vs f64's ~17 — and renamed to slice_from_csvgz_with_decimal_precision{,_vs_float}.

No #[ignore]s are introduced — all affected tests pass for the right reason.

Verification

  • test_slice: 140 passed, 0 ignored.
  • Special-format-input modules (count/json/profile/snappy/sniff/sqlp/stats/to/jsonl/tojsonl): 1005 passed, 0 failed.
  • cargo clippy -F all_features clean; cargo +nightly fmt applied.

🤖 Generated with Claude Code

…hem (#3988)

`Config::resolve_converted` previously swallowed polars-native special-format
conversion failures (Avro, Parquet, Arrow IPC, JSON, JSONL, compressed
CSV/TSV/SSV) and fell back to reading the original — often binary — bytes as
delimited text. That emitted garbage with a *success* exit code; only `.zip`
surfaced the error.

A detected special format that fails to convert is now a hard error for ALL
formats. The single escape hatch remains `QSV_SKIP_FORMAT_CHECK`, which opts
into reading the raw bytes as-is.

This surfaced pre-existing masked failures whose tests only passed because the
asserted substring is embedded in the raw bytes:

- `slice_from_avro`: the `NYC311-5.avro` fixture was unreadable by current
  polars (`OutOfSpec`). Regenerated it from the canonical CSV via the same
  polars AvroWriter; re-enabled the test with a full-row assertion.
- `slice_from_jsonl`: strengthened to a full-object comparison (parsed as JSON,
  order-independent since polars does not preserve JSONL column order).
- The two `slice` Decimal-pschema tests used an invalid pschema shape AND a
  format polars cannot apply a Decimal type to (JSONL). Reworked them onto the
  compressed-CSV reader — the path that genuinely applies a Decimal pschema and
  preserves 25-digit precision — and renamed to `slice_from_csvgz_*`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codacy-production

codacy-production Bot commented Jun 13, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copilot AI 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.

Pull request overview

This PR makes special-format input conversion failures (e.g., Avro/Parquet/Arrow IPC/JSON/JSONL and compressed delimited inputs) fail fast instead of silently falling back to reading raw bytes as delimited text, preventing “successful” commands from emitting corrupt/garbage output. It also strengthens slice tests that previously passed for the wrong reason, and documents the behavior change.

Changes:

  • Make Config::resolve_converted return a hard error on special-format conversion failure unless QSV_SKIP_FORMAT_CHECK is set.
  • Strengthen slice integration tests to validate full converted rows / decimal precision behavior rather than substring checks.
  • Add an [Unreleased] changelog entry describing the user-visible behavior change and the test fixture adjustments.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/config.rs Surfaces special-format conversion failures as InvalidInput errors unless explicitly opted out via QSV_SKIP_FORMAT_CHECK.
tests/test_slice.rs Updates several slice tests to assert real conversion results (full-row / decimal precision) instead of substring checks that could pass on raw bytes.
CHANGELOG.md Documents the behavior change under [Unreleased].
Comments suppressed due to low confidence (2)

tests/test_slice.rs:1325

  • This block runs slice twice (assert_success then stdout), but Workdir::stdout does not assert exit success. Capture Output once and reuse its stdout to avoid masking failures.
    wrk.assert_success(&mut cmd);

    // Get the output with Decimal schema
    let decimal_output: String = wrk.stdout(&mut cmd);

tests/test_slice.rs:1344

  • Same issue as above: assert_success runs the command once and stdout runs it again without checking the status. Use a single Output to ensure this assertion can’t pass if the command exits non-zero.
    wrk.assert_success(&mut cmd);

    // Get the output with Float64 schema
    let float_output: String = wrk.stdout(&mut cmd);

Comment thread tests/test_slice.rs
Comment thread tests/test_slice.rs Outdated
Comment thread tests/test_slice.rs Outdated
…-independent JSON compare

Copilot flagged that `assert_success` followed by `stdout` runs the command
twice and `Workdir::stdout` does not assert exit status. Added a
`Workdir::stdout_on_success` helper (mirroring the existing
`stderr_on_success`) that captures a single `Output`, asserts success, and
returns its stdout — so the asserted run is the same run whose output is
checked. Switched the four slice special-format tests to it.

Left the `slice_from_jsonl` comparison as a parsed `serde_json::Value`
equality: with the `preserve_order` feature, serde_json's `Map` is
IndexMap-backed and IndexMap's `PartialEq` is order-independent (same length +
all key/value pairs match, no order check — verified against indexmap docs), so
the assertion is already order-independent despite polars not preserving JSONL
column order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jqnatividad jqnatividad merged commit a329862 into master Jun 13, 2026
16 of 17 checks passed
@jqnatividad jqnatividad deleted the fix/3988-surface-special-format-conversion-errors branch June 13, 2026 21:21
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.

2 participants