Skip to content

Commit fb75d24

Browse files
authored
truncation refactoring and partial decimal truncation implementation (#3230)
1 parent c1e36e7 commit fb75d24

37 files changed

+1323
-431
lines changed

enginetest/enginetests.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3961,9 +3961,9 @@ func TestWindowRangeFrames(t *testing.T, harness Harness) {
39613961
TestQueryWithContext(t, ctx, e, harness, `SELECT sum(y) over (partition by z order by date range between unbounded preceding and interval '1' DAY following) FROM c order by x`, []sql.Row{{float64(1)}, {float64(1)}, {float64(1)}, {float64(1)}, {float64(5)}, {float64(5)}, {float64(10)}, {float64(10)}, {float64(10)}, {float64(10)}}, nil, nil, nil)
39623962
TestQueryWithContext(t, ctx, e, harness, `SELECT count(y) over (partition by z order by date range between interval '1' DAY following and interval '2' DAY following) FROM c order by x`, []sql.Row{{1}, {1}, {1}, {1}, {1}, {0}, {2}, {2}, {0}, {0}}, nil, nil, nil)
39633963
TestQueryWithContext(t, ctx, e, harness, `SELECT count(y) over (partition by z order by date range between interval '1' DAY preceding and interval '2' DAY following) FROM c order by x`, []sql.Row{{4}, {4}, {4}, {5}, {2}, {2}, {4}, {4}, {4}, {4}}, nil, nil, nil)
3964+
TestQueryWithContext(t, ctx, e, harness, "SELECT sum(y) over (partition by z order by date range interval 'e' DAY preceding) FROM c order by x", []sql.Row{{float64(0)}, {float64(0)}, {float64(0)}, {float64(1)}, {float64(1)}, {float64(3)}, {float64(1)}, {float64(1)}, {float64(4)}, {float64(4)}}, nil, nil, nil)
39643965

39653966
AssertErr(t, e, harness, "SELECT sum(y) over (partition by z range between unbounded preceding and interval '1' DAY following) FROM c order by x", nil, aggregation.ErrRangeInvalidOrderBy)
3966-
AssertErr(t, e, harness, "SELECT sum(y) over (partition by z order by date range interval 'e' DAY preceding) FROM c order by x", nil, sql.ErrInvalidValue)
39673967
}
39683968

39693969
func TestNamedWindows(t *testing.T, harness Harness) {

enginetest/evaluation.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,18 @@ func TestScriptWithEngine(t *testing.T, e QueryEngine, harness Harness, script q
129129
} else if assertion.ExpectedErrStr != "" {
130130
AssertErrWithCtx(t, e, harness, ctx, assertion.Query, assertion.Bindings, nil, assertion.ExpectedErrStr)
131131
} else if assertion.ExpectedWarning != 0 {
132-
AssertWarningAndTestQuery(t, e, nil, harness, assertion.Query,
133-
assertion.Expected, nil, assertion.ExpectedWarning, assertion.ExpectedWarningsCount,
134-
assertion.ExpectedWarningMessageSubstring, assertion.SkipResultsCheck)
132+
if IsServerEngine(e) && assertion.SkipResultCheckOnServerEngine {
133+
t.Skip()
134+
}
135+
AssertWarningAndTestQuery(t, e, nil, harness,
136+
assertion.Query,
137+
assertion.Expected,
138+
nil,
139+
assertion.ExpectedWarning,
140+
assertion.ExpectedWarningsCount,
141+
assertion.ExpectedWarningMessageSubstring,
142+
assertion.SkipResultsCheck,
143+
)
135144
} else if assertion.SkipResultsCheck {
136145
RunQueryWithContext(t, e, harness, nil, assertion.Query)
137146
} else if assertion.CheckIndexedAccess {

enginetest/queries/function_queries.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,7 @@ var FunctionQueryTests = []QueryTest{
10521052
{
10531053
Query: `SELECT FLOOR(15728640/1024/1030)`,
10541054
Expected: []sql.Row{
1055-
{"14"},
1055+
{14},
10561056
},
10571057
},
10581058
{

enginetest/queries/json_table_queries.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ var JSONTableScriptTests = []ScriptTest{
571571
},
572572
{
573573
Query: "SELECT * FROM JSON_TABLE('{\"c1\":\"abc\"}', '$' COLUMNS(c1 INT PATH '$.c1' DEFAULT 'def' ON ERROR)) as jt;",
574-
ExpectedErrStr: "error: 'def' is not a valid value for 'int'",
574+
ExpectedErrStr: "Invalid JSON text in argument 1 to function JSON_TABLE: \"Invalid value.\"",
575575
},
576576
},
577577
},
@@ -612,7 +612,7 @@ var JSONTableScriptTests = []ScriptTest{
612612
},
613613
{
614614
Query: "SELECT * FROM JSON_TABLE('{\"c1\":\"abc\"}', '$' COLUMNS(c1 INT PATH '$.c1' ERROR ON ERROR)) as jt;",
615-
ExpectedErrStr: "error: 'abc' is not a valid value for 'int'",
615+
ExpectedErrStr: "Invalid JSON text in argument 1 to function JSON_TABLE: \"Invalid value.\"",
616616
},
617617
},
618618
},

enginetest/queries/queries.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5509,11 +5509,11 @@ SELECT * FROM cte WHERE d = 2;`,
55095509
},
55105510
{
55115511
Query: "select ceil(i + 0.5) from mytable order by 1",
5512-
Expected: []sql.Row{{"2"}, {"3"}, {"4"}},
5512+
Expected: []sql.Row{{2}, {3}, {4}},
55135513
},
55145514
{
55155515
Query: "select floor(i + 0.5) from mytable order by 1",
5516-
Expected: []sql.Row{{"1"}, {"2"}, {"3"}},
5516+
Expected: []sql.Row{{1}, {2}, {3}},
55175517
},
55185518
{
55195519
Query: "select round(i + 0.55, 1) from mytable order by 1",

enginetest/queries/script_queries.go

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -501,12 +501,12 @@ FROM task_instance INNER JOIN job ON job.id = task_instance.queued_by_job_id INN
501501
},
502502
{
503503
// TODO: 123.456 is converted to a DECIMAL by Builder.ConvertVal, when it should be a DOUBLE
504-
Skip: true,
504+
SkipResultCheckOnServerEngine: true, // TODO: warnings do not make it to server engine
505505
Query: "SELECT '123.456ABC' = 123.456;",
506506
Expected: []sql.Row{{true}},
507507
ExpectedWarningsCount: 1,
508508
ExpectedWarning: mysql.ERTruncatedWrongValue,
509-
ExpectedWarningMessageSubstring: "Truncated incorrect double value: 123A",
509+
ExpectedWarningMessageSubstring: "Truncated incorrect decimal(65,30) value: 123.456ABC",
510510
},
511511
{
512512
Query: "SELECT '123.456e2' = 12345.6;",
@@ -528,20 +528,18 @@ FROM task_instance INNER JOIN job ON job.id = task_instance.queued_by_job_id INN
528528
},
529529
{
530530
// Valid float strings used as arguments to functions are truncated not rounded
531-
Skip: true,
532531
Query: "SELECT LENGTH(SPACE('1.9'));",
533532
Expected: []sql.Row{{1}},
534-
ExpectedWarningsCount: 2, // MySQL throws two warnings for some reason
533+
ExpectedWarningsCount: 1, // TODO: MySQL throws two warnings for some reason
535534
ExpectedWarning: mysql.ERTruncatedWrongValue,
536535
},
537536
{
538537
// TODO: 123.456 is converted to a DECIMAL by Builder.ConvertVal, when it should be a DOUBLE
539-
Skip: true,
540538
Query: "SELECT -'+123.456ABC' = -123.456",
541539
Expected: []sql.Row{{true}},
542540
ExpectedWarningsCount: 1,
543541
ExpectedWarning: mysql.ERTruncatedWrongValue,
544-
ExpectedWarningMessageSubstring: "Truncated incorrect double value: +123.456ABC",
542+
ExpectedWarningMessageSubstring: "Truncated incorrect decimal(65,30) value: +123.456ABC",
545543
},
546544
{
547545
Query: "SELECT '0xBEEF' = 0;",
@@ -608,12 +606,12 @@ FROM task_instance INNER JOIN job ON job.id = task_instance.queued_by_job_id INN
608606
},
609607
{
610608
// TODO: 123.456 is converted to a DECIMAL by Builder.ConvertVal, when it should be a DOUBLE
611-
Skip: true,
609+
SkipResultCheckOnServerEngine: true, // TODO: warnings do not make it to server engine
612610
Query: "SELECT '123.456ABC' in (123.456);",
613611
Expected: []sql.Row{{true}},
614612
ExpectedWarningsCount: 1,
615613
ExpectedWarning: mysql.ERTruncatedWrongValue,
616-
ExpectedWarningMessageSubstring: "Truncated incorrect double value: 123A",
614+
ExpectedWarningMessageSubstring: "Truncated incorrect decimal(65,30) value: 123.456ABC",
617615
},
618616
{
619617
Query: "SELECT '123.456e2' in (12345.6);",
@@ -975,13 +973,13 @@ FROM task_instance INNER JOIN job ON job.id = task_instance.queued_by_job_id INN
975973
Dialect: "mysql",
976974
Name: "strings cast to numbers",
977975
SetUpScript: []string{
978-
"create table test01(pk varchar(20) primary key)",
976+
"create table test01(pk varchar(20) primary key);",
979977
`insert into test01 values (' 3 12 4'),
980978
(' 3.2 12 4'),('-3.1234'),('-3.1a'),('-5+8'),('+3.1234'),
981979
('11d'),('11wha?'),('11'),('12'),('1a1'),('a1a1'),('11-5'),
982-
('3. 12 4'),('5.932887e+07'),('5.932887e+07abc'),('5.932887e7'),('5.932887e7abc')`,
983-
"create table test02(pk int primary key)",
984-
"insert into test02 values(11),(12),(13),(14),(15)",
980+
('3. 12 4'),('5.932887e+07'),('5.932887e+07abc'),('5.932887e7'),('5.932887e7abc');`,
981+
"create table test02(pk int primary key);",
982+
"insert into test02 values(11),(12),(13),(14),(15);",
985983
},
986984
Assertions: []ScriptTestAssertion{
987985
{
@@ -1082,10 +1080,8 @@ FROM task_instance INNER JOIN job ON job.id = task_instance.queued_by_job_id INN
10821080
{"5.932887e7abc", uint64(5)},
10831081
{"a1a1", uint64(0)},
10841082
},
1085-
// TODO: Should be 19. Missing warnings for "Cast to unsigned converted negative integer to its positive
1086-
// complement" (1105) https://github.com/dolthub/dolt/issues/9840
1087-
ExpectedWarningsCount: 16,
1088-
ExpectedWarning: mysql.ERTruncatedWrongValue,
1083+
ExpectedWarningsCount: 19,
1084+
// Can't check multiple different warnings
10891085
},
10901086
{
10911087
Query: "select pk, cast(pk as decimal(12,3)) from test01",
@@ -1119,7 +1115,6 @@ FROM task_instance INNER JOIN job ON job.id = task_instance.queued_by_job_id INN
11191115
ExpectedWarningsCount: 0,
11201116
},
11211117

1122-
// TODO: these are not directly testing casting
11231118
{
11241119
// https://github.com/dolthub/dolt/issues/9739
11251120
Skip: true,
@@ -1163,19 +1158,15 @@ FROM task_instance INNER JOIN job ON job.id = task_instance.queued_by_job_id INN
11631158
ExpectedWarning: mysql.ERTruncatedWrongValue,
11641159
},
11651160
{
1166-
// https://github.com/dolthub/dolt/issues/9739
1167-
Skip: true,
11681161
Dialect: "mysql",
1169-
Query: "select * from test02 where pk in ('11asdf')",
1170-
Expected: []sql.Row{{"11"}},
1162+
Query: "select * from test02 where pk in ('11asdf');",
1163+
Expected: []sql.Row{{11}},
11711164
ExpectedWarningsCount: 1,
11721165
ExpectedWarning: mysql.ERTruncatedWrongValue,
11731166
},
11741167
{
1175-
// https://github.com/dolthub/dolt/issues/9739
1176-
Skip: true,
11771168
Dialect: "mysql",
1178-
Query: "select * from test02 where pk='11.12asdf'",
1169+
Query: "select * from test02 where pk='11.12asdf';",
11791170
Expected: []sql.Row{},
11801171
ExpectedWarningsCount: 1,
11811172
ExpectedWarning: mysql.ERTruncatedWrongValue,

server/handler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1572,7 +1572,7 @@ func TestStatusVariableMaxUsedConnections(t *testing.T) {
15721572
}
15731573

15741574
checkGlobalStatVar(t, "Max_used_connections", uint64(0))
1575-
checkGlobalStatVar(t, "Max_used_connections_time", "")
1575+
checkGlobalStatVar(t, "Max_used_connections_time", uint64(0))
15761576

15771577
conn1 := newConn(1)
15781578
handler.NewConnection(conn1)

sql/columndefault.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,15 @@ func (e *ColumnDefaultValue) Eval(ctx *Context, r Row) (interface{}, error) {
8282

8383
if e.OutType != nil {
8484
var inRange ConvertInRange
85-
if val, inRange, err = e.OutType.Convert(ctx, val); err != nil {
85+
if roundType, isRoundType := e.OutType.(RoundingNumberType); isRoundType {
86+
val, inRange, err = roundType.ConvertRound(ctx, val)
87+
} else {
88+
val, inRange, err = e.OutType.Convert(ctx, val)
89+
}
90+
if err != nil {
8691
return nil, ErrIncompatibleDefaultType.New()
87-
} else if !inRange {
92+
}
93+
if !inRange {
8894
return nil, ErrValueOutOfRange.New(val, e.OutType)
8995
}
9096
}
@@ -227,13 +233,18 @@ func (e *ColumnDefaultValue) CheckType(ctx *Context) error {
227233
if val == nil && !e.ReturnNil {
228234
return ErrIncompatibleDefaultType.New()
229235
}
230-
_, inRange, err := e.OutType.Convert(ctx, val)
236+
var inRange ConvertInRange
237+
if roundType, isRoundType := e.OutType.(RoundingNumberType); isRoundType {
238+
val, inRange, err = roundType.ConvertRound(ctx, val)
239+
} else {
240+
val, inRange, err = e.OutType.Convert(ctx, val)
241+
}
231242
if err != nil {
232243
return ErrIncompatibleDefaultType.Wrap(err)
233-
} else if !inRange {
244+
}
245+
if !inRange {
234246
return ErrIncompatibleDefaultType.Wrap(ErrValueOutOfRange.New(val, e.Expr))
235247
}
236-
237248
}
238249
return nil
239250
}

sql/core.go

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,8 @@ import (
2424
"strings"
2525
"sync/atomic"
2626
"time"
27-
"unicode"
2827
"unsafe"
2928

30-
"github.com/dolthub/vitess/go/mysql"
3129
"github.com/shopspring/decimal"
3230
"gopkg.in/src-d/go-errors.v1"
3331

@@ -340,48 +338,6 @@ const (
340338
NumericCutSet = " \t\n\r"
341339
)
342340

343-
// TODO: type processing logic should all be in the types package
344-
func TrimStringToNumberPrefix(ctx *Context, s string, isInt bool) string {
345-
if isInt {
346-
s = strings.TrimLeft(s, IntCutSet)
347-
} else {
348-
s = strings.TrimLeft(s, NumericCutSet)
349-
}
350-
351-
seenDigit := false
352-
seenDot := false
353-
seenExp := false
354-
signIndex := 0
355-
356-
for i := 0; i < len(s); i++ {
357-
char := rune(s[i])
358-
if unicode.IsDigit(char) {
359-
seenDigit = true
360-
} else if char == '.' && !seenDot && !isInt {
361-
seenDot = true
362-
} else if (char == 'e' || char == 'E') && !seenExp && seenDigit && !isInt {
363-
seenExp = true
364-
signIndex = i + 1
365-
} else if !((char == '-' || char == '+') && i == signIndex) {
366-
// TODO: this should not happen here, and it should use sql.ErrIncorrectTruncation
367-
if isInt {
368-
ctx.Warn(mysql.ERTruncatedWrongValue, "Truncated incorrect INTEGER value: '%s'", s)
369-
} else {
370-
ctx.Warn(mysql.ERTruncatedWrongValue, "Truncated incorrect DOUBLE value: '%s'", s)
371-
}
372-
return convertEmptyStringToZero(s[:i])
373-
}
374-
}
375-
return convertEmptyStringToZero(s)
376-
}
377-
378-
func convertEmptyStringToZero(s string) string {
379-
if s == "" {
380-
return "0"
381-
}
382-
return s
383-
}
384-
385341
var ErrVectorInvalidBinaryLength = errors.NewKind("cannot convert BINARY(%d) to vector, byte length must be a multiple of 4 bytes")
386342

387343
// DecodeVector decodes a byte slice that represents a vector. This is needed for distance functions.

sql/expression/arithmetic.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -691,9 +691,12 @@ func (e *UnaryMinus) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
691691
}
692692

693693
if !types.IsNumber(e.Child.Type()) {
694-
child, err = decimal.NewFromString(fmt.Sprintf("%v", child))
694+
child, _, err = types.InternalDecimalType.Convert(ctx, child)
695695
if err != nil {
696-
child = 0.0
696+
if !sql.ErrTruncatedIncorrect.Is(err) {
697+
child = 0.0
698+
}
699+
ctx.Warn(mysql.ERTruncatedWrongValue, "%s", err.Error())
697700
}
698701
}
699702

@@ -735,7 +738,7 @@ func (e *UnaryMinus) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
735738
case uint64:
736739
return -int64(n), nil
737740
case decimal.Decimal:
738-
return n.Neg(), err
741+
return n.Neg(), nil
739742
case string:
740743
// try getting int out of string value
741744
i, iErr := strconv.ParseInt(n, 10, 64)

0 commit comments

Comments
 (0)