Skip to content

Commit 6e5593e

Browse files
authored
fix case insensitivity and return type for str_to_date (#2839)
1 parent d8430eb commit 6e5593e

File tree

8 files changed

+210
-221
lines changed

8 files changed

+210
-221
lines changed

enginetest/queries/queries.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10506,59 +10506,67 @@ var VersionedScripts = []ScriptTest{
1050610506
var DateParseQueries = []QueryTest{
1050710507
{
1050810508
Query: "SELECT STR_TO_DATE('Jan 3, 2000', '%b %e, %Y')",
10509-
Expected: []sql.Row{{"2000-01-03"}},
10509+
Expected: []sql.Row{{time.Date(2000, time.January, 3, 0, 0, 0, 0, time.UTC)}},
1051010510
},
1051110511
{
1051210512
Query: "SELECT STR_TO_DATE('01,5,2013', '%d,%m,%Y')",
10513-
Expected: []sql.Row{{"2013-05-01"}},
10513+
Expected: []sql.Row{{time.Date(2013, time.May, 1, 0, 0, 0, 0, time.UTC)}},
1051410514
},
1051510515
{
1051610516
Query: "SELECT STR_TO_DATE('May 1, 2013','%M %d,%Y')",
10517-
Expected: []sql.Row{{"2013-05-01"}},
10517+
Expected: []sql.Row{{time.Date(2013, time.May, 1, 0, 0, 0, 0, time.UTC)}},
1051810518
},
1051910519
{
1052010520
Query: "SELECT STR_TO_DATE('a09:30:17','a%h:%i:%s')",
10521-
Expected: []sql.Row{{"09:30:17"}},
10521+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 9, 30, 17, 0, time.UTC)}},
10522+
},
10523+
{
10524+
Query: "SELECT STR_TO_DATE('A09:30:17','A%h:%i:%s')",
10525+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 9, 30, 17, 0, time.UTC)}},
1052210526
},
1052310527
{
1052410528
Query: "SELECT STR_TO_DATE('a09:30:17','%h:%i:%s')",
1052510529
Expected: []sql.Row{{nil}},
1052610530
},
10531+
{
10532+
Query: "SELECT STR_TO_DATE('A09:30:17','a%h:%i:%s')",
10533+
Expected: []sql.Row{{nil}},
10534+
},
1052710535
{
1052810536
Query: "SELECT STR_TO_DATE('09:30:17a','%h:%i:%s')",
10529-
Expected: []sql.Row{{"09:30:17"}},
10537+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 9, 30, 17, 0, time.UTC)}},
1053010538
},
1053110539
{
1053210540
Query: "SELECT STR_TO_DATE('09:30:17 pm','%h:%i:%s %p')",
10533-
Expected: []sql.Row{{"21:30:17"}},
10541+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 9, 30, 17, 0, time.UTC)}},
1053410542
},
1053510543
{
1053610544
Query: "SELECT STR_TO_DATE('9','%m')",
10537-
Expected: []sql.Row{{"0000-09-00"}},
10545+
Expected: []sql.Row{{time.Date(0, time.August, 31, 0, 0, 0, 0, time.UTC)}},
1053810546
},
1053910547
{
1054010548
Query: "SELECT STR_TO_DATE('9','%s')",
10541-
Expected: []sql.Row{{"00:00:09"}},
10549+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 0, 0, 9, 0, time.UTC)}},
1054210550
},
1054310551
{
1054410552
Query: "SELECT STR_TO_DATE('01/02/99 314', '%m/%e/%y %f')",
10545-
Expected: []sql.Row{{"1999-01-02 00:00:00.314000"}},
10553+
Expected: []sql.Row{{time.Date(1999, time.January, 2, 0, 0, 0, 314000, time.UTC)}},
1054610554
},
1054710555
{
1054810556
Query: "SELECT STR_TO_DATE('01/02/99 0', '%m/%e/%y %f')",
10549-
Expected: []sql.Row{{"1999-01-02 00:00:00.000000"}},
10557+
Expected: []sql.Row{{time.Date(1999, time.January, 2, 0, 0, 0, 0, time.UTC)}},
1055010558
},
1055110559
{
1055210560
Query: "SELECT STR_TO_DATE('01/02/99 05:14:12 PM', '%m/%e/%y %r')",
10553-
Expected: []sql.Row{{"1999-01-02 17:14:12"}},
10561+
Expected: []sql.Row{{time.Date(1999, time.January, 2, 5, 14, 12, 0, time.UTC)}},
1055410562
},
1055510563
{
1055610564
Query: "SELECT STR_TO_DATE('May 3, 10:23:00 2000', '%b %e, %H:%i:%s %Y')",
10557-
Expected: []sql.Row{{"2000-05-03 10:23:00"}},
10565+
Expected: []sql.Row{{time.Date(2000, time.May, 3, 10, 23, 0, 0, time.UTC)}},
1055810566
},
1055910567
{
1056010568
Query: "SELECT STR_TO_DATE('May 3, 10:23:00 PM 2000', '%b %e, %h:%i:%s %p %Y')",
10561-
Expected: []sql.Row{{"2000-05-03 22:23:00"}},
10569+
Expected: []sql.Row{{time.Date(2000, time.May, 3, 10, 23, 0, 0, time.UTC)}},
1056210570
},
1056310571
{
1056410572
Query: "SELECT STR_TO_DATE('May 3, 10:23:00 PM 2000', '%b %e, %H:%i:%s %p %Y')", // cannot use 24 hour time (%H) with AM/PM (%p)

sql/expression/function/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ var BuiltIns = []sql.Function{
218218
sql.Function1{Name: "sleep", Fn: NewSleep},
219219
sql.Function1{Name: "soundex", Fn: NewSoundex},
220220
sql.Function1{Name: "sqrt", Fn: NewSqrt},
221-
sql.FunctionN{Name: "str_to_date", Fn: NewStrToDate},
221+
sql.Function2{Name: "str_to_date", Fn: NewStrToDate},
222222
sql.FunctionN{Name: "subdate", Fn: NewSubDate},
223223
sql.Function2{Name: "point", Fn: spatial.NewPoint},
224224
sql.FunctionN{Name: "linestring", Fn: spatial.NewLineString},

sql/expression/function/str_to_date.go

Lines changed: 61 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,12 @@ package function
33
import (
44
"fmt"
55

6-
"github.com/dolthub/go-mysql-server/sql/planbuilder/dateparse"
7-
86
"github.com/dolthub/go-mysql-server/sql"
7+
"github.com/dolthub/go-mysql-server/sql/expression"
8+
"github.com/dolthub/go-mysql-server/sql/planbuilder/dateparse"
99
"github.com/dolthub/go-mysql-server/sql/types"
1010
)
1111

12-
// NewStrToDate constructs a new function expression from the given child expressions.
13-
func NewStrToDate(args ...sql.Expression) (sql.Expression, error) {
14-
if len(args) != 2 {
15-
return nil, sql.ErrInvalidArgumentNumber.New("STR_TO_DATE", 2, len(args))
16-
}
17-
return &StrToDate{
18-
Date: args[0],
19-
Format: args[1],
20-
}, nil
21-
}
22-
2312
// StrToDate defines the built-in function STR_TO_DATE(str, format)
2413
type StrToDate struct {
2514
Date sql.Expression
@@ -29,40 +18,91 @@ type StrToDate struct {
2918
var _ sql.FunctionExpression = (*StrToDate)(nil)
3019
var _ sql.CollationCoercible = (*StrToDate)(nil)
3120

21+
// NewStrToDate constructs a new function expression from the given child expressions.
22+
func NewStrToDate(arg1, arg2 sql.Expression) sql.Expression {
23+
return &StrToDate{
24+
Date: arg1,
25+
Format: arg2,
26+
}
27+
}
28+
29+
func (s *StrToDate) FunctionName() string {
30+
return "str_to_date"
31+
}
32+
3233
// Description implements sql.FunctionExpression
33-
func (s StrToDate) Description() string {
34+
func (s *StrToDate) Description() string {
3435
return "parses the date/datetime/timestamp expression according to the format specifier."
3536
}
3637

3738
// Resolved returns whether the node is resolved.
38-
func (s StrToDate) Resolved() bool {
39+
func (s *StrToDate) Resolved() bool {
3940
dateResolved := s.Date == nil || s.Date.Resolved()
4041
formatResolved := s.Format == nil || s.Format.Resolved()
4142
return dateResolved && formatResolved
4243
}
4344

44-
func (s StrToDate) String() string {
45+
func (s *StrToDate) String() string {
4546
return fmt.Sprintf("%s(%s,%s)", s.FunctionName(), s.Date, s.Format)
4647
}
4748

4849
// Type returns the expression type.
49-
func (s StrToDate) Type() sql.Type {
50-
// TODO: precision
50+
func (s *StrToDate) Type() sql.Type {
51+
// TODO: depending on the format, the return type can be a date, datetime or timestamp
52+
// just make best guess for now
53+
formatLit, isLit := s.Format.(*expression.Literal)
54+
if !isLit {
55+
return types.Datetime
56+
}
57+
format, err := formatLit.Eval(nil, nil)
58+
if err != nil {
59+
return types.Datetime
60+
}
61+
formatStr, isStr := format.(string)
62+
if !isStr {
63+
return types.Datetime
64+
}
65+
66+
hasDate, hasTime, err := dateparse.HasDateOrTime(formatStr)
67+
if err != nil {
68+
return types.Datetime
69+
}
70+
if hasDate && hasTime {
71+
return types.Datetime
72+
}
73+
if hasDate {
74+
return types.Date
75+
}
76+
if hasTime {
77+
return types.Time
78+
}
5179
return types.Datetime
5280
}
5381

5482
// CollationCoercibility implements the interface sql.CollationCoercible.
55-
func (StrToDate) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
83+
func (*StrToDate) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
5684
return sql.Collation_binary, 5
5785
}
5886

5987
// IsNullable returns whether the expression can be null.
60-
func (s StrToDate) IsNullable() bool {
88+
func (s *StrToDate) IsNullable() bool {
6189
return true
6290
}
6391

92+
// Children returns the children expressions of this expression.
93+
func (s *StrToDate) Children() []sql.Expression {
94+
return []sql.Expression{s.Date, s.Format}
95+
}
96+
97+
func (s *StrToDate) WithChildren(children ...sql.Expression) (sql.Expression, error) {
98+
if len(children) != 2 {
99+
return nil, sql.ErrInvalidArgumentNumber.New("STR_TO_DATE", 2, len(children))
100+
}
101+
return NewStrToDate(children[0], children[1]), nil
102+
}
103+
64104
// Eval evaluates the given row and returns a result.
65-
func (s StrToDate) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
105+
func (s *StrToDate) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
66106
date, err := s.Date.Eval(ctx, row)
67107
if err != nil {
68108
return nil, err
@@ -93,23 +133,3 @@ func (s StrToDate) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
93133
// but depends on strict sql_mode with NO_ZERO_DATE or NO_ZERO_IN_DATE modes enabled.
94134
return goTime, nil
95135
}
96-
97-
// Children returns the children expressions of this expression.
98-
func (s StrToDate) Children() []sql.Expression {
99-
children := make([]sql.Expression, 0, 2)
100-
if s.Date != nil {
101-
children = append(children, s.Date)
102-
}
103-
if s.Format != nil {
104-
children = append(children, s.Format)
105-
}
106-
return children
107-
}
108-
109-
func (s StrToDate) WithChildren(children ...sql.Expression) (sql.Expression, error) {
110-
return NewStrToDate(children...)
111-
}
112-
113-
func (s StrToDate) FunctionName() string {
114-
return "str_to_date"
115-
}

sql/expression/function/str_to_date_test.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,22 @@ func TestStrToDate(t *testing.T) {
2020
fmtStr string
2121
expected interface{}
2222
}{
23-
{"standard", "Dec 26, 2000 2:13:15", "%b %e, %Y %T", "2000-12-26 02:13:15"},
24-
{"ymd", "20240101", "%Y%m%d", "2024-01-01"},
25-
{"ymd", "2024121", "%Y%m%d", "2024-12-01"},
23+
{"standard", "Dec 26, 2000 2:13:15", "%b %e, %Y %T", time.Date(2000, time.December, 26, 2, 13, 15, 0, time.UTC)},
24+
{"ymd", "20240101", "%Y%m%d", time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC)},
25+
{"ymd", "2024121", "%Y%m%d", time.Date(2024, time.December, 1, 0, 0, 0, 0, time.UTC)},
2626
{"ymd", "20241301", "%Y%m%d", nil},
2727
{"ymd", "20240001", "%Y%m%d", nil},
28-
{"ymd-with-time", "2024010203:04:05", "%Y%m%d%T", "2024-01-02 03:04:05"},
29-
{"ymd-with-time", "202408122:03:04", "%Y%m%d%T", "2024-08-12 02:03:04"},
28+
{"ymd-with-time", "2024010203:04:05", "%Y%m%d%T", time.Date(2024, time.January, 2, 3, 4, 5, 0, time.UTC)},
29+
{"ymd-with-time", "202408122:03:04", "%Y%m%d%T", time.Date(2024, time.August, 12, 2, 3, 4, 0, time.UTC)},
3030
// TODO: It shoud be nil, but returns "2024-02-31"
3131
// {"ymd", "20240231", "%Y%m%d", nil},
3232
}
3333

3434
for _, tt := range testCases {
35-
f, err := NewStrToDate(
35+
f := NewStrToDate(
3636
expression.NewGetField(0, types.Text, "", true),
3737
expression.NewGetField(1, types.Text, "", true),
3838
)
39-
if err != nil {
40-
t.Fatal(err)
41-
}
4239
t.Run(tt.name, func(t *testing.T) {
4340
dtime := eval(t, f, sql.NewRow(tt.dateStr, tt.fmtStr))
4441
require.Equal(t, tt.expected, dtime)
@@ -60,13 +57,10 @@ func TestStrToDateFailure(t *testing.T) {
6057
}
6158

6259
for _, tt := range testCases {
63-
f, err := NewStrToDate(
60+
f := NewStrToDate(
6461
expression.NewGetField(0, types.Text, "", true),
6562
expression.NewGetField(1, types.Text, "", true),
6663
)
67-
if err != nil {
68-
t.Fatal(err)
69-
}
7064
t.Run(tt.name, func(t *testing.T) {
7165
dtime := eval(t, f, sql.NewRow(tt.dateStr, tt.fmtStr))
7266
require.Equal(t, nil, dtime)

0 commit comments

Comments
 (0)