Skip to content

Read and write negative intervals - #359

Merged
Giorgi merged 1 commit into
Giorgi:developfrom
pengdows:fix/negative-interval
Oct 6, 2026
Merged

Giorgi merged 1 commit into
Giorgi:developfrom
pengdows:fix/negative-interval

Conversation

@alaricd

@alaricd alaricd commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

DuckDB intervals can be negative: duckdb_interval.micros is int64_t, and days can be negative too. DuckDBInterval converted both as unsigned, so today, on develop:

cmd.CommandText = "SELECT INTERVAL '-90 minutes'";
reader.GetFieldValue<TimeSpan>(0); // ArgumentOutOfRangeException: "...total microseconds is larger than 9223372036854775807"
  • reading any negative interval as a TimeSpan throws, even INTERVAL '-2 days' (zero micros);
  • INTERVAL '-1 month' reads as 00:00:00: the negative month is silently dropped, while +1 month throws;
  • binding or appending a negative TimeSpan throws OverflowException ("Value was either too large or too small for a UInt64").

Change

  • ToTimeSpan reads Micros as its signed value, adds days and micros in Int128, and refuses a result outside TimeSpan's range. A non-zero month count of either sign is refused instead of dropped.
  • FromTimeSpan keeps the sign: Days is the whole days of the TimeSpan and Micros the signed remainder, truncated toward zero.
  • Micros stays a ulong so the public API doesn't change; the value is its two's complement. (A long would be the natural type for a future major version.)

Tests

  • DuckDBIntervalTests: negative micros, negative and mixed-sign days, a negative month, the range limit, and negative TimeSpan → interval. The existing tests are unchanged and still pass.
  • Parameters/IntervalTests (new): SELECT INTERVAL '...' for negative and mixed-sign values through GetValue and GetFieldValue<TimeSpan>; a negative TimeSpan parameter inserted into an INTERVAL column and compared with CAST('n microseconds' AS INTERVAL); the appender writing a negative TimeSpan; ±1 month refused as a TimeSpan.
  • The new tests fail on develop (17 of them) and pass with the change, 5/5 runs. Full suite: 7175 passed against DuckDB 1.5.6 (Linux x64, net8.0).

Not in this PR

A TimeSpan parameter whose target type DuckDB can't infer (for example SELECT ?::INTERVAL) is sent as TimeSpan.ToString(), because TimeSpan has no DbType mapping. DuckDB parses -01:30:00, but not the 1.01:01:01 form .NET uses once there are days, positive or negative. The new tests bind into a typed column to stay clear of it; happy to open a separate issue or PR.

🤖 Generated with Claude Code

DuckDB's interval micros is signed (duckdb_interval.micros is int64_t) and days can be negative, but
DuckDBInterval converted both as unsigned: reading any negative interval as a TimeSpan threw
ArgumentOutOfRangeException ("INTERVAL '-90 minutes'", even "-2 days"), a negative month was silently
dropped instead of refused, and binding or appending a negative TimeSpan threw OverflowException.

Micros stays a ulong for compatibility and is read and written as its two's complement. A non-zero
month count, either sign, is refused, and the range check is done in Int128.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The signed conversions are correct, range-safe, and comprehensively covered by focused tests.

Review effort: Balanced
Findings: None

What changed in this PR

Corrects interval conversion in the native bindings to support negative days and microseconds safely.

Changes:

  • Uses signed two’s-complement microseconds and Int128 range checking.
  • Preserves negative TimeSpan values during parameter and appender writes.
  • Adds unit and integration coverage for negative, mixed-sign, monthly, and out-of-range intervals.
File Description
DuckDB.NET.Bindings/​DuckDBInterval.cs Corrects signed interval conversion and range validation.
DuckDB.NET.Test/​DuckDBIntervalTests.cs Tests conversion boundaries and negative values.
DuckDB.NET.Test/​Parameters/​IntervalTests.cs Tests querying, binding, and appending negative intervals.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@codecov

codecov Bot commented Oct 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.94%. Comparing base (56b8941) to head (f9de56d).

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #359      +/-   ##
===========================================
+ Coverage    89.89%   89.94%   +0.05%     
===========================================
  Files           82       82              
  Lines         3583     3572      -11     
  Branches       557      553       -4     
===========================================
- Hits          3221     3213       -8     
+ Misses         231      230       -1     
+ Partials       131      129       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 37478637040

Coverage increased (+0.03%) to 91.69%

Details

  • Coverage increased (+0.03%) from the base build.
  • Patch coverage: 7 of 7 lines across 1 file are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 3572
Covered Lines: 3342
Line Coverage: 93.56%
Relevant Branches: 1855
Covered Branches: 1634
Branch Coverage: 88.09%
Branches in Coverage %: Yes
Coverage Strength: 419310.55 hits per line

💛 - Coveralls

@Giorgi
Giorgi merged commit 136c4eb into Giorgi:develop Oct 6, 2026
11 of 12 checks passed
@Giorgi

Giorgi commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Thanks, this looks good. I checked it on Windows as well, and against develop the old code is worse than the description says: INTERVAL '-1 year 2 days' reads as 2.00:00:00, with the year silently dropped.

A few cases beyond the tests in the PR also behave correctly with the change:

  • TimeSpan.MinValue and TimeSpan.MaxValue round-trip through an INTERVAL column (to the microsecond). On develop both fail, including the positive one.
  • INTERVAL '-1 day 25 hours' reads as 01:00:00.
  • TIMESTAMP '2020-01-01' - TIMESTAMP '2021-03-05' reads as -429.00:00:00.
  • Intervals outside TimeSpan's range throw ArgumentOutOfRangeException and still read as DuckDBInterval.

Yes, please open a separate PR for the item under "Not in this PR": a TimeSpan parameter with no target type being sent as TimeSpan.ToString().

This branch is waiting to be deployed

1 waiting deployment
external — f9de56d8 Waiting Oct 6, 2026 by alaricd via Authorize #614
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.

4 participants