Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parser, expression: implement builtin LAST_DAY #4290

Merged
merged 34 commits into from
Aug 23, 2017
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fe4e72c
Merge pull request #3 from pingcap/master
spongedu Mar 21, 2017
40cd49e
Merge pull request #4 from pingcap/master
spongedu Mar 29, 2017
99847cd
Merge remote-tracking branch 'upstream/master'
spongedu Mar 31, 2017
1c670e4
Merge remote-tracking branch 'upstream/master'
spongedu Apr 7, 2017
029cf85
Merge remote-tracking branch 'upstream/master'
spongedu Jul 20, 2017
0adb1c6
Merge remote-tracking branch 'upstream/master'
spongedu Aug 1, 2017
fec7c8e
Merge remote-tracking branch 'upstream/master'
spongedu Aug 2, 2017
150787e
Merge remote-tracking branch 'upstream/master'
spongedu Aug 3, 2017
8cd6042
Merge remote-tracking branch 'upstream/master'
spongedu Aug 3, 2017
b6620af
Merge remote-tracking branch 'upstream/master'
spongedu Aug 3, 2017
86217bf
Merge remote-tracking branch 'upstream/master'
spongedu Aug 3, 2017
c872194
Merge remote-tracking branch 'upstream/master'
spongedu Aug 4, 2017
bb3ce69
Merge remote-tracking branch 'upstream/master'
spongedu Aug 9, 2017
2f11e70
Merge remote-tracking branch 'upstream/master'
spongedu Aug 10, 2017
0bcd8c9
Merge remote-tracking branch 'upstream/master'
spongedu Aug 11, 2017
58ac0a5
Merge remote-tracking branch 'upstream/master'
spongedu Aug 12, 2017
a57b198
Merge remote-tracking branch 'upstream/master'
spongedu Aug 14, 2017
d0e4250
Merge remote-tracking branch 'upstream/master'
spongedu Aug 14, 2017
622ebe8
Merge remote-tracking branch 'upstream/master'
spongedu Aug 15, 2017
620828e
Merge remote-tracking branch 'upstream/master'
spongedu Aug 15, 2017
d86616d
Merge remote-tracking branch 'upstream/master'
spongedu Aug 16, 2017
8b8fdca
Merge remote-tracking branch 'upstream/master'
spongedu Aug 17, 2017
a46d7af
Merge remote-tracking branch 'upstream/master'
spongedu Aug 20, 2017
ec7572e
Merge remote-tracking branch 'upstream/master'
spongedu Aug 21, 2017
965fdbf
Merge remote-tracking branch 'upstream/master'
spongedu Aug 21, 2017
3dc45e8
Merge remote-tracking branch 'upstream/master'
spongedu Aug 21, 2017
23836ec
Merge remote-tracking branch 'upstream/master'
spongedu Aug 22, 2017
30ea9c3
parser, expression: implement builtin LAST_DAY
spongedu Aug 22, 2017
f0abac2
Merge remote-tracking branch 'upstream/master' into builtin_lastday
spongedu Aug 22, 2017
23e25d9
resolve conficts
spongedu Aug 22, 2017
3957064
resolve conflicts
spongedu Aug 22, 2017
520d9bc
code refine
spongedu Aug 22, 2017
5e95510
resolve conflicts
spongedu Aug 23, 2017
f9fcf1d
Merge branch 'master' into builtin_lastday
jackysp Aug 23, 2017
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
1 change: 1 addition & 0 deletions ast/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ const (
WeekOfYear = "weekofyear"
Year = "year"
YearWeek = "yearweek"
LastDay = "last_day"

// string functions
ASCII = "ascii"
Expand Down
1 change: 1 addition & 0 deletions expression/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ var funcs = map[string]functionClass{
ast.WeekOfYear: &weekOfYearFunctionClass{baseFunctionClass{ast.WeekOfYear, 1, 1}},
ast.Year: &yearFunctionClass{baseFunctionClass{ast.Year, 1, 1}},
ast.YearWeek: &yearWeekFunctionClass{baseFunctionClass{ast.YearWeek, 1, 2}},
ast.LastDay: &lastDayFunctionClass{baseFunctionClass{ast.LastDay, 1, 1}},

// string functions
ast.ASCII: &asciiFunctionClass{baseFunctionClass{ast.ASCII, 1, 1}},
Expand Down
59 changes: 53 additions & 6 deletions expression/builtin_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ var (
_ functionClass = &toSecondsFunctionClass{}
_ functionClass = &utcTimeFunctionClass{}
_ functionClass = &timestampFunctionClass{}
_ functionClass = &lastDayFunctionClass{}
)

var (
Expand Down Expand Up @@ -154,6 +155,7 @@ var (
_ builtinFunc = &builtinToSecondsSig{}
_ builtinFunc = &builtinUTCTimeSig{}
_ builtinFunc = &builtinTimestampSig{}
_ builtinFunc = &builtinLastDaySig{}
)

// handleInvalidTimeError reports error or warning depend on the context.
Expand Down Expand Up @@ -2981,9 +2983,6 @@ func (b *builtinToDaysSig) evalInt(row []types.Datum) (int64, bool, error) {
if isNull || err != nil {
return 0, true, errors.Trace(handleInvalidTimeError(b.ctx, err))
}
if err != nil {
return 0, true, errors.Trace(handleInvalidTimeError(b.ctx, err))
}
ret := types.TimestampDiff("DAY", types.ZeroDate, arg)
if ret == 0 {
return 0, true, errors.Trace(handleInvalidTimeError(b.ctx, types.ErrInvalidTimeFormat))
Expand Down Expand Up @@ -3018,9 +3017,6 @@ func (b *builtinToSecondsSig) evalInt(row []types.Datum) (int64, bool, error) {
if isNull || err != nil {
return 0, true, errors.Trace(handleInvalidTimeError(b.ctx, err))
}
if err != nil {
return 0, true, errors.Trace(handleInvalidTimeError(b.ctx, err))
}
ret := types.TimestampDiff("SECOND", types.ZeroDate, arg)
if ret == 0 {
return 0, true, errors.Trace(handleInvalidTimeError(b.ctx, types.ErrInvalidTimeFormat))
Expand Down Expand Up @@ -3063,3 +3059,54 @@ func (b *builtinUTCTimeSig) eval(row []types.Datum) (d types.Datum, err error) {
d.SetString(time.Now().UTC().Format(utctimeFormat))
return convertToDuration(b.ctx.GetSessionVars().StmtCtx, d, fsp)
}

type lastDayFunctionClass struct {
baseFunctionClass
}

func (c *lastDayFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
if err := c.verifyArgs(args); err != nil {
return nil, errors.Trace(err)
}
bf, err := newBaseBuiltinFuncWithTp(args, ctx, tpTime, tpTime)
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move the error check up, just after newBaseBuiltinFuncWithTp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

return nil, errors.Trace(err)
}
bf.tp.Tp, bf.tp.Flen, bf.tp.Decimal = mysql.TypeDate, mysql.MaxDateWidth, types.DefaultFsp
sig := &builtinLastDaySig{baseTimeBuiltinFunc{bf}}
return sig.setSelf(sig), nil
}

type builtinLastDaySig struct {
baseTimeBuiltinFunc
}

// evalTime evals a builtinLastDaySig.
// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_last-day
func (b *builtinLastDaySig) evalTime(row []types.Datum) (types.Time, bool, error) {
sc := b.ctx.GetSessionVars().StmtCtx
arg, isNull, err := b.args[0].EvalTime(row, sc)
if isNull || err != nil {
return types.Time{}, true, errors.Trace(handleInvalidTimeError(b.ctx, err))
}
tm := arg.Time
year, month, day := tm.Year(), tm.Month(), 30
if year == 0 && month == 0 && tm.Day() == 0 {
return types.Time{}, true, errors.Trace(handleInvalidTimeError(b.ctx, types.ErrInvalidTimeFormat))
}
if month == 1 || month == 3 || month == 5 ||
month == 7 || month == 8 || month == 10 || month == 12 {
day = 31
} else if month == 2 {
day = 28
if tm.IsLeapYear() {
day = 29
}
}
ret := types.Time{
Time: types.FromDate(year, month, day, 0, 0, 0, 0),
Type: mysql.TypeDate,
Fsp: types.DefaultFsp,
}
return ret, false, nil
}
39 changes: 39 additions & 0 deletions expression/builtin_time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1943,3 +1943,42 @@ func (s *testEvaluatorSuite) TestPeriodDiff(c *C) {
c.Assert(err, IsNil)
c.Assert(v.Kind(), Equals, types.KindNull)
}

func (s *testEvaluatorSuite) TestLastDay(c *C) {
tests := []struct {
param interface{}
expect string
}{
{"2003-02-05", "2003-02-28"},
{"2004-02-05", "2004-02-29"},
{"2004-01-01 01:01:01", "2004-01-31"},
{950501, "1995-05-31"},
}

fc := funcs[ast.LastDay]
for _, test := range tests {
t := []types.Datum{types.NewDatum(test.param)}
f, err := fc.getFunction(datumsToConstants(t), s.ctx)
c.Assert(f.isDeterministic(), IsTrue)
c.Assert(err, IsNil)
d, err := f.eval(nil)
c.Assert(err, IsNil)
result, _ := d.ToString()
c.Assert(result, Equals, test.expect)
}

testsNull := []interface{}{
"0000-00-00",
"1992-13-00",
"2007-10-07 23:59:61",
123456789}

for _, i := range testsNull {
t := []types.Datum{types.NewDatum(i)}
f, err := fc.getFunction(datumsToConstants(t), s.ctx)
c.Assert(f.isDeterministic(), IsTrue)
c.Assert(err, IsNil)
d, err := f.eval(nil)
c.Assert(d.IsNull(), IsTrue)
}
}
11 changes: 11 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,17 @@ func (s *testIntegrationSuite) TestTimeBuiltin(c *C) {
result.Check(testkit.Rows("62966505600 63426672000 63426721412 63426721412"))
result = tk.MustQuery("select to_days(950501), to_days('2007-10-07'), to_days('2007-10-07 00:00:59'), to_days('0000-01-01')")
result.Check(testkit.Rows("728779 733321 733321 1"))
result = tk.MustQuery("select last_day('2003-02-05'), last_day('2004-02-05'), last_day('2004-01-01 01:01:01'), last_day(950501);")
result.Check(testkit.Rows("2003-02-28 2004-02-29 2004-01-31 1995-05-31"))

tk.MustExec("SET SQL_MODE='';")
result = tk.MustQuery("select last_day('0000-00-00');")
result.Check(testkit.Rows("<nil>"))
result = tk.MustQuery("select to_days('0000-00-00');")
result.Check(testkit.Rows("<nil>"))
result = tk.MustQuery("select to_seconds('0000-00-00');")
result.Check(testkit.Rows("<nil>"))

result = tk.MustQuery("select TIMESTAMPDIFF(MONTH,'2003-02-01','2003-05-01'), TIMESTAMPDIFF(yEaR,'2002-05-01', " +
"'2001-01-01'), TIMESTAMPDIFF(minute,binary('2003-02-01'),'2003-05-01 12:05:55'), TIMESTAMPDIFF(day," +
"'1995-05-02', 950501);")
Expand Down
2 changes: 1 addition & 1 deletion expression/typeinferer.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ func (v *typeInferrer) handleFuncCallExpr(x *ast.FuncCallExpr) {
case ast.Curtime, ast.CurrentTime, ast.TimeDiff, ast.MakeTime, ast.SecToTime, ast.UTCTime, ast.Time:
tp = types.NewFieldType(mysql.TypeDuration)
tp.Decimal = v.getFsp(x)
case ast.Curdate, ast.CurrentDate, ast.Date, ast.FromDays, ast.MakeDate:
case ast.Curdate, ast.CurrentDate, ast.Date, ast.FromDays, ast.MakeDate, ast.LastDay:
tp = types.NewFieldType(mysql.TypeDate)
case ast.DateAdd, ast.DateSub, ast.AddDate, ast.SubDate, ast.Timestamp, ast.TimestampAdd, ast.StrToDate, ast.ConvertTz:
tp = types.NewFieldType(mysql.TypeDatetime)
Expand Down
1 change: 1 addition & 0 deletions parser/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ var tokenMap = map[string]int{
"TO_BASE64": toBase64,
"TO_DAYS": toDays,
"TO_SECONDS": toSeconds,
"LAST_DAY": lastDay,
"TRAILING": trailing,
"TRANSACTION": transaction,
"TRIGGER": trigger,
Expand Down
7 changes: 6 additions & 1 deletion parser/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ import (
toBase64 "TO_BASE64"
toDays "TO_DAYS"
toSeconds "TO_SECONDS"
lastDay "LAST_DAY"
getLock "GET_LOCK"
releaseLock "RELEASE_LOCK"
rpad "RPAD"
Expand Down Expand Up @@ -2459,7 +2460,7 @@ NotKeywordToken:
"QUOTE" | "SEC_TO_TIME" | "SECOND" | "SIGN" | "SIN" | "SLEEP" | "SQRT" | "SQL_CALC_FOUND_ROWS" | "STR_TO_DATE" | "SUBTIME" | "SUBDATE" | "SUBSTRING" %prec lowerThanLeftParen |
"SESSION_USER" | "SUBSTRING_INDEX" | "SUM" | "SYSTEM_USER" | "TAN" | "TIME_FORMAT" | "TIME_TO_SEC" | "TIMESTAMPADD" | "TO_BASE64" | "TO_DAYS" | "TO_SECONDS" | "TRIM" | "RTRIM" | "UCASE" | "UTC_TIME" | "UPPER" | "VERSION" | "WEEKDAY" | "WEEKOFYEAR" | "YEARWEEK" | "ROUND"
| "STATS_PERSISTENT" | "GET_LOCK" | "RELEASE_LOCK" | "CEIL" | "CEILING" | "FLOOR" | "FROM_UNIXTIME" | "TIMEDIFF" | "LN" | "LOG" | "LOG2" | "LOG10" | "FIELD_KWD"
| "AES_DECRYPT" | "AES_ENCRYPT" | "QUOTE"
| "AES_DECRYPT" | "AES_ENCRYPT" | "QUOTE" | "LAST_DAY"
| "ANY_VALUE" | "INET_ATON" | "INET_NTOA" | "INET6_ATON" | "INET6_NTOA" | "IS_FREE_LOCK" | "IS_IPV4" | "IS_IPV4_COMPAT" | "IS_IPV4_MAPPED" | "IS_IPV6" | "IS_USED_LOCK" | "MASTER_POS_WAIT" | "NAME_CONST" | "RELEASE_ALL_LOCKS" | "UUID" | "UUID_SHORT"
| "COMPRESS" | "DECODE" | "DES_DECRYPT" | "DES_ENCRYPT" | "ENCODE" | "ENCRYPT" | "MD5" | "OLD_PASSWORD" | "RANDOM_BYTES" | "SHA1" | "SHA" | "SHA2" | "UNCOMPRESS" | "UNCOMPRESSED_LENGTH" | "VALIDATE_PASSWORD_STRENGTH"
| "JSON_EXTRACT" | "JSON_UNQUOTE" | "JSON_TYPE" | "JSON_MERGE" | "JSON_SET" | "JSON_INSERT" | "JSON_REPLACE" | "JSON_REMOVE" | "JSON_OBJECT" | "JSON_ARRAY" | "TIDB_VERSION"
Expand Down Expand Up @@ -3563,6 +3564,10 @@ FunctionCallNonKeyword:
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: $3.([]ast.ExprNode)}
}
| "LAST_DAY" '(' ExpressionListOpt ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: $3.([]ast.ExprNode)}
}
| "TRIM" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{
Expand Down
3 changes: 3 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,9 @@ func (s *testParserSuite) TestBuiltin(c *C) {
{"SELECT TO_DAYS('2007-10-07')", true},
{"SELECT TO_SECONDS('2009-11-29')", true},

// for LAST_DAY
{"SELECT LAST_DAY('2003-02-05');", true},

// for UTC_TIME
{"SELECT UTC_TIME(), UTC_TIME(1)", true},

Expand Down
10 changes: 10 additions & 0 deletions plan/typeinfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,16 @@ func (s *testPlanSuite) createTestCase4TimeFuncs() []typeInferTestCase {
{"microsecond(c_set )", mysql.TypeLonglong, charset.CharsetBin, mysql.BinaryFlag, 6, 0},
{"microsecond(c_enum )", mysql.TypeLonglong, charset.CharsetBin, mysql.BinaryFlag, 6, 0},

{"last_day(c_datetime)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},
{"last_day(c_datetime_d)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},
{"last_day(c_timestamp)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},
{"last_day(c_timestamp_d)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},
{"last_day(c_char)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},
{"last_day(c_varchar)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},
{"last_day(c_varchar)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},
{"last_day(c_text_d)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},
{"last_day(c_blob_d)", mysql.TypeDate, charset.CharsetBin, mysql.BinaryFlag, mysql.MaxDateWidth, 0},

{"week(c_int_d )", mysql.TypeLonglong, charset.CharsetBin, mysql.BinaryFlag, 2, 0},
{"week(c_bigint_d )", mysql.TypeLonglong, charset.CharsetBin, mysql.BinaryFlag, 2, 0},
{"week(c_float_d )", mysql.TypeLonglong, charset.CharsetBin, mysql.BinaryFlag, 2, 0},
Expand Down
4 changes: 4 additions & 0 deletions util/types/mytime.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ func (t mysqlTime) GoTime(loc *gotime.Location) (gotime.Time, error) {
return tm, nil
}

func (t mysqlTime) IsLeapYear() bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add unit test for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return (t.year%4 == 0 && t.year%100 != 0) || t.year%400 == 0
}

func newMysqlTime(year, month, day, hour, minute, second, microsecond int) mysqlTime {
return mysqlTime{
uint16(year),
Expand Down
25 changes: 25 additions & 0 deletions util/types/mytime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,28 @@ func (s *testMyTimeSuite) TestMixDateAndTime(c *C) {
c.Assert(compareTime(&t.date, &t.expect), Equals, 0, Commentf("%d", ith))
}
}

func (s *testMyTimeSuite) TestIsLeapYear(c *C) {
tests := []struct {
T mysqlTime
Expect bool
}{
{mysqlTime{1960, 1, 1, 0, 0, 0, 0}, true},
{mysqlTime{1963, 2, 21, 0, 0, 0, 0}, false},
{mysqlTime{2008, 11, 25, 0, 0, 0, 0}, true},
{mysqlTime{2017, 4, 24, 0, 0, 0, 0}, false},
{mysqlTime{1988, 2, 29, 0, 0, 0, 0}, true},
{mysqlTime{2000, 3, 15, 0, 0, 0, 0}, true},
{mysqlTime{1992, 5, 3, 0, 0, 0, 0}, true},
{mysqlTime{2024, 10, 1, 0, 0, 0, 0}, true},
{mysqlTime{2016, 6, 29, 0, 0, 0, 0}, true},
{mysqlTime{2015, 6, 29, 0, 0, 0, 0}, false},
{mysqlTime{2014, 9, 31, 0, 0, 0, 0}, false},
{mysqlTime{2001, 12, 7, 0, 0, 0, 0}, false},
{mysqlTime{1989, 7, 6, 0, 0, 0, 0}, false},
}

for _, tt := range tests {
c.Assert(tt.T.IsLeapYear(), Equals, tt.Expect)
}
}
1 change: 1 addition & 0 deletions util/types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ type TimeInternal interface {
Week(mode int) int
Microsecond() int
GoTime(*gotime.Location) (gotime.Time, error)
IsLeapYear() bool
}

// FromGoTime translates time.Time to mysql time internal representation.
Expand Down