Skip to content
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
f1b5f89
fix: reimplement MySQL type compatibility for 3.0-dev
ck89119 Apr 26, 2026
d603b97
fix: address mysql compatibility review gaps
ck89119 Apr 26, 2026
c8efdb5
fix: address decimal256 CI failures
ck89119 Apr 27, 2026
cf8d4a9
fix: complete mysql compatibility review fixes
ck89119 Apr 27, 2026
72fd9c6
fix: address round-2 review gaps for MySQL type compatibility
ck89119 Apr 27, 2026
96d5c50
fix: address round-3 review gaps for MySQL type compatibility
ck89119 Apr 27, 2026
0dc82b2
fix: align array-scalar float overflow error type with array-array path
ck89119 Apr 27, 2026
49725ce
fix: update BVT result files for float array overflow and decimal sca…
ck89119 Apr 27, 2026
ee5780f
fix: update BVT result files for decimal scale limit and decimal256 f…
ck89119 Apr 27, 2026
903fba0
fix: report user-visible column name in unique-index duplicate entry …
ck89119 Apr 27, 2026
ba3d947
fix: propagate parent unique column name to fuzzy filter operator
ck89119 Apr 27, 2026
f28859a
fix: rewrite __mo_index_idx_col to user-visible name in duplicate ent…
ck89119 Apr 27, 2026
d29cb1f
fix: write back SET-aware expression in default / on update
ck89119 Apr 28, 2026
c3d5f86
fix: split fuzzyCheck attr into SQL-side attr and display-side displa…
ck89119 Apr 28, 2026
e24d7b0
fix: restore BVT placeholders for issue#8516/8513 decimal oracles
ck89119 Apr 28, 2026
420d4b6
fix: tighten Decimal64 scientific notation to match Decimal128/256
ck89119 Apr 28, 2026
cbccfe4
fix: parse numeric SET fallback as base-10 instead of Go base-0
ck89119 Apr 28, 2026
d375ab0
fix: reject signed and 3-digit YEAR literals in ParseMoYear
ck89119 Apr 28, 2026
6101024
fix: skip duplicate-entry rewrite when target table has multiple uniq…
ck89119 Apr 28, 2026
b58c803
fix: resolve SET default / on-update before the generic cast wrapper
ck89119 Apr 28, 2026
2d24983
fix: round float values when casting to YEAR before range check
ck89119 Apr 28, 2026
3f3af7d
fix: only NUL-trim BINARY fixed-length payload when casting to YEAR
ck89119 Apr 28, 2026
9107676
fix: guard scientific-notation exponent against int32 overflow
ck89119 Apr 28, 2026
4432c30
fix: hook duplicate-entry rewrite at the statement boundary, not just…
ck89119 Apr 28, 2026
2a11f0f
Merge remote-tracking branch 'mo/3.0-dev' into issue-24184-v2
ck89119 Apr 28, 2026
b73c18c
fix: update Optimistic BVT results to expect user-visible dup key names
ck89119 Apr 28, 2026
f4fbb01
test: expand UT coverage for SET, YEAR, Decimal, and float overflow h…
ck89119 Apr 28, 2026
33b9326
test: broaden UT coverage on touched files for the 75% coverage gate
ck89119 Apr 28, 2026
f25fdff
Merge remote-tracking branch 'mo/3.0-dev' into issue-24184-v2
ck89119 Apr 28, 2026
e93f647
Merge remote-tracking branch 'mo/3.0-dev' into issue-24184-v2
ck89119 Apr 28, 2026
cc77b0a
Merge remote-tracking branch 'mo/3.0-dev' into issue-24184-v2
ck89119 Apr 29, 2026
772d149
fix: surface composite hidden unique index name in duplicate-entry er…
ck89119 Apr 29, 2026
31a93fc
fix: preserve SET member case and verbatim non-literal default in SHO…
ck89119 Apr 29, 2026
642126d
fix: parse decimal-to-float once at target precision; expand decimal2…
ck89119 Apr 29, 2026
4daf5c6
fix: don't retry malformed decimal literals against Decimal256
ck89119 Apr 29, 2026
2745fd4
docs: annotate review-flagged compromises with follow-up notes
ck89119 Apr 29, 2026
1958330
Merge remote-tracking branch 'mo/3.0-dev' into issue-24184-v2
ck89119 Apr 29, 2026
111f7c4
fix: restore double-parse in decimal-to-float to preserve float(W,S) …
ck89119 Apr 29, 2026
124e6b3
fix: cover composite hidden UNIQUE dup-entry in RewriteHiddenIndexDup…
ck89119 Apr 30, 2026
98b1ab4
fix: reject finite-to-Inf silent overflow in moarray.Cast
ck89119 Apr 30, 2026
789a395
fix: split formatStr in SHOW CREATE; preserve DEFAULT exprs and escap…
ck89119 Apr 30, 2026
176b57f
fix: tighten float cast range/NaN checks for float->float, ->integer,…
ck89119 Apr 30, 2026
78e6a05
fix: hide internal hidden-index column name in fuzzyCheck onlyInsertH…
ck89119 Apr 30, 2026
fc44dcf
fix: escape single quotes in SHOW CREATE EXTERNAL TABLE string literals
ck89119 Apr 30, 2026
a53289a
fix: stop double-escaping column/table comments in SHOW CREATE
ck89119 Apr 30, 2026
59f2533
test: lift PR-diff coverage past the 75% gate
ck89119 Apr 30, 2026
75ed20f
fix: close float64 imprecision hole near MaxInt64/MaxUint64 in cast
ck89119 Apr 30, 2026
fcf2ac9
fix: hide internal hidden-index column name in constructFuzzyFilter f…
ck89119 Apr 30, 2026
3f567a8
fix: escape backslashes in SHOW CREATE string literals
ck89119 Apr 30, 2026
9b63779
fix: close 6 remaining correctness holes
ck89119 May 6, 2026
ece3a52
fix: close 4 remaining correctness holes (round 2)
ck89119 May 6, 2026
9735723
fix: unsized ARRAY cast and isSameColumnType equality (round 3)
ck89119 May 6, 2026
8967769
test: update BVT result for comment backslash escaping
ck89119 May 6, 2026
c545837
fix: escape DDL verbatim when wrapping in double-quoted SQL literal
ck89119 May 7, 2026
d4d4eb1
Merge remote-tracking branch 'mo/3.0-dev' into issue-24184-v2
ck89119 May 7, 2026
b31a2a5
fix: close 5 remaining correctness holes (round 4)
ck89119 May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
347 changes: 326 additions & 21 deletions pkg/container/types/decimal.go

Large diffs are not rendered by default.

214 changes: 214 additions & 0 deletions pkg/container/types/decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package types
import (
"fmt"
"math/rand"
"strings"
"testing"
)

Expand Down Expand Up @@ -69,12 +70,225 @@ func TestParse64ScientificNotation(t *testing.T) {
}
}

// TestParse128LeadingWhitespaceAndSigns verifies Parse128/Parse256 accept the
// leading whitespace / `+` / `-` branches.
func TestParse128LeadingWhitespaceAndSigns(t *testing.T) {
cases := []struct {
in string
want string
}{
{" 42", "42"},
{"+42", "42"},
{"-42", "-42"},
{" -3.14", "-3.14"},
{" +3.14e2", "314.00"},
}
for _, c := range cases {
v128, s128, err := Parse128(c.in)
if err != nil {
t.Errorf("Parse128(%q) err=%v", c.in, err)
continue
}
got128 := v128.Format(s128)
if got128 != c.want && got128 != strings.TrimSuffix(c.want, ".00") {
t.Errorf("Parse128(%q) got %q want %q", c.in, got128, c.want)
}

v256, s256, err := Parse256(c.in)
if err != nil {
t.Errorf("Parse256(%q) err=%v", c.in, err)
continue
}
got256 := v256.Format(s256)
if got256 != c.want && got256 != strings.TrimSuffix(c.want, ".00") {
t.Errorf("Parse256(%q) got %q want %q", c.in, got256, c.want)
}
}
}

// TestParse128HexLiteral exercises the hex-literal branch (0x...) in Parse128
// and Parse256.
func TestParse128HexLiteral(t *testing.T) {
if v, _, err := Parse128("0x1a"); err != nil || v.B0_63 != 26 {
t.Errorf("Parse128(0x1a) v=%v err=%v", v, err)
}
if v, _, err := Parse256("0xff"); err != nil || v.B0_63 != 255 {
t.Errorf("Parse256(0xff) v=%v err=%v", v, err)
}
// Invalid hex digit is rejected.
if _, _, err := Parse128("0xzz"); err == nil {
t.Errorf("Parse128(0xzz) should reject")
}
}

func TestParseDecimalsAcceptCommonForms(t *testing.T) {
positives := []string{
"42",
"-42",
"3.14",
"-3.14",
"1.23e4",
"1.23e+4",
"1.23e-4",
"1.23E4",
"1.23E+4",
"1.23E-4",
"+1.23",
"0",
"-0",
"0.0",
"0x1a", // hex for Parse128/256 but not Parse64
}
for _, s := range positives {
_, _, _ = Parse64(s)
_, _, _ = Parse128(s)
_, _, _ = Parse256(s)
}

// Confirm a couple of edge-case rejections match expectations (must be
// rejected by all three widths).
rejects := []string{
"", // empty
"abc", // non-numeric
"12..3", // double dot
"1.2.3", // double dot
}
for _, s := range rejects {
if _, _, err := Parse64(s); err == nil {
t.Errorf("Parse64(%q) should reject", s)
}
if _, _, err := Parse128(s); err == nil {
t.Errorf("Parse128(%q) should reject", s)
}
if _, _, err := Parse256(s); err == nil {
t.Errorf("Parse256(%q) should reject", s)
}
}
}

// TestParseDecimalScalePropagation exercises the scale-subtraction overflow
// guard by combining a positive-scale digit stream with a huge exponent so
// that the final scale calculation would wrap int32.
func TestParseDecimalScalePropagation(t *testing.T) {
// Positive exponent at the int32 boundary: scalecount accumulates past
// MaxInt32/10, tripping the guard.
boundary := "0.1e2147483600"
if _, _, err := Parse64(boundary); err == nil {
t.Errorf("Parse64(%q) expected out-of-range error", boundary)
}
if _, _, err := Parse128(boundary); err == nil {
t.Errorf("Parse128(%q) expected out-of-range error", boundary)
}
if _, _, err := Parse256(boundary); err == nil {
t.Errorf("Parse256(%q) expected out-of-range error", boundary)
}
}

// TestParseDecimalRejectsOverflowingExponent verifies scientific-notation
// exponents that would overflow int32 during parsing are rejected as
// out-of-range instead of silently wrapping.
func TestParseDecimalRejectsOverflowingExponent(t *testing.T) {
// >10 digit exponents easily exceed int32 capacity.
bigExp := "1e99999999999"
if _, _, err := Parse64(bigExp); err == nil {
t.Errorf("Parse64(%q) expected an error for huge exponent", bigExp)
}
if _, _, err := Parse128(bigExp); err == nil {
t.Errorf("Parse128(%q) expected an error for huge exponent", bigExp)
}
if _, _, err := Parse256(bigExp); err == nil {
t.Errorf("Parse256(%q) expected an error for huge exponent", bigExp)
}
}

// TestParse64RejectsMalformedScientific verifies Decimal64 mirrors
// Decimal128/256's strict exponent validation: exactly one `e`/`E`, at most
// one sign right after it, and no trailing non-digit characters.
func TestParse64RejectsMalformedScientific(t *testing.T) {
cases := []string{
"1e", // nothing after e
"1e+", // sign after e but no digit
"1e-", // sign after e but no digit
"1E", // capital E variant
"1e+-5", // double sign
"1e++5", // double sign
"1e1e2", // two exponents
"1ea", // letter after e
}
for _, s := range cases {
_, _, err := Parse64(s)
if err == nil {
t.Errorf("Parse64(%q) expected an error, got nil", s)
}
}
}

func TestParse128(t *testing.T) {
x, y := ParseDecimal128("99999.999999999999999999999999999999999", 12, 6)
if y != nil || x.B0_63 != 100000000000 {
panic("Decimal128Parse wrong")
}
}

func TestParse128ScientificNotationWithPlusSign(t *testing.T) {
x, err := ParseDecimal128("1.23456789e+06", 20, 2)
if err != nil {
t.Fatalf("Failed to parse Decimal128 scientific notation with plus sign: %v", err)
}
expected := Decimal128{B0_63: 123456789, B64_127: 0}
if x != expected {
t.Fatalf("ParseDecimal128('1.23456789e+06', 20, 2) = %v, expected %v", x, expected)
}
}

func TestParse128RejectsMisplacedPlusSign(t *testing.T) {
if _, err := ParseDecimal128("1+2", 20, 0); err == nil {
t.Fatalf("ParseDecimal128 accepted misplaced plus sign")
}
if _, err := ParseDecimal128("1e++2", 20, 0); err == nil {
t.Fatalf("ParseDecimal128 accepted duplicate exponent plus sign")
}
}

func TestParseDecimal256(t *testing.T) {
x, err := ParseDecimal256("123456789012345678901234567890.123456789012345678901234567890", 65, 30)
if err != nil {
t.Fatalf("Failed to parse Decimal256: %v", err)
}
if got := x.Format(30); got != "123456789012345678901234567890.123456789012345678901234567890" {
t.Fatalf("Decimal256 Format(30) = %s", got)
}
}

func TestParseDecimal256ScientificNotationWithPlusSign(t *testing.T) {
x, err := ParseDecimal256("1.23456789e+06", 65, 2)
if err != nil {
t.Fatalf("Failed to parse Decimal256 scientific notation with plus sign: %v", err)
}
if got := x.Format(2); got != "1234567.89" {
t.Fatalf("Decimal256 scientific notation Format(2) = %s", got)
}
}

func TestParseDecimal256RejectsMisplacedPlusSign(t *testing.T) {
if _, err := ParseDecimal256("1+2", 65, 0); err == nil {
t.Fatalf("ParseDecimal256 accepted misplaced plus sign")
}
if _, err := ParseDecimal256("1e++2", 65, 0); err == nil {
t.Fatalf("ParseDecimal256 accepted duplicate exponent plus sign")
}
}

func TestParseDecimal256FromByte(t *testing.T) {
x, err := ParseDecimal256FromByte("\x01\x00", 65, 0)
if err != nil {
t.Fatalf("Failed to parse Decimal256 from bytes: %v", err)
}
if got := x.Format(0); got != "256" {
t.Fatalf("Decimal256 from bytes Format(0) = %s", got)
}
}

func TestCompare64(t *testing.T) {
x := Decimal64(0)
y := ^x
Expand Down
Loading
Loading