matrixorigin / matrixorigin/matrixone
[Compatibility]: PERIOD_ADD and PERIOD_DIFF mishandle exact DECIMAL arguments
- Dominant language
- Go
- Stars
- 1.9k
- Forks
- 311
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 768
Description
## Environment
- MatrixOne: latest `main`, commit `f0c31cd4b830be32442cf329e0a3fb08aa9c16c3`
- MySQL comparison: 8.3.0
## Problem
`PERIOD_ADD` and `PERIOD_DIFF` truncate exact DECIMAL arguments instead of applying MySQL's exact numeric-to-integer rounding. Values at or above a `.5` boundary therefore select a different period or month count. SQL prepared statements take a third path and reject the same DECIMAL values outright.
## Reproduction
```sql
SELECT PERIOD_ADD(CAST(200801.5 AS DECIMAL(7,1)), 1);
SELECT PERIOD_ADD(200801, CAST(1.5 AS DECIMAL(3,1)));
SELECT PERIOD_ADD(200801, CAST(-1.5 AS DECIMAL(3,1)));
SELECT PERIOD_DIFF(CAST(200802.5 AS DECIMAL(7,1)), 200801);
SELECT PERIOD_DIFF(200802, CAST(200801.5 AS DECIMAL(7,1)));
```
MatrixOne returns:
```text
200802
200802
200712
1
1
```
MySQL 8.3 returns:
```text
200803
200803
200711
2
0
```
The difference comes from converting `200801.5` to period `200801` instead of `200802`, and month counts `1.5`/`-1.5` to `1`/`-1` instead of `2`/`-2`.
## Prepared-statement path
```sql
PREPARE p FROM 'SELECT PERIOD_ADD(?, ?)';
SET @period = CAST(200801.5 AS DECIMAL(7,1));
SET @months = CAST(1.5 AS DECIMAL(3,1));
EXECUTE p USING @period, @months;
```
MatrixOne rejects this with `invalid argument cast to int, bad value 200801.5`; MySQL returns `200804` after rounding both exact arguments. `PERIOD_DIFF` has the same prepared-parameter rejection.
## Coverage
All relevant paths were repeated three times:
- DECIMAL constants below, at, and above the `.5` boundary;
- positive and negative DECIMAL month counts;
- DECIMAL columns with valid YYMM/YYYYMM periods;
- SQL prepared statements;
- CTAS, which persists the incorrect `PERIOD_ADD` and `PERIOD_DIFF` results.
Integer and NULL controls behave normally, and `.4` controls match MySQL because truncation and rounding happen to choose the same integer. The server remains healthy after all runs.
## Code analysis
The function registrations in `pkg/sql/plan/function/list_builtIn.go` provide only signed integer, unsigned integer, and FLOAT64 overload combinations—there is no DECIMAL overload.
Both `PeriodAdd` and `PeriodDiff` in `pkg/sql/plan/function/func_binary.go` convert the floating execution path with direct truncation:
```go
return int64(val), null // Truncate decimal part
```
Prepared parameter resolution instead selects an integer cast and rejects the fractional DECIMAL text. A dedicated exact DECIMAL conversion path is needed to keep constant, column, and prepared behavior aligned.
Contributor guide
Assessment
This issue has not been assessed yet.