blob: 362f16bab5da4a8ce3b4588150bb5f1973233f3f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// The functions in this file are specifically not cross-compiled to IR because there
// is no signifcant performance benefit to be gained.
#include "exprs/udf-builtins.h"
#include <gutil/walltime.h>
#include "gen-cpp/Exprs_types.h"
#include "runtime/date-value.h"
#include "runtime/runtime-state.h"
#include "runtime/timestamp-value.h"
#include "udf/udf-internal.h"
#include "util/bit-util.h"
#include "common/names.h"
using boost::gregorian::date;
using boost::gregorian::date_duration;
using boost::posix_time::ptime;
using boost::posix_time::time_duration;
using boost::posix_time::milliseconds;
using boost::posix_time::microseconds;
using namespace impala;
using namespace strings;
// The units which can be used when Truncating a Timestamp
enum class TruncUnit {
UNIT_INVALID,
YEAR,
QUARTER,
MONTH,
WW,
W,
DAY,
DAY_OF_WEEK,
HOUR,
MINUTE,
// Below units are only used by DateTrunc
MILLENNIUM,
CENTURY,
DECADE,
WEEK,
SECOND,
MILLISECONDS,
MICROSECONDS,
};
// Put non-exported functions in anonymous namespace to encourage inlining.
namespace {
// Returns the most recent date, no later than orig_date, which is on week_day
// week_day: 0==Sunday, 1==Monday, ...
date GoBackToWeekday(const date& orig_date, int week_day) {
int current_week_day = orig_date.day_of_week();
int diff = current_week_day - week_day;
if (diff == 0) return orig_date;
if (diff > 0) {
// ex. Weds(3) shifts to Tues(2), so we go back 1 day
return orig_date - date_duration(diff);
}
// ex. Tues(2) shifts to Weds(3), so we go back 6 days
DCHECK_LT(diff, 0);
return orig_date - date_duration(7 + diff);
}
// Maps the user facing name of a unit to a TruncUnit used by DateTrunc function
// Returns the TruncUnit for the given string
TruncUnit StrToDateTruncUnit(FunctionContext* ctx, const StringVal& unit_str) {
StringVal unit = UdfBuiltins::Lower(ctx, unit_str);
if (UNLIKELY(unit.is_null)) return TruncUnit::UNIT_INVALID;
if (unit == "millennium") {
return TruncUnit::MILLENNIUM;
} else if (unit == "century") {
return TruncUnit::CENTURY;
} else if (unit == "decade") {
return TruncUnit::DECADE;
} else if (unit == "year") {
return TruncUnit::YEAR;
} else if (unit == "month") {
return TruncUnit::MONTH;
} else if (unit == "week") {
return TruncUnit::WEEK;
} else if (unit == "day") {
return TruncUnit::DAY;
} else if (unit == "hour") {
return TruncUnit::HOUR;
} else if (unit == "minute") {
return TruncUnit::MINUTE;
} else if (unit == "second") {
return TruncUnit::SECOND;
} else if (unit == "milliseconds") {
return TruncUnit::MILLISECONDS;
} else if (unit == "microseconds") {
return TruncUnit::MICROSECONDS;
} else {
return TruncUnit::UNIT_INVALID;
}
}
// Maps the user facing name of a unit to a TruncUnit
// Returns the TruncUnit for the given string
TruncUnit StrToTruncUnit(FunctionContext* ctx, const StringVal& unit_str) {
StringVal unit = UdfBuiltins::Lower(ctx, unit_str);
if (UNLIKELY(unit.is_null)) return TruncUnit::UNIT_INVALID;
if ((unit == "syyyy") || (unit == "yyyy") || (unit == "year") || (unit == "syear")
|| (unit == "yyy") || (unit == "yy") || (unit == "y")) {
return TruncUnit::YEAR;
} else if (unit == "q") {
return TruncUnit::QUARTER;
} else if ((unit == "month") || (unit == "mon") || (unit == "mm") || (unit == "rm")) {
return TruncUnit::MONTH;
} else if (unit == "ww") {
return TruncUnit::WW;
} else if (unit == "w") {
return TruncUnit::W;
} else if ((unit == "ddd") || (unit == "dd") || (unit == "j")) {
return TruncUnit::DAY;
} else if ((unit == "day") || (unit == "dy") || (unit == "d")) {
return TruncUnit::DAY_OF_WEEK;
} else if ((unit == "hh") || (unit == "hh12") || (unit == "hh24")) {
return TruncUnit::HOUR;
} else if (unit == "mi") {
return TruncUnit::MINUTE;
} else {
return TruncUnit::UNIT_INVALID;
}
}
// Truncate to first day of millennium
TimestampValue TruncMillennium(const date& orig_date) {
DCHECK_GT(orig_date.year(), 2000);
// First year of current millennium is 2001
date new_date((orig_date.year() - 1) / 1000 * 1000 + 1, 1, 1);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Truncate to first day of century
TimestampValue TruncCentury(const date& orig_date) {
DCHECK_GT(orig_date.year(), 1400);
// First year of current century is 2001
date new_date((orig_date.year() - 1) / 100 * 100 + 1, 1, 1);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Truncate to first day of decade
TimestampValue TruncDecade(const date& orig_date) {
// Decades start with years ending in '0'.
date new_date(orig_date.year() / 10 * 10, 1, 1);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Truncate to first day of year
TimestampValue TruncYear(const date& orig_date) {
date new_date(orig_date.year(), 1, 1);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Truncate to first day of quarter
TimestampValue TruncQuarter(const date& orig_date) {
int first_month_of_quarter = BitUtil::RoundDown(orig_date.month() - 1, 3) + 1;
date new_date(orig_date.year(), first_month_of_quarter, 1);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Truncate to first day of month
TimestampValue TruncMonth(const date& orig_date) {
date new_date(orig_date.year(), orig_date.month(), 1);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Truncate to first day of the week (monday)
TimestampValue TruncWeek(const date& orig_date) {
// ISO-8601 week starts on monday. go back to monday
date new_date = GoBackToWeekday(orig_date, 1);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Same day of the week as the first day of the year
TimestampValue TruncWW(const date& orig_date) {
date first_day_of_year(orig_date.year(), 1, 1);
int target_week_day = first_day_of_year.day_of_week();
date new_date = GoBackToWeekday(orig_date, target_week_day);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Same day of the week as the first day of the month
TimestampValue TruncW(const date& orig_date) {
date first_day_of_mon(orig_date.year(), orig_date.month(), 1);
date new_date = GoBackToWeekday(orig_date, first_day_of_mon.day_of_week());
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Truncate to midnight on the given date
TimestampValue TruncDay(const date& orig_date) {
time_duration new_time(0, 0, 0, 0);
return TimestampValue(orig_date, new_time);
}
// Date of the previous Monday
TimestampValue TruncDayOfWeek(const date& orig_date) {
date new_date = GoBackToWeekday(orig_date, 1);
time_duration new_time(0, 0, 0, 0);
return TimestampValue(new_date, new_time);
}
// Truncate minutes, seconds, and parts of seconds
TimestampValue TruncHour(const date& orig_date, const time_duration& orig_time) {
time_duration new_time(orig_time.hours(), 0, 0, 0);
return TimestampValue(orig_date, new_time);
}
// Truncate seconds and parts of seconds
TimestampValue TruncMinute(const date& orig_date, const time_duration& orig_time) {
time_duration new_time(orig_time.hours(), orig_time.minutes(), 0, 0);
return TimestampValue(orig_date, new_time);
}
// Truncate parts of seconds
TimestampValue TruncSecond(const date& orig_date, const time_duration& orig_time) {
time_duration new_time(orig_time.hours(), orig_time.minutes(), orig_time.seconds());
return TimestampValue(orig_date, new_time);
}
// Truncate parts of milliseconds
TimestampValue TruncMilliseconds(const date& orig_date, const time_duration& orig_time) {
time_duration new_time(orig_time.hours(), orig_time.minutes(), orig_time.seconds());
// Fractional seconds are nanoseconds because Boost is configured to use nanoseconds
// precision.
time_duration fraction = milliseconds(orig_time.fractional_seconds() / 1000000);
new_time = new_time + fraction;
return TimestampValue(orig_date, new_time);
}
// Truncate parts of microseconds
TimestampValue TruncMicroseconds(const date& orig_date, const time_duration& orig_time) {
time_duration new_time(orig_time.hours(), orig_time.minutes(), orig_time.seconds());
// Fractional seconds are nanoseconds because Boost is configured to use nanoseconds
// precision.
time_duration fraction = microseconds(orig_time.fractional_seconds() / 1000);
new_time = new_time + fraction;
return TimestampValue(orig_date, new_time);
}
// Used by both TRUNC and DATE_TRUNC functions to perform the truncation
TimestampVal DoTrunc(
const TimestampValue& ts, TruncUnit trunc_unit, FunctionContext* ctx) {
const date& orig_date = ts.date();
const time_duration& orig_time = ts.time();
TimestampValue ret;
TimestampVal ret_val;
// check for invalid or malformed timestamps
switch (trunc_unit) {
case TruncUnit::MILLENNIUM:
// for millenium <= 2000 year value goes to 1001 (outside the supported range)
if (orig_date.is_special()) return TimestampVal::null();
if (orig_date.year() <= 2000) return TimestampVal::null();
break;
case TruncUnit::CENTURY:
// for century <= 1400 year value goes to 1301 (outside the supported range)
if (orig_date.is_special()) return TimestampVal::null();
if (orig_date.year() <= 1400) return TimestampVal::null();
break;
case TruncUnit::WEEK:
// anything less than 1400-1-6 we have to move to year 1399
if (orig_date.is_special()) return TimestampVal::null();
if (orig_date < date(1400, 1, 6)) return TimestampVal::null();
break;
case TruncUnit::YEAR:
case TruncUnit::QUARTER:
case TruncUnit::MONTH:
case TruncUnit::WW:
case TruncUnit::W:
case TruncUnit::DAY:
case TruncUnit::DAY_OF_WEEK:
case TruncUnit::DECADE:
if (orig_date.is_special()) return TimestampVal::null();
break;
case TruncUnit::HOUR:
case TruncUnit::MINUTE:
case TruncUnit::SECOND:
case TruncUnit::MILLISECONDS:
case TruncUnit::MICROSECONDS:
if (orig_time.is_special()) return TimestampVal::null();
break;
case TruncUnit::UNIT_INVALID:
DCHECK(false);
}
switch(trunc_unit) {
case TruncUnit::YEAR:
ret = TruncYear(orig_date);
break;
case TruncUnit::QUARTER:
ret = TruncQuarter(orig_date);
break;
case TruncUnit::MONTH:
ret = TruncMonth(orig_date);
break;
case TruncUnit::WW:
ret = TruncWW(orig_date);
break;
case TruncUnit::W:
ret = TruncW(orig_date);
break;
case TruncUnit::DAY:
ret = TruncDay(orig_date);
break;
case TruncUnit::DAY_OF_WEEK:
ret = TruncDayOfWeek(orig_date);
break;
case TruncUnit::HOUR:
ret = TruncHour(orig_date, orig_time);
break;
case TruncUnit::MINUTE:
ret = TruncMinute(orig_date, orig_time);
break;
case TruncUnit::MILLENNIUM:
ret = TruncMillennium(orig_date);
break;
case TruncUnit::CENTURY:
ret = TruncCentury(orig_date);
break;
case TruncUnit::DECADE:
ret = TruncDecade(orig_date);
break;
case TruncUnit::WEEK:
ret = TruncWeek(orig_date);
break;
case TruncUnit::SECOND:
ret = TruncSecond(orig_date, orig_time);
break;
case TruncUnit::MILLISECONDS:
ret = TruncMilliseconds(orig_date, orig_time);
break;
case TruncUnit::MICROSECONDS:
ret = TruncMicroseconds(orig_date, orig_time);
break;
default:
// internal error: implies StrToTruncUnit out of sync with this switch
ctx->SetError("truncate unit not supported");
return TimestampVal::null();
}
ret.ToTimestampVal(&ret_val);
return ret_val;
}
// Returns the most recent date, no later than 'orig_date', which is on 'week_day'
// 'week_day' is in [0, 6]; 0 = Monday, 6 = Sunday.
DateValue GoBackToWeekday(const DateValue& orig_date, int week_day) {
DCHECK(orig_date.IsValid());
DCHECK(week_day >= 0 && week_day <= 6);
// Week days are in [0, 6]; 0 = Monday, 6 = Sunday.
int current_week_day = orig_date.WeekDay();
DCHECK(current_week_day >= 0 && current_week_day <= 6);
if (current_week_day == week_day) {
return orig_date;
} else if (current_week_day > week_day) {
return orig_date.AddDays(week_day - current_week_day);
} else {
return orig_date.AddDays(week_day - current_week_day - 7);
}
}
// Used by both TRUNC and DATE_TRUNC functions to perform the truncation
DateVal DoTrunc(const DateValue& date, TruncUnit trunc_unit, FunctionContext* ctx) {
if (!date.IsValid()) return DateVal::null();
DCHECK(trunc_unit != TruncUnit::UNIT_INVALID
&& trunc_unit != TruncUnit::MICROSECONDS
&& trunc_unit != TruncUnit::HOUR
&& trunc_unit != TruncUnit::MINUTE
&& trunc_unit != TruncUnit::SECOND
&& trunc_unit != TruncUnit::MILLISECONDS);
DateValue ret;
switch(trunc_unit) {
case TruncUnit::YEAR: {
int year;
discard_result(date.ToYear(&year));
ret = DateValue(year, 1, 1);
break;
}
case TruncUnit::QUARTER: {
int year, month, day;
discard_result(date.ToYearMonthDay(&year, &month, &day));
ret = DateValue(year, BitUtil::RoundDown(month - 1, 3) + 1, 1);
break;
}
case TruncUnit::MONTH: {
int year, month, day;
discard_result(date.ToYearMonthDay(&year, &month, &day));
ret = DateValue(year, month, 1);
break;
}
case TruncUnit::DAY: {
ret = date;
break;
}
case TruncUnit::WW: {
int year;
discard_result(date.ToYear(&year));
ret = GoBackToWeekday(date, DateValue(year, 1, 1).WeekDay());
break;
}
case TruncUnit::W: {
int year, month, day;
discard_result(date.ToYearMonthDay(&year, &month, &day));
ret = GoBackToWeekday(date, DateValue(year, month, 1).WeekDay());
break;
}
case TruncUnit::DAY_OF_WEEK: {
// Date of the previous Monday
ret = GoBackToWeekday(date, 0);
break;
}
case TruncUnit::WEEK: {
// ISO-8601 week starts on monday. go back to monday
ret = GoBackToWeekday(date, 0);
break;
}
case TruncUnit::MILLENNIUM: {
int year;
discard_result(date.ToYear(&year));
if (year <= 0) return DateVal::null();
// First year of current millennium is 2001
ret = DateValue((year - 1) / 1000 * 1000 + 1, 1, 1);
break;
}
case TruncUnit::CENTURY: {
int year;
discard_result(date.ToYear(&year));
if (year <= 0) return DateVal::null();
// First year of current century is 2001
ret = DateValue((year - 1) / 100 * 100 + 1, 1, 1);
break;
}
case TruncUnit::DECADE: {
int year;
// Decades start with years ending in '0'.
discard_result(date.ToYear(&year));
ret = DateValue(year / 10 * 10, 1, 1);
break;
}
default:
// internal error: implies StrToTruncUnit out of sync with this switch
ctx->SetError("truncate unit not supported");
return DateVal::null();
}
return ret.ToDateVal();
}
// Maps the user facing name of a unit to a TExtractField
// Returns the TExtractField for the given unit
TExtractField::type StrToExtractField(FunctionContext* ctx,
const StringVal& unit_str) {
StringVal unit = UdfBuiltins::Lower(ctx, unit_str);
if (UNLIKELY(unit.is_null)) return TExtractField::INVALID_FIELD;
if (unit == "year") return TExtractField::YEAR;
if (unit == "quarter") return TExtractField::QUARTER;
if (unit == "month") return TExtractField::MONTH;
if (unit == "day") return TExtractField::DAY;
if (unit == "hour") return TExtractField::HOUR;
if (unit == "minute") return TExtractField::MINUTE;
if (unit == "second") return TExtractField::SECOND;
if (unit == "millisecond") return TExtractField::MILLISECOND;
if (unit == "epoch") return TExtractField::EPOCH;
return TExtractField::INVALID_FIELD;
}
static int64_t ExtractMillisecond(const time_duration& time) {
// Fractional seconds are nanoseconds because Boost is configured
// to use nanoseconds precision
return time.fractional_seconds() / (NANOS_PER_MICRO * MICROS_PER_MILLI)
+ time.seconds() * MILLIS_PER_SEC;
}
// Used by both EXTRACT and DATE_PART functions to perform field extraction.
BigIntVal DoExtract(const TimestampValue& tv, TExtractField::type field,
FunctionContext* ctx) {
switch (field) {
case TExtractField::YEAR:
case TExtractField::QUARTER:
case TExtractField::MONTH:
case TExtractField::DAY:
if (!tv.HasDate()) return BigIntVal::null();
break;
case TExtractField::HOUR:
case TExtractField::MINUTE:
case TExtractField::SECOND:
case TExtractField::MILLISECOND:
if (!tv.HasTime()) return BigIntVal::null();
break;
case TExtractField::EPOCH:
if (!tv.HasDateAndTime()) return BigIntVal::null();
break;
case TExtractField::INVALID_FIELD:
DCHECK(false);
}
const date& orig_date = tv.date();
const time_duration& time = tv.time();
switch (field) {
case TExtractField::YEAR: {
return BigIntVal(orig_date.year());
}
case TExtractField::QUARTER: {
int m = orig_date.month();
return BigIntVal((m - 1) / 3 + 1);
}
case TExtractField::MONTH: {
return BigIntVal(orig_date.month());
}
case TExtractField::DAY: {
return BigIntVal(orig_date.day());
}
case TExtractField::HOUR: {
return BigIntVal(time.hours());
}
case TExtractField::MINUTE: {
return BigIntVal(time.minutes());
}
case TExtractField::SECOND: {
return BigIntVal(time.seconds());
}
case TExtractField::MILLISECOND: {
return BigIntVal(ExtractMillisecond(time));
}
case TExtractField::EPOCH: {
ptime epoch_date(date(1970, 1, 1), time_duration(0, 0, 0));
ptime cur_date(orig_date, time);
time_duration diff = cur_date - epoch_date;
return BigIntVal(diff.total_seconds());
}
default: {
// internal error: implies StrToExtractField out of sync with this switch
ctx->SetError("extract unit not supported");
return BigIntVal::null();
}
}
}
// Used by both EXTRACT and DATE_PART functions to perform field extraction.
BigIntVal DoExtract(const DateValue& dv, TExtractField::type field,
FunctionContext* ctx) {
if (!dv.IsValid()) return BigIntVal::null();
DCHECK(field != TExtractField::INVALID_FIELD
&& field != TExtractField::HOUR
&& field != TExtractField::MINUTE
&& field != TExtractField::SECOND
&& field != TExtractField::MILLISECOND
&& field != TExtractField::EPOCH);
switch (field) {
case TExtractField::YEAR: {
int year;
discard_result(dv.ToYear(&year));
return BigIntVal(year);
}
case TExtractField::QUARTER: {
int year, month, day;
discard_result(dv.ToYearMonthDay(&year, &month, &day));
return BigIntVal((month - 1) / 3 + 1);
}
case TExtractField::MONTH: {
int year, month, day;
discard_result(dv.ToYearMonthDay(&year, &month, &day));
return BigIntVal(month);
}
case TExtractField::DAY: {
int year, month, day;
discard_result(dv.ToYearMonthDay(&year, &month, &day));
return BigIntVal(day);
}
default: {
// internal error: implies StrToExtractField out of sync with this switch
ctx->SetError("extract unit not supported");
return BigIntVal::null();
}
}
}
inline TimestampValue FromVal(const TimestampVal& val) {
return TimestampValue::FromTimestampVal(val);
}
inline DateValue FromVal(const DateVal& val) {
return DateValue::FromDateVal(val);
}
inline bool IsTimeOfDayUnit(TruncUnit unit) {
return (unit == TruncUnit::HOUR
|| unit == TruncUnit::MINUTE
|| unit == TruncUnit::SECOND
|| unit == TruncUnit::MILLISECONDS
|| unit == TruncUnit::MICROSECONDS);
}
inline bool IsTimeOfDayUnit(TExtractField::type unit) {
return (unit == TExtractField::HOUR
|| unit == TExtractField::MINUTE
|| unit == TExtractField::SECOND
|| unit == TExtractField::MILLISECOND
|| unit == TExtractField::EPOCH);
}
inline bool IsInvalidUnit(TruncUnit unit) {
return (unit == TruncUnit::UNIT_INVALID);
}
inline bool IsInvalidUnit(TExtractField::type unit) {
return (unit == TExtractField::INVALID_FIELD);
}
/// Used for TRUNC/DATE_TRUNC/EXTRACT/DATE_PART built-in functions.
/// ALLOW_TIME_OF_DAY_UNIT: true iff the built-in function call accepts time-of-day units.
/// UdfType: udf type the built-in function works with.
/// InternalType: Impla's internal type that corresponds to UdfType.
/// ReturnUdfType: The built-in function's return type.
/// UnitType: type to represent unit values.
/// to_unit: function to parse unit strings.
/// do_func: function to implement the built-in function.
/// func_descr: description of the built-in function.
/// unit_descr: description of the unit parameter.
template <
bool ALLOW_TIME_OF_DAY_UNIT,
typename UdfType,
typename InternalType,
typename ReturnUdfType,
typename UnitType,
UnitType to_unit(FunctionContext*, const StringVal&),
ReturnUdfType do_func(const InternalType&, UnitType, FunctionContext*)>
ReturnUdfType ExtractTruncFuncTempl(FunctionContext* ctx, const UdfType& val,
const StringVal& unit_str, const string& func_descr, const string& unit_descr) {
if (val.is_null) return ReturnUdfType::null();
// resolve 'unit' using the prepared state if possible, o.w. parse now
// ExtractTruncFuncPrepareTempl() can only parse unit if user passes it as a string
// literal
// TODO: it would be nice to resolve the branch before codegen so we can optimise
// this better.
UnitType unit;
void* state = ctx->GetFunctionState(FunctionContext::THREAD_LOCAL);
if (state != NULL) {
unit = *reinterpret_cast<UnitType*>(state);
} else if (unit_str.is_null) {
ctx->SetError(Substitute("Invalid $0 $1: NULL", func_descr, unit_descr).c_str());
return ReturnUdfType::null();
} else {
unit = to_unit(ctx, unit_str);
if (!ALLOW_TIME_OF_DAY_UNIT && IsTimeOfDayUnit(unit)) {
string string_unit(reinterpret_cast<char*>(unit_str.ptr), unit_str.len);
ctx->SetError(Substitute(
"Unsupported $0 $1: $2", func_descr, unit_descr, string_unit).c_str());
return ReturnUdfType::null();
} else if (IsInvalidUnit(unit)) {
string string_unit(reinterpret_cast<char*>(unit_str.ptr), unit_str.len);
ctx->SetError(Substitute(
"Invalid $0 $1: $2", func_descr, unit_descr, string_unit).c_str());
return ReturnUdfType::null();
}
}
return do_func(FromVal(val), unit, ctx);
}
/// Does the preparation for TRUNC/DATE_TRUNC/EXTRACT/DATE_PART built-in functions.
/// ALLOW_TIME_OF_DAY_UNIT: true iff the built-in function call accepts time-of-day units.
/// UNIT_IDX: indicates which parameter of the function call is the unit parameter.
/// UnitType: type to represent unit values.
/// to_unit: function to parse unit strings.
/// func_descr: description of the built-in function.
/// unit_descr: description of the unit parameter.
template <
bool ALLOW_TIME_OF_DAY_UNIT,
int UNIT_IDX,
typename UnitType,
UnitType to_unit(FunctionContext*, const StringVal&)>
void ExtractTruncFuncPrepareTempl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope,
const string& func_descr, const string& unit_descr) {
// Parse the unit up front if we can, otherwise do it on the fly in trunc_templ()
if (ctx->IsArgConstant(UNIT_IDX)) {
StringVal* unit_str = reinterpret_cast<StringVal*>(ctx->GetConstantArg(UNIT_IDX));
if (unit_str == nullptr || unit_str->is_null) {
ctx->SetError(Substitute("Invalid $0 $1: NULL", func_descr, unit_descr).c_str());
} else {
UnitType unit = to_unit(ctx, *unit_str);
if (!ALLOW_TIME_OF_DAY_UNIT && IsTimeOfDayUnit(unit)) {
string string_unit(reinterpret_cast<char*>(unit_str->ptr), unit_str->len);
ctx->SetError(Substitute(
"Unsupported $0 $1: $2", func_descr, unit_descr, string_unit).c_str());
} else if (IsInvalidUnit(unit)) {
string string_unit(reinterpret_cast<char*>(unit_str->ptr), unit_str->len);
ctx->SetError(Substitute(
"Invalid $0 $1: $2", func_descr, unit_descr, string_unit).c_str());
} else {
UnitType* state = ctx->Allocate<UnitType>();
RETURN_IF_NULL(ctx, state);
*state = unit;
ctx->SetFunctionState(scope, state);
}
}
}
}
}
void UdfBuiltins::TruncForTimestampPrepareImpl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope) {
return ExtractTruncFuncPrepareTempl<true,
1,
TruncUnit,
StrToTruncUnit>(ctx, scope, "Truncate", "Unit");
}
TimestampVal UdfBuiltins::TruncForTimestampImpl(FunctionContext* ctx,
const TimestampVal& tv, const StringVal &unit_str) {
return ExtractTruncFuncTempl<true,
TimestampVal,
TimestampValue,
TimestampVal,
TruncUnit,
StrToTruncUnit,
DoTrunc>(ctx, tv, unit_str, "Truncate", "Unit");
}
void UdfBuiltins::TruncForDatePrepareImpl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope) {
return ExtractTruncFuncPrepareTempl<false,
1,
TruncUnit,
StrToTruncUnit>(ctx, scope, "Truncate", "Unit");
}
DateVal UdfBuiltins::TruncForDateImpl(FunctionContext* ctx, const DateVal& dv,
const StringVal &unit_str) {
return ExtractTruncFuncTempl<false,
DateVal,
DateValue,
DateVal,
TruncUnit,
StrToTruncUnit,
DoTrunc>(ctx, dv, unit_str, "Truncate", "Unit");
}
void UdfBuiltins::DateTruncForTimestampPrepareImpl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope) {
return ExtractTruncFuncPrepareTempl<true,
0,
TruncUnit,
StrToDateTruncUnit>(ctx, scope, "Date Truncate", "Unit");
}
TimestampVal UdfBuiltins::DateTruncForTimestampImpl(FunctionContext* ctx,
const StringVal &unit_str, const TimestampVal& tv) {
return ExtractTruncFuncTempl<true,
TimestampVal,
TimestampValue,
TimestampVal,
TruncUnit,
StrToDateTruncUnit,
DoTrunc>(ctx, tv, unit_str, "Date Truncate", "Unit");
}
void UdfBuiltins::DateTruncForDatePrepareImpl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope) {
return ExtractTruncFuncPrepareTempl<false,
0,
TruncUnit,
StrToDateTruncUnit>(ctx, scope, "Date Truncate", "Unit");
}
DateVal UdfBuiltins::DateTruncForDateImpl(FunctionContext* ctx, const StringVal &unit_str,
const DateVal& dv) {
return ExtractTruncFuncTempl<false,
DateVal,
DateValue,
DateVal,
TruncUnit,
StrToDateTruncUnit,
DoTrunc>(ctx, dv, unit_str, "Date Truncate", "Unit");
}
void UdfBuiltins::ExtractForTimestampPrepareImpl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope) {
return ExtractTruncFuncPrepareTempl<true,
1,
TExtractField::type,
StrToExtractField>(ctx, scope, "Extract", "Field");
}
BigIntVal UdfBuiltins::ExtractForTimestampImpl(FunctionContext* ctx,
const TimestampVal& tv, const StringVal& unit_str) {
return ExtractTruncFuncTempl<true,
TimestampVal,
TimestampValue,
BigIntVal,
TExtractField::type,
StrToExtractField,
DoExtract>(ctx, tv, unit_str, "Extract", "Field");
}
void UdfBuiltins::ExtractForDatePrepareImpl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope) {
return ExtractTruncFuncPrepareTempl<false,
1,
TExtractField::type,
StrToExtractField>(ctx, scope, "Extract", "Field");
}
BigIntVal UdfBuiltins::ExtractForDateImpl(FunctionContext* ctx, const DateVal& dv,
const StringVal& unit_str) {
return ExtractTruncFuncTempl<false,
DateVal,
DateValue,
BigIntVal,
TExtractField::type,
StrToExtractField,
DoExtract>(ctx, dv, unit_str, "Extract", "Field");
}
void UdfBuiltins::DatePartForTimestampPrepareImpl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope) {
return ExtractTruncFuncPrepareTempl<true,
0,
TExtractField::type,
StrToExtractField>(ctx, scope, "Date Part", "Field");
}
BigIntVal UdfBuiltins::DatePartForTimestampImpl(FunctionContext* ctx,
const StringVal& unit_str, const TimestampVal& tv) {
return ExtractTruncFuncTempl<true,
TimestampVal,
TimestampValue,
BigIntVal,
TExtractField::type,
StrToExtractField,
DoExtract>(ctx, tv, unit_str, "Date Part", "Field");
}
void UdfBuiltins::DatePartForDatePrepareImpl(FunctionContext* ctx,
FunctionContext::FunctionStateScope scope) {
return ExtractTruncFuncPrepareTempl<false,
0,
TExtractField::type,
StrToExtractField>(ctx, scope, "Date Part", "Field");
}
BigIntVal UdfBuiltins::DatePartForDateImpl(FunctionContext* ctx,
const StringVal& unit_str, const DateVal& dv) {
return ExtractTruncFuncTempl<false,
DateVal,
DateValue,
BigIntVal,
TExtractField::type,
StrToExtractField,
DoExtract>(ctx, dv, unit_str, "Date Part", "Field");
}