matrixorigin / matrixorigin/matrixone

[Bug]: unquoted DECIMAL256 literals lose fractional digits before evaluation

Open
#28,765 4 comments 0 reactions 1 assignee Claimed by @XuPeng-SH View on GitHub
kind/bug needs-triage
Dominant language
Go
Stars
1.9k
Forks
311
Avg merge
1d 3h
Merged PRs (30d)
768

Description

## Description

An unquoted decimal literal that requires the `DECIMAL256` domain silently loses fractional digits before evaluation. The loss starts at 39 significant digits: a literal with the same spelling as a stored `DECIMAL(39,1)`, `DECIMAL(40,2)`, or `DECIMAL(65,30)` value does not compare equal to it.

This is distinct from #26897. That issue fixed exact coercion for text string constants paired with DECIMAL. Here the failing operand is an unquoted numeric literal; the text-literal and explicit-DECIMAL controls are correct on the same revision.

## Environment

- Branch: `main`
- Commit: `4f9d170a22deaabc2eee6b239a51a8cb40a39f9f`
- Deployment: local one Log service, one TN service, two CN services, launched with `etc/launch-multi-cn/launch.toml`; direct MySQL-wire connection to CN on port 16001.

## Steps to reproduce

```sql
CREATE DATABASE decimal256_unquoted_literal;
USE decimal256_unquoted_literal;

CREATE TABLE t (id INT PRIMARY KEY, v DECIMAL(65,30));
INSERT INTO t VALUES
(1, 12345678901234567890123456789012345.123456789012345678901234567890);

SELECT 12345678901234567890123456789012345.123456789012345678901234567890;

SELECT id FROM t
WHERE v = 12345678901234567890123456789012345.123456789012345678901234567890;

SELECT id FROM t
WHERE v = CAST(
'12345678901234567890123456789012345.123456789012345678901234567890'
AS DECIMAL(65,30)
);
```

## Actual behavior

The scalar literal is returned as:

```text
12345678901234567890123456789012345.123
```

The equality predicate returns no rows. `EXPLAIN` records the already-truncated literal:

```text
Filter Cond: (t.v = cast(12345678901234567890123456789012345.123 AS DECIMAL256))
```

The same outcome occurs with exact direct literals at `DECIMAL(39,1)`, `DECIMAL(40,2)`, and `DECIMAL(65,30)`.

## Expected behavior

The unquoted literal must preserve its complete numeric value. It has the same value as the inserted `DECIMAL(65,30)` value, so the equality predicate must return `id = 1`. Direct scalar evaluation must retain all 30 fractional digits.

## Stability and controls

- The full boundary matrix reproduced identically in 3/3 independent databases on the stated revision.
- `DECIMAL(38,18)` is a passing control: its same-spelling unquoted literal returns `id = 1`.
- At `DECIMAL(39,1)`, `DECIMAL(40,2)`, and `DECIMAL(65,30)`, the same-spelling unquoted literal returns no row while the explicit `CAST(... AS DECIMAL(M,D))` control returns `id = 1`.
- A quoted literal containing the same 65-digit decimal spelling also returns `id = 1` for the `DECIMAL(65,30)` table.
- The statements are read-only after fixture insertion; rows remained unchanged after every repetition.

## Evidence

The saved local matrix is `evidence/types/decimal256_numeric_literal_boundary.sql`; its three current-main outputs are `decimal256_numeric_literal_boundary_main_4f9d170_run7.out` through `run9.out`. After replacing only the isolated database names, all outputs are byte-identical.

## Code analysis

The failing public path enters `baseBinder.bindNumVal` in `pkg/sql/plan/base_binder.go` for `tree.P_decimal`, then selects `makePlan2DecimalExprWithType` in `pkg/sql/plan/make.go` once `types.Parse128` cannot represent the value. `makePlan2DecimalExprWithType` constructs a `DECIMAL256` cast from the literal's original string, while direct `Parse256` and explicit-cast controls retain the full value. The exact point at which this bind/execution path reduces the effective scale is not yet confirmed; the `EXPLAIN` output proves the reduction happens before the predicate is executed.

## Regression coverage

Add a deterministic decimal planner/executor regression after the fix. It should assert scalar value and equality row set for 38-digit `DECIMAL128` and 39-, 40-, and 65-digit `DECIMAL256` unquoted literals, with explicit-CAST and text-literal controls.

## Related

- #26897: text string literal DECIMAL coercion, fixed separately
- #27088: closed tracking issue for earlier DECIMAL coercion and prepared-runtime manifestations

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.