matrixorigin / matrixorigin/matrixone

[Compatibility]: PERIOD_ADD and PERIOD_DIFF mishandle exact DECIMAL arguments

Open
#28,496 0 comments 0 reactions 1 assignee Claimed by @ck89119 View on GitHub
area/compatibility kind/bug severity/s0
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.