diff --git a/be/src/exprs/expr-test.cc b/be/src/exprs/expr-test.cc index 3c316e17b..4147093c9 100644 --- a/be/src/exprs/expr-test.cc +++ b/be/src/exprs/expr-test.cc @@ -267,6 +267,80 @@ class ExprTest : public testing::Test { return results; } + void TestNextDayFunction(){ + // Sequential test cases + TestTimestampValue("next_day('2016-05-01','Sunday')", + TimestampValue("2016-05-08 00:00:00", 19)); + TestTimestampValue("next_day('2016-05-01','Monday')", + TimestampValue("2016-05-02 00:00:00", 19)); + TestTimestampValue("next_day('2016-05-01','Tuesday')", + TimestampValue("2016-05-03 00:00:00", 19)); + TestTimestampValue("next_day('2016-05-01','Wednesday')", + TimestampValue("2016-05-04 00:00:00", 19)); + TestTimestampValue("next_day('2016-05-01','Thursday')", + TimestampValue("2016-05-05 00:00:00", 19)); + TestTimestampValue("next_day('2016-05-01','Friday')", + TimestampValue("2016-05-06 00:00:00", 19)); + TestTimestampValue("next_day('2016-05-01','Saturday')", + TimestampValue("2016-05-07 00:00:00", 19)); + + // Random test cases + TestTimestampValue("next_day('1910-01-18','SunDay')", + TimestampValue("1910-01-23 00:00:00", 19)); + TestTimestampValue("next_day('1916-06-05', 'SUN')", + TimestampValue("1916-06-11 00:00:00", 19)); + TestTimestampValue("next_day('1932-11-08','monday')", + TimestampValue("1932-11-14 00:00:00", 19)); + TestTimestampValue("next_day('1933-09-11','Mon')", + TimestampValue("1933-09-18 00:00:00", 19)); + TestTimestampValue("next_day('1934-03-21','TUeSday')", + TimestampValue("1934-03-27 00:00:00", 19)); + TestTimestampValue("next_day('1954-02-25','tuE')", + TimestampValue("1954-03-02 00:00:00", 19)); + TestTimestampValue("next_day('1965-04-18','WeDneSdaY')", + TimestampValue("1965-04-21 00:00:00", 19)); + TestTimestampValue("next_day('1966-08-29','wed')", + TimestampValue("1966-08-31 00:00:00", 19)); + TestTimestampValue("next_day('1968-07-23','tHurSday')", + TimestampValue("1968-07-25 00:00:00", 19)); + TestTimestampValue("next_day('1969-05-28','thu')", + TimestampValue("1969-05-29 00:00:00", 19)); + TestTimestampValue("next_day('1989-10-12','fRIDay')", + TimestampValue("1989-10-13 00:00:00", 19)); + TestTimestampValue("next_day('1973-10-02','frI')", + TimestampValue("1973-10-05 00:00:00", 19)); + TestTimestampValue("next_day('2000-02-29','saTUrDaY')", + TimestampValue("2000-03-04 00:00:00", 19)); + TestTimestampValue("next_day('2013-04-12','sat')", + TimestampValue("2013-04-13 00:00:00", 19)); + TestTimestampValue("next_day('2013-12-25','Saturday')", + TimestampValue("2013-12-28 00:00:00", 19)); + + // Explicit timestamp conversion tests + TestTimestampValue("next_day(to_timestamp('12-27-2008', 'MM-dd-yyyy'), 'moN')", + TimestampValue("2008-12-29 00:00:00", 19)); + TestTimestampValue("next_day(to_timestamp('2007-20-10 11:22', 'yyyy-dd-MM HH:mm'),\ + 'TUeSdaY')", TimestampValue("2007-10-23 11:22:00", 19)); + TestTimestampValue("next_day(to_timestamp('18-11-2070 09:12', 'dd-MM-yyyy HH:mm'),\ + 'WeDneSdaY')", TimestampValue("2070-11-19 09:12:00", 19)); + TestTimestampValue("next_day(to_timestamp('12-1900-05', 'dd-yyyy-MM'), 'tHurSday')", + TimestampValue("1900-05-17 00:00:00", 19)); + TestTimestampValue("next_day(to_timestamp('08-1987-21', 'MM-yyyy-dd'), 'FRIDAY')", + TimestampValue("1987-08-28 00:00:00", 19)); + TestTimestampValue("next_day(to_timestamp('02-04-2001', 'dd-MM-yyyy'), 'SAT')", + TimestampValue("2001-04-07 00:00:00", 19)); + TestTimestampValue("next_day(to_timestamp('1970-01-31 00:00:00',\ + 'yyyy-MM-dd HH:mm:ss'), 'SunDay')", TimestampValue("1970-02-01 00:00:00", 19)); + + // Invalid input: unacceptable date parameter + TestIsNull("next_day('12202010','Saturday')", TYPE_TIMESTAMP); + TestIsNull("next_day('2011 02 11','thu')", TYPE_TIMESTAMP); + TestIsNull("next_day('09-19-2012xyz','monDay')", TYPE_TIMESTAMP); + TestIsNull("next_day('000000000000000','wed')", TYPE_TIMESTAMP); + TestIsNull("next_day('hell world!','fRiDaY')", TYPE_TIMESTAMP); + TestIsNull("next_day('t1c7t0c9','sunDAY')", TYPE_TIMESTAMP); + } + // This macro adds a scoped trace to provide the line number of the caller upon failure. #define EXPECT_BETWEEN(start, value, end) { \ SCOPED_TRACE(""); \ @@ -4346,8 +4420,13 @@ TEST_F(ExprTest, TimestampFunctions) { TestIsNull("unix_timestamp('12/2/2015', 'MM/dd/yyyy')", TYPE_BIGINT); TestValue("unix_timestamp('12/31/2015', 'MM/d/yyyy')", TYPE_BIGINT, 1451520000); TestValue("unix_timestamp('12/31/2015', 'MM/dd/yyyy')", TYPE_BIGINT, 1451520000); + + // next_day udf test for IMPALA-2459 + TestNextDayFunction(); } + + TEST_F(ExprTest, ConditionalFunctions) { // If first param evaluates to true, should return second parameter, // false or NULL should return the third. diff --git a/be/src/exprs/timestamp-functions-ir.cc b/be/src/exprs/timestamp-functions-ir.cc index 08992d91d..05bc6ee6a 100644 --- a/be/src/exprs/timestamp-functions-ir.cc +++ b/be/src/exprs/timestamp-functions-ir.cc @@ -573,6 +573,38 @@ DoubleVal TimestampFunctions::MonthsBetween(FunctionContext* context, return DoubleVal(months_between); } +TimestampVal TimestampFunctions::NextDay(FunctionContext* context, + const TimestampVal& date, const StringVal& weekday) { + string weekday_str = string(reinterpret_cast(weekday.ptr), weekday.len); + transform(weekday_str.begin(), weekday_str.end(), weekday_str.begin(), tolower); + int day_idx = 0; + if (weekday_str == "sunday" || weekday_str == "sun") { + day_idx = 1; + } else if (weekday_str == "monday" || weekday_str == "mon") { + day_idx = 2; + } else if (weekday_str == "tuesday" || weekday_str == "tue") { + day_idx = 3; + } else if (weekday_str == "wednesday" || weekday_str == "wed") { + day_idx = 4; + } else if (weekday_str == "thursday" || weekday_str == "thu") { + day_idx = 5; + } else if (weekday_str == "friday" || weekday_str == "fri") { + day_idx = 6; + } else if (weekday_str == "saturday" || weekday_str == "sat") { + day_idx = 7; + } + DCHECK_GE(day_idx, 1); + DCHECK_LE(day_idx, 7); + + int delta_days = day_idx - DayOfWeek(context, date).val; + delta_days = delta_days <= 0 ? delta_days + 7 : delta_days; + DCHECK_GE(delta_days, 1); + DCHECK_LE(delta_days, 7); + + IntVal delta(delta_days); + return AddSub(context, date, delta); +} + IntVal TimestampFunctions::IntMonthsBetween(FunctionContext* context, const TimestampVal& ts1, const TimestampVal& ts2) { if (ts1.is_null || ts2.is_null) return IntVal::null(); diff --git a/be/src/exprs/timestamp-functions.h b/be/src/exprs/timestamp-functions.h index 23c0d100b..6738bd0ef 100644 --- a/be/src/exprs/timestamp-functions.h +++ b/be/src/exprs/timestamp-functions.h @@ -141,6 +141,17 @@ class TimestampFunctions { static DoubleVal MonthsBetween(FunctionContext* context, const TimestampVal& ts_val1, const TimestampVal& ts_val2); + /// Return the date of the weekday that follows a particular date. + /// The 'weekday' argument is a string literal indicating the day of the week. + /// Also this argument is case-insensitive. Available values are: + /// "Sunday"/"SUN", "Monday"/"MON", "Tuesday"/"TUE", + /// "Wednesday"/"WED", "Thursday"/"THU", "Friday"/"FRI", "Saturday"/"SAT". + /// For example, the first Saturday after Wednesday, 25 December 2013 + /// is on 28 December 2013. + /// select next_day('2013-12-25','Saturday') returns '2013-12-28' + static TimestampVal NextDay(FunctionContext* context, const TimestampVal& date, + const StringVal& weekday); + /// Add/sub functions on the timestamp. This handles three forms of adding/subtracting /// intervals to/from timestamps: /// 1) ADD/SUB_(, ), diff --git a/common/function-registry/impala_functions.py b/common/function-registry/impala_functions.py index 879ff8a77..86a60e060 100644 --- a/common/function-registry/impala_functions.py +++ b/common/function-registry/impala_functions.py @@ -106,6 +106,7 @@ visible_functions = [ '_ZN6impala11UdfBuiltins9VectorGetEPN10impala_udf15FunctionContextERKNS1_9BigIntValERKNS1_9StringValE'], # Timestamp functions + [['next_day'], 'TIMESTAMP', ['TIMESTAMP', 'STRING'], '_ZN6impala18TimestampFunctions7NextDayEPN10impala_udf15FunctionContextERKNS1_12TimestampValERKNS1_9StringValE'], [['unix_timestamp'], 'BIGINT', ['STRING'], '_ZN6impala18TimestampFunctions14UnixFromStringEPN10impala_udf15FunctionContextERKNS1_9StringValE'], [['year'], 'INT', ['TIMESTAMP'], '_ZN6impala18TimestampFunctions4YearEPN10impala_udf15FunctionContextERKNS1_12TimestampValE'], [['month'], 'INT', ['TIMESTAMP'], '_ZN6impala18TimestampFunctions5MonthEPN10impala_udf15FunctionContextERKNS1_12TimestampValE'],