Skip to content

Commit

Permalink
implement last_days() (#2460)
Browse files Browse the repository at this point in the history
  • Loading branch information
jycor authored Apr 16, 2024
1 parent 9a87691 commit 509c423
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
12 changes: 12 additions & 0 deletions enginetest/queries/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -9611,6 +9611,18 @@ from typestable`,
{time.Date(2024, 4, 15, 0, 0, 0, 0, time.UTC)},
},
},
{
Query: "select last_day('2000-02-21');",
Expected: []sql.Row{
{time.Date(2000, 2, 29, 0, 0, 0, 0, time.UTC)},
},
},
{
Query: "select last_day('1999-11-05');",
Expected: []sql.Row{
{time.Date(1999, 11, 30, 0, 0, 0, 0, time.UTC)},
},
},
}

var KeylessQueries = []QueryTest{
Expand Down
79 changes: 79 additions & 0 deletions sql/expression/function/days.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,82 @@ func (f *FromDays) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
months, days := daysToMonth(years, days)
return time.Date(int(years), time.Month(months), int(days), 0, 0, 0, 0, time.UTC), nil
}

// LastDay is a function that returns the date at the last day of the month.
type LastDay struct {
expression.UnaryExpression
}

var _ sql.FunctionExpression = (*LastDay)(nil)
var _ sql.CollationCoercible = (*LastDay)(nil)

// NewLastDay creates a new LastDay function.
func NewLastDay(date sql.Expression) sql.Expression {
return &LastDay{expression.UnaryExpression{Child: date}}
}

// CollationCoercibility implements sql.CollationCoercible
func (f *LastDay) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
return sql.Collation_binary, 5
}

// String implements sql.Stringer
func (f *LastDay) String() string {
return fmt.Sprintf("%s(%s)", f.FunctionName(), f.Child.String())
}

// FunctionName implements sql.FunctionExpression
func (f *LastDay) FunctionName() string {
return "last_day"
}

// Description implements sql.FunctionExpression
func (f *LastDay) Description() string {
return "return the last day of the month for date"
}

// Type implements sql.Expression
func (f *LastDay) Type() sql.Type {
return types.Date
}

// WithChildren implements sql.Expression
func (f *LastDay) WithChildren(children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(f, len(children), 1)
}
return NewLastDay(children[0]), nil
}

// lastDay returns the last day of the month for the given year and month
func lastDay(year, month int) int {
if month == 2 && isLeapYear(int64(year)) {
return 29
}
return int(daysPerMonth[month-1])
}

// Eval implements sql.Expression
func (f *LastDay) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
date, err := f.Child.Eval(ctx, row)
if err != nil {
return nil, err
}
if date == nil {
return nil, nil
}

date, _, err = types.Date.Convert(date)
if err != nil {
ctx.Warn(1292, err.Error())
return nil, nil
}

d, ok := date.(time.Time)
if !ok {
return nil, nil
}

lDay := lastDay(d.Year(), int(d.Month()))
return time.Date(d.Year(), d.Month(), lDay, 0, 0, 0, 0, time.UTC), nil
}
107 changes: 107 additions & 0 deletions sql/expression/function/days_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,110 @@ func TestFromDays(t *testing.T) {
})
}
}

func TestLastDay(t *testing.T) {
tests := []struct {
arg sql.Expression
exp interface{}
err bool
skip bool
}{
{
arg: expression.NewLiteral(nil, types.Null),
exp: nil,
},
{
arg: expression.NewLiteral("notadate", types.Text),
exp: nil,
},
{
arg: expression.NewLiteral(-10, types.Int32),
exp: nil,
},
{
arg: expression.NewLiteral(366.13, types.Float32),
exp: nil,
},
{
arg: expression.NewLiteral("0004-02-01", types.Text),
exp: time.Date(4, 2, 29, 0, 0, 0, 0, time.UTC),
},
{
skip: true, // we should parse 00 days
arg: expression.NewLiteral("0001-11-00", types.Text),
exp: time.Date(1, 11, 30, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("2001-02-30", types.Text),
exp: nil,
},
{
arg: expression.NewLiteral("0001-01-31", types.Text),
exp: time.Date(1, 1, 31, 0, 0, 0, 0, time.UTC),
},

{
arg: expression.NewLiteral("0001-01-01", types.Text),
exp: time.Date(1, 1, 31, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-02-01", types.Text),
exp: time.Date(1, 2, 28, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-03-01", types.Text),
exp: time.Date(1, 3, 31, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-04-01", types.Text),
exp: time.Date(1, 4, 30, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-05-01", types.Text),
exp: time.Date(1, 5, 31, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-06-01", types.Text),
exp: time.Date(1, 6, 30, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-07-01", types.Text),
exp: time.Date(1, 7, 31, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-08-01", types.Text),
exp: time.Date(1, 8, 31, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-09-01", types.Text),
exp: time.Date(1, 9, 30, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-10-01", types.Text),
exp: time.Date(1, 10, 31, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-11-01", types.Text),
exp: time.Date(1, 11, 30, 0, 0, 0, 0, time.UTC),
},
{
arg: expression.NewLiteral("0001-12-01", types.Text),
exp: time.Date(1, 12, 31, 0, 0, 0, 0, time.UTC),
},
}
for _, tt := range tests {
name := fmt.Sprintf("last_day(%v)", tt.arg)
t.Run(name, func(t *testing.T) {
if tt.skip {
t.Skip()
}
f := NewLastDay(tt.arg)
res, err := f.Eval(sql.NewEmptyContext(), nil)
if tt.err {
require.Error(t, err)
return
}
require.Equal(t, tt.exp, res)
})
}
}
1 change: 1 addition & 0 deletions sql/expression/function/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ var BuiltIns = []sql.Function{
sql.FunctionN{Name: "json_value", Fn: json.NewJsonValue},
sql.FunctionN{Name: "lag", Fn: func(e ...sql.Expression) (sql.Expression, error) { return window.NewLag(e...) }},
sql.Function1{Name: "last", Fn: func(e sql.Expression) sql.Expression { return aggregation.NewLast(e) }},
sql.Function1{Name: "last_day", Fn: NewLastDay},
sql.FunctionN{Name: "last_insert_id", Fn: NewLastInsertId},
sql.FunctionN{Name: "last_insert_uuid", Fn: NewLastInsertUuid},
sql.Function1{Name: "lcase", Fn: NewLower},
Expand Down

0 comments on commit 509c423

Please sign in to comment.