#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace DB { namespace ErrorCodes { extern const int ILLEGAL_TYPE_OF_ARGUMENT; } template struct FunctionRunningDifferenceName; template <> struct FunctionRunningDifferenceName { static constexpr auto name = "runningDifference"; }; template <> struct FunctionRunningDifferenceName { static constexpr auto name = "runningDifferenceStartingWithFirstValue"; }; /** Calculate difference of consecutive values in columns. * So, result of function depends on partition of data to columnss and on order of data in columns. */ template class FunctionRunningDifferenceImpl : public IFunction { private: /// It is possible to track value from previous columns, to calculate continuously across all columnss. Not implemented. template static void process(const PaddedPODArray & src, PaddedPODArray & dst, const NullMap * null_map) { size_t size = src.size(); dst.resize(size); if (size == 0) return; /// It is possible to SIMD optimize this loop. By no need for that in practice. Src prev{}; bool has_prev_value = false; for (size_t i = 0; i < size; ++i) { if (null_map && (*null_map)[i]) { dst[i] = Dst{}; continue; } if (!has_prev_value) { dst[i] = is_first_line_zero ? 0 : src[i]; prev = src[i]; has_prev_value = true; } else { auto cur = src[i]; dst[i] = static_cast(cur) - prev; prev = cur; } } } /// Result type is same as result of subtraction of argument types. template using DstFieldType = typename NumberTraits::ResultOfSubtraction::Type; /// Call polymorphic lambda with tag argument of concrete field type of src_type. template void dispatchForSourceType(const IDataType & src_type, F && f) const { WhichDataType which(src_type); if (which.isUInt8()) f(UInt8()); else if (which.isUInt16()) f(UInt16()); else if (which.isUInt32()) f(UInt32()); else if (which.isUInt64()) f(UInt64()); else if (which.isInt8()) f(Int8()); else if (which.isInt16()) f(Int16()); else if (which.isInt32()) f(Int32()); else if (which.isInt64()) f(Int64()); else if (which.isFloat32()) f(Float32()); else if (which.isFloat64()) f(Float64()); else if (which.isDate()) f(DataTypeDate::FieldType()); else if (which.isDateTime()) f(DataTypeDateTime::FieldType()); else throw Exception("Argument for function " + getName() + " must have numeric type.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); } public: static constexpr auto name = FunctionRunningDifferenceName::name; static FunctionPtr create(const Context &) { return std::make_shared>(); } String getName() const override { return name; } bool isStateful() const override { return true; } size_t getNumberOfArguments() const override { return 1; } bool isDeterministic() const override { return false; } bool isDeterministicInScopeOfQuery() const override { return false; } bool useDefaultImplementationForNulls() const override { return false; } DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override { DataTypePtr res; dispatchForSourceType(*removeNullable(arguments[0]), [&](auto field_type_tag) { res = std::make_shared>>(); }); if (arguments[0]->isNullable()) res = makeNullable(res); return res; } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override { const auto & src = arguments.at(0); /// When column is constant, its difference is zero. if (isColumnConst(*src.column)) return result_type->createColumnConstWithDefaultValue(input_rows_count); auto res_column = removeNullable(result_type)->createColumn(); const auto * src_column = src.column.get(); ColumnPtr null_map_column = nullptr; const NullMap * null_map = nullptr; if (const auto * nullable_column = checkAndGetColumn(src_column)) { src_column = &nullable_column->getNestedColumn(); null_map_column = nullable_column->getNullMapColumnPtr(); null_map = &nullable_column->getNullMapData(); } dispatchForSourceType(*removeNullable(src.type), [&](auto field_type_tag) { using SrcFieldType = decltype(field_type_tag); process(assert_cast &>(*src_column).getData(), assert_cast> &>(*res_column).getData(), null_map); }); if (null_map_column) return ColumnNullable::create(std::move(res_column), null_map_column); else return res_column; } }; }