Skip to content

Use FLOAT8_FITS_IN_INT64 in dsinterval_mul#1211

Draft
c2main wants to merge 4 commits intoIvorySQL:masterfrom
Data-Bene:upstream/fix-int64-min-max
Draft

Use FLOAT8_FITS_IN_INT64 in dsinterval_mul#1211
c2main wants to merge 4 commits intoIvorySQL:masterfrom
Data-Bene:upstream/fix-int64-min-max

Conversation

@c2main
Copy link
Copy Markdown
Contributor

@c2main c2main commented Mar 10, 2026

dsinterval_mul() compared a rounded double directly to PG_INT64_MAX, which triggers Clang because that integer constant is not exactly representable as double near 2^63.

Warning excerpt:

src/datatype/dsinterval.c:2336:22: warning:
implicit conversion from long to double changes value from 9223372036854775807 to 9223372036854775808
[-Wimplicit-const-int-float-conversion]

Follow the same integer-fit pattern used in timestamp.c by checking isnan() and FLOAT8_FITS_IN_INT64() after rint(). This avoids custom bound constants and keeps overflow checks consistent with core code.

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened interval multiplication/division validation to catch NaN and out-of-range results, preventing invalid interval values.
  • Tests

    • Added/updated interval arithmetic tests covering very large/small operands and NaN to verify overflow/error handling.
  • Style / Refactor

    • Minor formatting and parsing refinements in interval handling code with no public API changes.

dsinterval_mul() compared a rounded double directly to PG_INT64_MAX,
which triggers Clang because that integer constant is not exactly
representable as double near 2^63.

Warning excerpt:

```
src/datatype/dsinterval.c:2336:22: warning:
implicit conversion from long to double changes value from 9223372036854775807 to 9223372036854775808
[-Wimplicit-const-int-float-conversion]
```

Follow the same integer-fit pattern used in timestamp.c by checking
isnan() and FLOAT8_FITS_IN_INT64() after rint(). This avoids custom
bound constants and keeps overflow checks consistent with core code.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Replaced direct integer-range checks in dsinterval multiplication/division with NaN checks and FLOAT8_FITS_IN_INT32/INT64 guards; added tests for overflow and NaN cases; applied formatting/comment tweaks in yminterval including commenting out the year+month aggregation. No exported signatures changed.

Changes

Cohort / File(s) Summary
Interval Datatype Logic
contrib/ivorysql_ora/src/datatype/dsinterval.c
Replaced direct range comparisons with NaN checks and FLOAT8_FITS_IN_INT32/FLOAT8_FITS_IN_INT64 guards in dsinterval_mul/dsinterval_div; introduced result_double as intermediate validation; preserved cascade logic; minor formatting/comment edits.
Year‑Month Interval Parsing
contrib/ivorysql_ora/src/datatype/yminterval.c
Mostly whitespace and formatting changes; in DecodeYminterval the previous aggregation val = val * MONTHS_PER_YEAR + val2 for DTK_DATE is commented out, removing that aggregation from active parsing.
Datetime SQL Tests
contrib/ivorysql_ora/sql/ora_datetime.sql
Added overflow/NaN test cases for interval arithmetic (year-to-month and day-to-second multiplication/division with huge/tiny/NaN operands).
Expected Test Output
contrib/ivorysql_ora/expected/ora_datetime.out
Updated expected outputs to include ERROR: interval out of range for the newly added overflow and NaN test cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibbled through the interval grass today,
Caught NaN and bounds before they leapt away,
Floats tuck months and seconds in tidy rows,
Tests thunder when a wild overflow grows,
A rabbit hops on—safe math here to stay.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main functional change in the pull request: replacing direct bounds checks with FLOAT8_FITS_IN_INT64 macros in dsinterval_mul and related functions.
Docstring Coverage ✅ Passed Docstring coverage is 82.22% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
contrib/ivorysql_ora/src/datatype/dsinterval.c (1)

2414-2419: Consider adding the same overflow check for consistency with dsinterval_mul.

The dsinterval_div function has similar code that assigns a rint() result to result->time without checking for NaN or int64 overflow. Division by a very small factor could produce values exceeding int64 range.

♻️ Suggested change for consistency
 	/* cascade units down */
 	result->day += (int32) month_remainder_days;
 `#ifdef` HAVE_INT64_TIMESTAMP
-	result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+	result_double = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+	if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+				 errmsg("interval out of range")));
+	result->time = (int64) result_double;
 `#else`

This would require declaring result_double at the top of dsinterval_div.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contrib/ivorysql_ora/src/datatype/dsinterval.c` around lines 2414 - 2419,
dsinterval_div currently assigns rint(span->time / factor + sec_remainder *
USECS_PER_SEC) directly to result->time under HAVE_INT64_TIMESTAMP without
checking for NaN or int64 overflow; mirror the dsinterval_mul pattern by
declaring a double (e.g., result_double) at the top of dsinterval_div, compute
result_double = span->time / factor + sec_remainder * USECS_PER_SEC, then
validate with isfinite(result_double) and that result_double is within
PG_INT64_MIN..PG_INT64_MAX before assigning result->time = rint(result_double);
if the value is non-finite or out of range, clamp result->time to PG_INT64_MIN
or PG_INT64_MAX (matching dsinterval_mul behavior) to avoid undefined overflow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@contrib/ivorysql_ora/src/datatype/dsinterval.c`:
- Around line 2414-2419: dsinterval_div currently assigns rint(span->time /
factor + sec_remainder * USECS_PER_SEC) directly to result->time under
HAVE_INT64_TIMESTAMP without checking for NaN or int64 overflow; mirror the
dsinterval_mul pattern by declaring a double (e.g., result_double) at the top of
dsinterval_div, compute result_double = span->time / factor + sec_remainder *
USECS_PER_SEC, then validate with isfinite(result_double) and that result_double
is within PG_INT64_MIN..PG_INT64_MAX before assigning result->time =
rint(result_double); if the value is non-finite or out of range, clamp
result->time to PG_INT64_MIN or PG_INT64_MAX (matching dsinterval_mul behavior)
to avoid undefined overflow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 91ae9c9a-d22f-4112-81b5-561637abd5ef

📥 Commits

Reviewing files that changed from the base of the PR and between 0da228b and 3abcbab.

📒 Files selected for processing (1)
  • contrib/ivorysql_ora/src/datatype/dsinterval.c

dsinterval_div() in HAVE_INT64_TIMESTAMP mode assigned a rounded
double directly to result->time without validating NaN/range. That can
produce undefined overflow behavior on extreme inputs.

Mirror dsinterval_mul() by validating the rounded value with
FLOAT8_FITS_IN_INT64() before casting, and raise interval out of range
when it is not representable.

Add regression cases in ora_datetime for both overflow directions:
interval * huge_number and interval / tiny_number, each expecting
"interval out of range".
@c2main c2main force-pushed the upstream/fix-int64-min-max branch from 3abcbab to 9cca17c Compare March 10, 2026 16:26
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
contrib/ivorysql_ora/src/datatype/dsinterval.c (1)

2273-2274: Apply the same NaN-safety improvements to yminterval_mul, mul_d_yminterval, and yminterval_div.

Both yminterval_mul and mul_d_yminterval use the bounds-check pattern if (result_double > INT_MAX || result_double < INT_MIN) without NaN validation, which is vulnerable to the same overflow bypass issue. Additionally, yminterval_div performs a direct cast (int32) (span->month / factor) without any overflow or NaN protection.

For consistency with dsinterval_mul and to maintain robustness, update these functions to check isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double) before casting to int32.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contrib/ivorysql_ora/src/datatype/dsinterval.c` around lines 2273 - 2274,
Replace the existing fragile bounds checks in yminterval_mul, mul_d_yminterval,
and yminterval_div with the same NaN-safe pattern used in dsinterval_mul:
compute result_double, then validate with isnan(result_double) ||
!FLOAT8_FITS_IN_INT32(result_double) before casting to int32 (or returning); in
particular, change any checks like (result_double > INT_MAX || result_double <
INT_MIN) to the combined isnan + FLOAT8_FITS_IN_INT32 check, and for
yminterval_div add this validation before the direct cast (int32)(span->month /
factor).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@contrib/ivorysql_ora/src/datatype/dsinterval.c`:
- Around line 2273-2274: Replace the existing fragile bounds checks in
yminterval_mul, mul_d_yminterval, and yminterval_div with the same NaN-safe
pattern used in dsinterval_mul: compute result_double, then validate with
isnan(result_double) || !FLOAT8_FITS_IN_INT32(result_double) before casting to
int32 (or returning); in particular, change any checks like (result_double >
INT_MAX || result_double < INT_MIN) to the combined isnan + FLOAT8_FITS_IN_INT32
check, and for yminterval_div add this validation before the direct cast
(int32)(span->month / factor).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4f2e1ac3-e266-4a35-bbda-8056f769cd1c

📥 Commits

Reviewing files that changed from the base of the PR and between 3abcbab and 9cca17c.

📒 Files selected for processing (3)
  • contrib/ivorysql_ora/expected/ora_datetime.out
  • contrib/ivorysql_ora/sql/ora_datetime.sql
  • contrib/ivorysql_ora/src/datatype/dsinterval.c

c2main added 2 commits March 10, 2026 16:51
yminterval_mul() and mul_d_yminterval() still used manual INT_MAX/
INT_MIN checks on double intermediates, and yminterval_div() cast the
division result directly to int32 without representability validation.

Switch all three paths to the same NaN-safe pattern used in interval
arithmetic: reject isnan() and !FLOAT8_FITS_IN_INT32() before casting.

Add regression cases in ora_datetime for year-to-month interval
overflow on both multiply (huge factor) and divide (tiny factor), each
expecting "interval out of range".
dsinterval_mul() still validated month/day intermediates with manual
INT_MAX/INT_MIN comparisons. That style does not reject NaN reliably
and diverges from the FLOAT8_FITS_IN_INT32 pattern now used in related
interval arithmetic paths.

Switch month/day checks to isnan() || !FLOAT8_FITS_IN_INT32() before
casting to int32.

Add an ora_datetime regression case for interval day-to-second
multiplication by NaN and expect "interval out of range".
@c2main c2main force-pushed the upstream/fix-int64-min-max branch from 9cca17c to 971560b Compare March 10, 2026 17:19
@c2main
Copy link
Copy Markdown
Contributor Author

c2main commented Mar 10, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 10, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
contrib/ivorysql_ora/src/datatype/dsinterval.c (2)

2390-2428: ⚠️ Potential issue | 🔴 Critical

Add NaN and range checks for month/day divisions in dsinterval_div() before the (int32) casts.

Lines 2404–2405 cast division results directly to int32 without first checking for NaN or range overflow. This leaves the function vulnerable to undefined behavior when dividing by NaN or values that produce out-of-range quotients. The newly added result_double and FLOAT8_FITS_IN_INT64() guard on line 2425 protects only result->time, not month or day.

Apply the same guarding pattern already used in yminterval_div(): compute the division result into a double, check with isnan() and FLOAT8_FITS_IN_INT32(), then cast to int32.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contrib/ivorysql_ora/src/datatype/dsinterval.c` around lines 2390 - 2428, In
dsinterval_div(), avoid casting span->month/factor and span->day/factor directly
to int32; instead compute each division into a double (reuse result_double),
check isnan() and FLOAT8_FITS_IN_INT32() for the month and day results (like the
existing check used for result->time), then cast to (int32) only after the
checks; update the assignments that set result->month and result->day to perform
this safe double->int32 guarded conversion and use the same error handling
(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE) if checks fail.

2341-2346: ⚠️ Potential issue | 🟠 Major

Missing int32 overflow check for result->day after time-to-day carry normalization.

The overflow validation at lines 2343-2346 and 2423-2428 only ensures result->time fits in int64. It does not validate that the subsequent carry operation at lines 2356 and 2439—result->day += result->time / USECS_PER_DAY—keeps result->day within int32 bounds. For example, interval '21 23:59:59.999999' day to second * 99000000 passes the int64 check but the day carry causes result->day to exceed INT32_MAX.

This also applies to dsinterval_div at lines 2423-2428 and 2439.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contrib/ivorysql_ora/src/datatype/dsinterval.c` around lines 2341 - 2346,
After normalizing time to microseconds and performing the carry into days (the
operation result->day += result->time / USECS_PER_DAY in functions handling
multiplication and division—e.g., dsinterval_mul and dsinterval_div), add an
explicit int32 bounds check for result->day against INT32_MIN and INT32_MAX and
ereport(ERROR, errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval
out of range")) if the carry makes result->day overflow; keep the existing int64
check for result->time but validate result->day immediately after the carry to
prevent examples like large multipliers causing day overflow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contrib/ivorysql_ora/sql/ora_datetime.sql`:
- Around line 570-592: Add a missing division-by-NaN test for the day-to-second
interval: insert the SQL line "interval '365 11:11:11' day(3) to second /
'NaN'::number" alongside the other interval division/multiplication cases so the
dsinterval_div() NaN path is covered, and add the corresponding "ERROR: 
interval out of range" entry to the expected output file for ora_datetime tests
to match the new failing case.

---

Outside diff comments:
In `@contrib/ivorysql_ora/src/datatype/dsinterval.c`:
- Around line 2390-2428: In dsinterval_div(), avoid casting span->month/factor
and span->day/factor directly to int32; instead compute each division into a
double (reuse result_double), check isnan() and FLOAT8_FITS_IN_INT32() for the
month and day results (like the existing check used for result->time), then cast
to (int32) only after the checks; update the assignments that set result->month
and result->day to perform this safe double->int32 guarded conversion and use
the same error handling (ERRCODE_DATETIME_VALUE_OUT_OF_RANGE) if checks fail.
- Around line 2341-2346: After normalizing time to microseconds and performing
the carry into days (the operation result->day += result->time / USECS_PER_DAY
in functions handling multiplication and division—e.g., dsinterval_mul and
dsinterval_div), add an explicit int32 bounds check for result->day against
INT32_MIN and INT32_MAX and ereport(ERROR,
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))
if the carry makes result->day overflow; keep the existing int64 check for
result->time but validate result->day immediately after the carry to prevent
examples like large multipliers causing day overflow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7dda8807-d344-4cf6-9a4f-b6219585abcb

📥 Commits

Reviewing files that changed from the base of the PR and between 9cca17c and 971560b.

📒 Files selected for processing (4)
  • contrib/ivorysql_ora/expected/ora_datetime.out
  • contrib/ivorysql_ora/sql/ora_datetime.sql
  • contrib/ivorysql_ora/src/datatype/dsinterval.c
  • contrib/ivorysql_ora/src/datatype/yminterval.c

Comment on lines +570 to +592
-- interval year to month * huge number (overflow)
SELECT interval'10-11' year to month * 100000000000000000000;

-- interval day to second * number
SELECT interval'365 11:11:11' day(3) to second * 12.34;

-- interval day to second * huge number (overflow)
SELECT interval'365 11:11:11' day(3) to second * 100000000000000000000;

-- interval day to second * NaN number (overflow)
SELECT interval'365 11:11:11' day(3) to second * 'NaN'::number;

-- interval year to month / number
SELECT interval'10-11' year to month / 12.34;

-- interval year to month / tiny number (overflow)
SELECT interval'10-11' year to month / 0.00000000000000000001;

-- interval day to second / number
SELECT interval'365 11:11:11' day(3) to second / 12.34;

-- interval day to second / tiny number (overflow)
SELECT interval'365 11:11:11' day(3) to second / 0.00000000000000000001;
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.

⚠️ Potential issue | 🟡 Minor

Add the missing division-by-NaN regression.

This block covers NaN only on multiplication, even though dsinterval_div() is also touched in this PR. Please add interval '365 11:11:11' day(3) to second / 'NaN'::number here and the matching ERROR: interval out of range entry in contrib/ivorysql_ora/expected/ora_datetime.out so the remaining NaN path stays covered.

As per coding guidelines, **/sql/*.sql: Test SQL files. Ensure comprehensive coverage of features.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contrib/ivorysql_ora/sql/ora_datetime.sql` around lines 570 - 592, Add a
missing division-by-NaN test for the day-to-second interval: insert the SQL line
"interval '365 11:11:11' day(3) to second / 'NaN'::number" alongside the other
interval division/multiplication cases so the dsinterval_div() NaN path is
covered, and add the corresponding "ERROR:  interval out of range" entry to the
expected output file for ora_datetime tests to match the new failing case.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contrib/ivorysql_ora/sql/ora_datetime.sql`:
- Around line 579-592: Add the three missing NaN test cases that exercise the
isnan() branches in the interval arithmetic functions: add SELECT interval
'10-11' year to month * 'NaN'::number to hit yminterval_mul, SELECT interval
'10-11' year to month / 'NaN'::number to hit yminterval_div, and SELECT interval
'365 11:11:11' day(3) to second / 'NaN'::number to hit dsinterval_div; place
them alongside the existing interval tests so yminterval_mul, yminterval_div,
dsinterval_mul, and dsinterval_div all have NaN coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b17f001-173f-460f-8492-db6dfff810dd

📥 Commits

Reviewing files that changed from the base of the PR and between 0da228b and 971560b.

📒 Files selected for processing (4)
  • contrib/ivorysql_ora/expected/ora_datetime.out
  • contrib/ivorysql_ora/sql/ora_datetime.sql
  • contrib/ivorysql_ora/src/datatype/dsinterval.c
  • contrib/ivorysql_ora/src/datatype/yminterval.c

Comment on lines +579 to +592
-- interval day to second * NaN number (overflow)
SELECT interval'365 11:11:11' day(3) to second * 'NaN'::number;

-- interval year to month / number
SELECT interval'10-11' year to month / 12.34;

-- interval year to month / tiny number (overflow)
SELECT interval'10-11' year to month / 0.00000000000000000001;

-- interval day to second / number
SELECT interval'365 11:11:11' day(3) to second / 12.34;

-- interval day to second / tiny number (overflow)
SELECT interval'365 11:11:11' day(3) to second / 0.00000000000000000001;
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.

⚠️ Potential issue | 🟡 Minor

Add missing NaN tests for complete coverage.

The tests cover NaN multiplication for day-to-second intervals but are missing:

  1. interval '10-11' year to month * 'NaN'::number - tests yminterval_mul NaN path
  2. interval '10-11' year to month / 'NaN'::number - tests yminterval_div NaN path
  3. interval '365 11:11:11' day(3) to second / 'NaN'::number - tests dsinterval_div NaN path (also noted in a prior review)

Since all four arithmetic functions (yminterval_mul, yminterval_div, dsinterval_mul, dsinterval_div) now include isnan() checks, they should all have corresponding test coverage.

As per coding guidelines, **/sql/*.sql: Test SQL files. Ensure comprehensive coverage of features.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contrib/ivorysql_ora/sql/ora_datetime.sql` around lines 579 - 592, Add the
three missing NaN test cases that exercise the isnan() branches in the interval
arithmetic functions: add SELECT interval '10-11' year to month * 'NaN'::number
to hit yminterval_mul, SELECT interval '10-11' year to month / 'NaN'::number to
hit yminterval_div, and SELECT interval '365 11:11:11' day(3) to second /
'NaN'::number to hit dsinterval_div; place them alongside the existing interval
tests so yminterval_mul, yminterval_div, dsinterval_mul, and dsinterval_div all
have NaN coverage.

@c2main c2main marked this pull request as draft March 10, 2026 20:04
@OreoYang
Copy link
Copy Markdown
Collaborator

@jiaoshuntian follow up this PR

@jiaoshuntian
Copy link
Copy Markdown
Collaborator

in dsinterval_div():
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
Should the same checks be implemented?

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.

3 participants