Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
116 changes: 115 additions & 1 deletion be/src/vec/functions/function_date_or_datetime_computation.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@

namespace doris::vectorized {
#include "common/compile_check_avoid_begin.h"

/// because all these functions(xxx_add/xxx_sub) defined in FE use Integer as the second value
/// so Int64 as delta is needed to support large values. For upstream(FunctionDateOrDateTimeComputation) we use Int64.

Expand Down Expand Up @@ -113,7 +114,6 @@ auto date_time_add(const typename PrimitiveTypeTraits<ArgType>::DataType::FieldT
std::make_shared<typename PrimitiveTypeTraits<IntervalPType>::DataType>()}; \
} \
}

ADD_TIME_FUNCTION_IMPL(AddMicrosecondsImpl, microseconds_add, MICROSECOND);
ADD_TIME_FUNCTION_IMPL(AddMillisecondsImpl, milliseconds_add, MILLISECOND);
ADD_TIME_FUNCTION_IMPL(AddSecondsImpl, seconds_add, SECOND);
Expand Down Expand Up @@ -1246,5 +1246,119 @@ class FunctionTime : public IFunction {
return Status::OK();
}
};

struct AddTimeImpl {
static constexpr auto name = "add_time";
static bool is_negative() { return false; }
};

struct SubTimeImpl {
static constexpr auto name = "sub_time";
static bool is_negative() { return true; }
};

template <PrimitiveType PType, typename Impl>
class FunctionAddTime : public IFunction {
public:
static constexpr auto name = Impl::name;
static constexpr PrimitiveType ReturnType = PType;
static constexpr PrimitiveType ArgType1 = PType;
static constexpr PrimitiveType ArgType2 = TYPE_TIMEV2;
using ColumnType1 = typename PrimitiveTypeTraits<PType>::ColumnType;
using ColumnType2 = typename PrimitiveTypeTraits<TYPE_TIMEV2>::ColumnType;
using InputType1 = typename PrimitiveTypeTraits<PType>::DataType::FieldType;
using InputType2 = typename PrimitiveTypeTraits<TYPE_TIMEV2>::DataType::FieldType;
using ReturnNativeType = InputType1;
using ReturnDataType = typename PrimitiveTypeTraits<PType>::DataType;

String get_name() const override { return name; }
size_t get_number_of_arguments() const override { return 2; }
DataTypes get_variadic_argument_types_impl() const override {
return {std::make_shared<typename PrimitiveTypeTraits<PType>::DataType>(),
std::make_shared<typename PrimitiveTypeTraits<TYPE_TIMEV2>::DataType>()};
}
DataTypePtr get_return_type_impl(const ColumnsWithTypeAndName& arguments) const override {
return std::make_shared<ReturnDataType>();
}

ReturnNativeType compute(const InputType1& arg1, const InputType2& arg2) const {
if constexpr (PType == TYPE_DATETIMEV2) {
DateV2Value<DateTimeV2ValueType> dtv1 =
binary_cast<InputType1, DateV2Value<DateTimeV2ValueType>>(arg1);
auto tv2 = static_cast<TimeValue::TimeType>(arg2);
TimeInterval interval(TimeUnit::MICROSECOND, tv2, Impl::is_negative());
bool out_range = dtv1.template date_add_interval<TimeUnit::MICROSECOND>(interval);
if (UNLIKELY(!out_range)) {
throw Exception(ErrorCode::INVALID_ARGUMENT,
"datetime value is out of range in function {}", name);
}
return binary_cast<DateV2Value<DateTimeV2ValueType>, ReturnNativeType>(dtv1);
} else if constexpr (PType == TYPE_TIMEV2) {
auto tv1 = static_cast<TimeValue::TimeType>(arg1);
auto tv2 = static_cast<TimeValue::TimeType>(arg2);
double res = TimeValue::limit_with_bound(Impl::is_negative() ? tv1 - tv2 : tv1 + tv2);
return res;
} else {
throw Exception(ErrorCode::FATAL_ERROR, "not support type for function {}", name);
}
}

static FunctionPtr create() { return std::make_shared<FunctionAddTime>(); }

Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments,
uint32_t result, size_t input_rows_count) const override {
DCHECK_EQ(arguments.size(), 2);
const auto& [left_col, left_const] =
unpack_if_const(block.get_by_position(arguments[0]).column);
const auto& [right_col, right_const] =
unpack_if_const(block.get_by_position(arguments[1]).column);
ColumnPtr nest_col1 = remove_nullable(left_col);
ColumnPtr nest_col2 = remove_nullable(right_col);
auto res = ColumnVector<ReturnType>::create(input_rows_count, 0);

if (left_const) {
execute_constant_vector(assert_cast<const ColumnType1&>(*nest_col1).get_element(0),
assert_cast<const ColumnType2&>(*nest_col2).get_data(),
res->get_data(), input_rows_count);
} else if (right_const) {
execute_vector_constant(assert_cast<const ColumnType1&>(*nest_col1).get_data(),
assert_cast<const ColumnType2&>(*nest_col2).get_element(0),
res->get_data(), input_rows_count);
} else {
execute_vector_vector(assert_cast<const ColumnType1&>(*nest_col1).get_data(),
assert_cast<const ColumnType2&>(*nest_col2).get_data(),
res->get_data(), input_rows_count);
}

block.replace_by_position(result, std::move(res));
return Status::OK();
}
void execute_vector_vector(const PaddedPODArray<InputType1>& left_col,
const PaddedPODArray<InputType2>& right_col,
PaddedPODArray<ReturnNativeType>& res_data,
size_t input_rows_count) const {
for (size_t i = 0; i < input_rows_count; ++i) {
res_data[i] = compute(left_col[i], right_col[i]);
}
}

void execute_vector_constant(const PaddedPODArray<InputType1>& left_col,
const InputType2 right_value,
PaddedPODArray<ReturnNativeType>& res_data,
size_t input_rows_count) const {
for (size_t i = 0; i < input_rows_count; ++i) {
res_data[i] = compute(left_col[i], right_value);
}
}

void execute_constant_vector(const InputType1 left_value,
const PaddedPODArray<InputType2>& right_col,
PaddedPODArray<ReturnNativeType>& res_data,
size_t input_rows_count) const {
for (size_t i = 0; i < input_rows_count; ++i) {
res_data[i] = compute(left_value, right_col[i]);
}
}
};
#include "common/compile_check_avoid_end.h"
} // namespace doris::vectorized
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@

namespace doris::vectorized {

using FunctionAddTimeV2 = FunctionAddTime<TYPE_DATETIMEV2, AddTimeImpl>;
using FunctionSubTimeV2 = FunctionAddTime<TYPE_DATETIMEV2, SubTimeImpl>;
using FunctionDateTimeAddTimeV2 = FunctionAddTime<TYPE_TIMEV2, AddTimeImpl>;
using FunctionDateTimeSubTimeV2 = FunctionAddTime<TYPE_TIMEV2, SubTimeImpl>;
using FunctionAddDaysV2 = FunctionDateOrDateTimeComputation<AddDaysImpl<TYPE_DATEV2>>;
using FunctionAddWeeksV2 = FunctionDateOrDateTimeComputation<AddWeeksImpl<TYPE_DATEV2>>;
using FunctionAddMonthsV2 = FunctionDateOrDateTimeComputation<AddMonthsImpl<TYPE_DATEV2>>;
Expand Down Expand Up @@ -157,6 +161,10 @@ void register_function_date_time_computation_v2(SimpleFunctionFactory& factory)
factory.register_function<FunctionToWeekTwoArgsV2>();
factory.register_function<FunctionDatetimeV2ToYearWeekTwoArgs>();
factory.register_function<FunctionDatetimeV2ToWeekTwoArgs>();
factory.register_function<FunctionAddTimeV2>();
factory.register_function<FunctionSubTimeV2>();
factory.register_function<FunctionDateTimeAddTimeV2>();
factory.register_function<FunctionDateTimeSubTimeV2>();
}

} // namespace doris::vectorized
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,9 @@ public boolean matchesType(Type t) {
if (isDatetimeV2() && scalarType.isDatetimeV2()) {
return true;
}
if (isTimeV2() && scalarType.isTimeV2()) {
return true;
}
if (isVariantType() && scalarType.isVariantType()) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.apache.doris.nereids.trees.expressions.functions.scalar.Abs;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Acos;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Acosh;
import org.apache.doris.nereids.trees.expressions.functions.scalar.AddTime;
import org.apache.doris.nereids.trees.expressions.functions.scalar.AesDecrypt;
import org.apache.doris.nereids.trees.expressions.functions.scalar.AesEncrypt;
import org.apache.doris.nereids.trees.expressions.functions.scalar.AppendTrailingCharIfAbsent;
Expand Down Expand Up @@ -460,6 +461,7 @@
import org.apache.doris.nereids.trees.expressions.functions.scalar.StructElement;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SubBitmap;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SubReplace;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SubTime;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Substring;
import org.apache.doris.nereids.trees.expressions.functions.scalar.SubstringIndex;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Tan;
Expand Down Expand Up @@ -542,6 +544,7 @@ public class BuiltinScalarFunctions implements FunctionHelper {
scalar(Abs.class, "abs"),
scalar(Acos.class, "acos"),
scalar(Acosh.class, "acosh"),
scalar(AddTime.class, "add_time"),
scalar(AesDecrypt.class, "aes_decrypt"),
scalar(AesEncrypt.class, "aes_encrypt"),
scalar(AppendTrailingCharIfAbsent.class, "append_trailing_char_if_absent"),
Expand Down Expand Up @@ -970,7 +973,8 @@ public class BuiltinScalarFunctions implements FunctionHelper {
scalar(SubBitmap.class, "sub_bitmap"),
scalar(SubReplace.class, "sub_replace"),
scalar(Substring.class, "substr", "substring", "mid"),
scalar(SubstringIndex.class, "substring_index"),
scalar(SubTime.class, "sub_time"),
scalar(SubstringIndex.class, "substring_index"),
scalar(Tan.class, "tan"),
scalar(Tanh.class, "tanh"),
scalar(Time.class, "time"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// 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.

package org.apache.doris.nereids.trees.expressions.functions;

import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.nereids.annotation.Developing;
import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
import org.apache.doris.nereids.trees.expressions.literal.TimeV2Literal;
import org.apache.doris.nereids.types.TimeV2Type;

/**
* use for literal in time arithmetic, such as time_add('12:34:56', INTERVAL 1 second).
* if the first argument is string like literal and could cast to legal time literal,
* then use TimeType signature rather than DatetimeV2 signature(usually in SIGNATURES).
* but won't add any signatures to SIGNATURES. normally there should be a TimeType signature in SIGNATURES.
*/
@Developing
public interface ComputeSignatureForTimeArithmetic extends ComputeSignature {

@Override
default FunctionSignature computeSignature(FunctionSignature signature) {
if (child(0) instanceof StringLikeLiteral) {
try {
String s = ((StringLikeLiteral) child(0)).getStringValue().trim();
if (isTimeFormat(s)) {
new TimeV2Literal(s); // check legality
TimeV2Type t1 = TimeV2Type.forTypeFromString(s);
TimeV2Type t2 = TimeV2Type.INSTANCE;
if (child(1) instanceof StringLikeLiteral) {
String s2 = ((StringLikeLiteral) child(1)).getStringValue().trim();
new TimeV2Literal(s2); // check legality
t2 = TimeV2Type.forTypeFromString(s2);
}
int maxScale = Math.max(t1.getScale(), t2.getScale());
TimeV2Type resultType = TimeV2Type.of(maxScale);
return FunctionSignature.ret(resultType).args(resultType, resultType);
}
} catch (Exception e) {
// string like literal cannot cast to a legal time literal
}
}
// Call the parent implementation for non-time formats
return ComputeSignature.super.computeSignature(signature);
}

/**
* Check if the string is in a valid time format.
*/
default boolean isTimeFormat(String s) {
if (s == null || s.isEmpty()) {
return false;
}

s = s.trim();
if (s.startsWith("+") || s.startsWith("-")) {
return true;
}

if (s.contains("-") || s.contains("/") || s.contains(" ") || s.contains("T")) {
return false;
}

if (s.contains(":")) {
return isColonTimeFormat(s);
} else {
return isNumericTimeFormat(s);
}
}

/**
* Check if the string is in HH:MM[:SS[.FFFFFF]] format.
*/
default boolean isColonTimeFormat(String s) {
String[] parts = s.split("\\.", 2);
String timePart = parts[0];

/*
* Split time part, first part is two length we define it is time type, such as
* 12:12:12
* datetime such as 2023:12:12 12:12:12 length is 19,time part length is <= 15
*/
String[] timeFields = timePart.split(":");
if ((timeFields[0].length() == 2 && timeFields[1].length() <= 15) || timeFields[0].length() == 3
|| timePart.length() < 8) {
return true;
}

return false;
}

/**
* Check if the string is in numeric format: continuous digits [ .FFFFFF ]
*/
default boolean isNumericTimeFormat(String s) {
String[] parts = s.split("\\.", 2);
String numberPart = parts[0];

int length = numberPart.length();
if (length <= 7) {
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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.

package org.apache.doris.nereids.trees.expressions.functions.scalar;

import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.functions.ComputeSignatureForTimeArithmetic;
import org.apache.doris.nereids.trees.expressions.functions.DateAddSubMonotonic;
import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.DateTimeV2Type;
import org.apache.doris.nereids.types.TimeV2Type;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

import java.util.List;

/**
* ScalarFunction 'add_time'.
*/
public class AddTime extends ScalarFunction implements BinaryExpression, ExplicitlyCastableSignature,
ComputeSignatureForTimeArithmetic, PropagateNullable, DateAddSubMonotonic {

private static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
FunctionSignature.ret(DateTimeV2Type.SYSTEM_DEFAULT).args(DateTimeV2Type.SYSTEM_DEFAULT,
TimeV2Type.INSTANCE),
FunctionSignature.ret(TimeV2Type.INSTANCE).args(TimeV2Type.INSTANCE, TimeV2Type.INSTANCE));

public AddTime(Expression arg0, Expression arg1) {
super("add_time", arg0, arg1);
}

/** constructor for withChildren and reuse signature */
private AddTime(ScalarFunctionParams functionParams) {
super(functionParams);
}

@Override
public AddTime withChildren(List<Expression> children) {
Preconditions.checkArgument(children.size() == 2);
return new AddTime(getFunctionParams(children));
}

@Override
public List<FunctionSignature> getSignatures() {
return SIGNATURES;
}

@Override
public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
return visitor.visitAddTime(this, context);
}

@Override
public Expression withConstantArgs(Expression literal) {
return new AddTime(literal, child(1));
}
}
Loading