Skip to content

Commit 84b0609

Browse files
author
James Cor
committed
best guess at this
1 parent bd0f19f commit 84b0609

File tree

5 files changed

+152
-78
lines changed

5 files changed

+152
-78
lines changed

enginetest/queries/queries.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10506,23 +10506,23 @@ 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)}},
1052210522
},
1052310523
{
1052410524
Query: "SELECT STR_TO_DATE('A09:30:17','A%h:%i:%s')",
10525-
Expected: []sql.Row{{"09:30:17"}},
10525+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 9, 30, 17, 0, time.UTC)}},
1052610526
},
1052710527
{
1052810528
Query: "SELECT STR_TO_DATE('a09:30:17','%h:%i:%s')",
@@ -10534,39 +10534,39 @@ var DateParseQueries = []QueryTest{
1053410534
},
1053510535
{
1053610536
Query: "SELECT STR_TO_DATE('09:30:17a','%h:%i:%s')",
10537-
Expected: []sql.Row{{"09:30:17"}},
10537+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 9, 30, 17, 0, time.UTC)}},
1053810538
},
1053910539
{
1054010540
Query: "SELECT STR_TO_DATE('09:30:17 pm','%h:%i:%s %p')",
10541-
Expected: []sql.Row{{"21:30:17"}},
10541+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 9, 30, 17, 0, time.UTC)}},
1054210542
},
1054310543
{
1054410544
Query: "SELECT STR_TO_DATE('9','%m')",
10545-
Expected: []sql.Row{{"0000-09-00"}},
10545+
Expected: []sql.Row{{time.Date(0, time.August, 31, 0, 0, 0, 0, time.UTC)}},
1054610546
},
1054710547
{
1054810548
Query: "SELECT STR_TO_DATE('9','%s')",
10549-
Expected: []sql.Row{{"00:00:09"}},
10549+
Expected: []sql.Row{{time.Date(-1, time.November, 30, 0, 0, 9, 0, time.UTC)}},
1055010550
},
1055110551
{
1055210552
Query: "SELECT STR_TO_DATE('01/02/99 314', '%m/%e/%y %f')",
10553-
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)}},
1055410554
},
1055510555
{
1055610556
Query: "SELECT STR_TO_DATE('01/02/99 0', '%m/%e/%y %f')",
10557-
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)}},
1055810558
},
1055910559
{
1056010560
Query: "SELECT STR_TO_DATE('01/02/99 05:14:12 PM', '%m/%e/%y %r')",
10561-
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)}},
1056210562
},
1056310563
{
1056410564
Query: "SELECT STR_TO_DATE('May 3, 10:23:00 2000', '%b %e, %H:%i:%s %Y')",
10565-
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)}},
1056610566
},
1056710567
{
1056810568
Query: "SELECT STR_TO_DATE('May 3, 10:23:00 PM 2000', '%b %e, %h:%i:%s %p %Y')",
10569-
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)}},
1057010570
},
1057110571
{
1057210572
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/planbuilder/dateparse/date.go

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,32 @@ var (
1111
timeSpecifiers = []uint8{'f', 'H', 'h', 'I', 'i', 'k', 'l', 'p', 'r', 'S', 's', 'T'}
1212
)
1313

14+
func HasDateOrTime(format string) (bool, bool, error) {
15+
_, specifiers, err := parsersFromFormatString(format)
16+
if err != nil {
17+
return false, false, err
18+
}
19+
20+
hasDate := false
21+
for _, s := range dateSpecifiers {
22+
if _, ok := specifiers[s]; ok {
23+
hasDate = true
24+
break
25+
}
26+
}
27+
28+
hasTime := false
29+
for _, s := range timeSpecifiers {
30+
if _, ok := specifiers[s]; ok {
31+
hasTime = true
32+
break
33+
}
34+
}
35+
36+
return hasDate, hasTime, nil
37+
}
38+
39+
1440
// ParseDateWithFormat parses the date string according to the given
1541
// format string, as defined in the MySQL specification.
1642
//
@@ -26,33 +52,25 @@ func ParseDateWithFormat(date, format string) (interface{}, error) {
2652
return nil, err
2753
}
2854

29-
hasDate := false
3055
for _, s := range dateSpecifiers {
3156
if _, ok := specifiers[s]; ok {
32-
hasDate = true
3357
break
3458
}
3559
}
36-
37-
hasTime := false
3860
_, hasAmPm := specifiers['p']
3961
for _, s := range timeSpecifiers {
4062
if _, ok := specifiers[s]; ok {
4163
// validate that am/pm is not used with 24 hour time specifiers
4264
if (s == 'H' || s == 'k' || s == 'T') && hasAmPm {
4365
return nil, fmt.Errorf("cannot use 24 hour time (H) with AM/PM (p)")
4466
}
45-
hasTime = true
4667
break
4768
}
4869
}
4970

5071
// trim all leading and trailing whitespace
5172
date = strings.TrimSpace(date)
5273

53-
// convert to all lowercase
54-
//date = strings.ToLower(date)
55-
5674
var dt datetime
5775
target := date
5876
for _, parser := range parsers {
@@ -64,18 +82,52 @@ func ParseDateWithFormat(date, format string) (interface{}, error) {
6482
target = rest
6583
}
6684

67-
var result string
68-
if hasDate && hasTime {
69-
result = fmt.Sprintf("%s %s", evaluateDate(dt), evaluateTime(dt))
70-
} else if hasTime {
71-
result = fmt.Sprintf("%s", evaluateTime(dt))
72-
} else if hasDate {
73-
result = fmt.Sprintf("%s", evaluateDate(dt))
74-
} else {
75-
return nil, fmt.Errorf("no value to evaluate")
85+
if dt.isEmpty() {
86+
return nil, nil
87+
}
88+
89+
// TODO: depending on if it's a date or time we should return a different type
90+
var year, month, day, hours, minutes, seconds, milliseconds, microseconds, nanoseconds int
91+
if dt.year != nil {
92+
year = int(*dt.year)
93+
}
94+
if dt.month != nil {
95+
month = int(*dt.month)
96+
}
97+
if dt.day != nil {
98+
day = int(*dt.day)
99+
}
100+
if dt.dayOfYear != nil {
101+
// offset from Jan 1st by the specified number of days
102+
dayOffsetted := time.Date(year, time.January, 0, 0, 0, 0, 0, time.Local).AddDate(0, 0, int(*dt.dayOfYear))
103+
month = int(dayOffsetted.Month())
104+
day = dayOffsetted.Day()
105+
}
106+
107+
if dt.hours != nil {
108+
hours = int(*dt.hours)
109+
}
110+
if dt.minutes != nil {
111+
minutes = int(*dt.minutes)
112+
}
113+
if dt.seconds != nil {
114+
seconds = int(*dt.seconds)
115+
}
116+
if dt.milliseconds != nil {
117+
milliseconds = int(*dt.milliseconds)
118+
}
119+
if dt.microseconds != nil {
120+
microseconds = int(*dt.microseconds)
121+
}
122+
if dt.nanoseconds != nil {
123+
nanoseconds = int(*dt.nanoseconds)
76124
}
125+
// convert partial seconds to nanoseconds
126+
nanosecondDuration := time.Microsecond*time.Duration(microseconds) +
127+
time.Millisecond*time.Duration(milliseconds) +
128+
time.Nanosecond*time.Duration(nanoseconds)
77129

78-
return result, nil
130+
return time.Date(year, time.Month(month), day, hours, minutes, seconds, int(nanosecondDuration), time.UTC), nil
79131
}
80132

81133
// Convert the user-defined format string into a slice of parser functions

sql/planbuilder/dateparse/eval.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"time"
66
)
77

8-
func evaluateDate(dt datetime) string {
8+
// TODO: delete file
9+
10+
func evaluateDate(dt datetime) time.Time {
911
var year, month, day int
1012

1113
if dt.year != nil {
@@ -27,10 +29,10 @@ func evaluateDate(dt datetime) string {
2729
day = dayOffsetted.Day()
2830
}
2931

30-
return fillWithZero(year, 4) + "-" + fillWithZero(month, 2) + "-" + fillWithZero(day, 2)
32+
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
3133
}
3234

33-
func evaluateTime(dt datetime) string {
35+
func evaluateTime(dt datetime) time.Time {
3436
var hours, minutes, seconds, milliseconds, microseconds, nanoseconds int
3537

3638
if dt.hours != nil {
@@ -68,7 +70,7 @@ func evaluateTime(dt datetime) string {
6870
t = t + "." + fillWithZero(int(nanosecondDuration), 6)
6971
}
7072

71-
return t
73+
return time.Date(0, 0, 0, hours, minutes, seconds, int(nanosecondDuration), time.Local)
7274
}
7375

7476
func fillWithZero(n int, length int) string {

0 commit comments

Comments
 (0)