Repository navigation
Read and write negative intervals - #359
Conversation
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>
There was a problem hiding this comment.
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
Int128range checking. - Preserves negative
TimeSpanvalues 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 Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
Coverage Report for CI Build 37478637040Coverage increased (+0.03%) to 91.69%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats💛 - Coveralls |
|
Thanks, this looks good. I checked it on Windows as well, and against A few cases beyond the tests in the PR also behave correctly with the change:
Yes, please open a separate PR for the item under "Not in this PR": a |
DuckDB intervals can be negative:
duckdb_interval.microsisint64_t, anddayscan be negative too.DuckDBIntervalconverted both as unsigned, so today, ondevelop:TimeSpanthrows, evenINTERVAL '-2 days'(zero micros);INTERVAL '-1 month'reads as00:00:00: the negative month is silently dropped, while+1 monththrows;TimeSpanthrowsOverflowException("Value was either too large or too small for a UInt64").Change
ToTimeSpanreadsMicrosas its signed value, adds days and micros inInt128, and refuses a result outsideTimeSpan's range. A non-zero month count of either sign is refused instead of dropped.FromTimeSpankeeps the sign:Daysis the whole days of theTimeSpanandMicrosthe signed remainder, truncated toward zero.Microsstays aulongso the public API doesn't change; the value is its two's complement. (Alongwould 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 negativeTimeSpan→ interval. The existing tests are unchanged and still pass.Parameters/IntervalTests(new):SELECT INTERVAL '...'for negative and mixed-sign values throughGetValueandGetFieldValue<TimeSpan>; a negativeTimeSpanparameter inserted into anINTERVALcolumn and compared withCAST('n microseconds' AS INTERVAL); the appender writing a negativeTimeSpan;±1 monthrefused as aTimeSpan.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
TimeSpanparameter whose target type DuckDB can't infer (for exampleSELECT ?::INTERVAL) is sent asTimeSpan.ToString(), becauseTimeSpanhas noDbTypemapping. DuckDB parses-01:30:00, but not the1.01:01:01form .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