| /** |
| * 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. |
| **/ |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <vector> |
| |
| #include "catalog/CatalogAttribute.hpp" |
| #include "catalog/CatalogTypedefs.hpp" |
| #include "expressions/scalar/Scalar.hpp" |
| #include "expressions/scalar/ScalarAttribute.hpp" |
| #include "expressions/window_aggregation/WindowAggregateFunction.hpp" |
| #include "expressions/window_aggregation/WindowAggregateFunctionFactory.hpp" |
| #include "expressions/window_aggregation/WindowAggregationHandle.hpp" |
| #include "expressions/window_aggregation/WindowAggregationID.hpp" |
| #include "storage/ValueAccessor.hpp" |
| #include "types/CharType.hpp" |
| #include "types/DateOperatorOverloads.hpp" |
| #include "types/DatetimeIntervalType.hpp" |
| #include "types/DoubleType.hpp" |
| #include "types/FloatType.hpp" |
| #include "types/IntType.hpp" |
| #include "types/IntervalLit.hpp" |
| #include "types/LongType.hpp" |
| #include "types/Type.hpp" |
| #include "types/TypeFactory.hpp" |
| #include "types/TypeID.hpp" |
| #include "types/TypedValue.hpp" |
| #include "types/VarCharType.hpp" |
| #include "types/YearMonthIntervalType.hpp" |
| #include "types/containers/ColumnVector.hpp" |
| #include "types/containers/ColumnVectorsValueAccessor.hpp" |
| |
| #include "gtest/gtest.h" |
| |
| namespace quickstep { |
| |
| namespace { |
| |
| constexpr int kNumTuples = 100; |
| constexpr int kNumTuplesPerPartition = 8; |
| constexpr int kNullInterval = 25; |
| constexpr int kNumPreceding = 2; |
| constexpr int kNumFollowing = 2; |
| constexpr int kPartitionKeyIndex = 0; |
| constexpr int kOrderKeyIndex = 1; |
| constexpr int kNumTuplesPerOrderKey = 2; |
| |
| } // namespace |
| |
| // Attribute value could be null if set true. |
| class WindowAggregationHandleAvgTest : public::testing::Test { |
| protected: |
| // Handle initialization. |
| WindowAggregationHandle* initializeHandle(const Type &argument_type, |
| const bool is_row = true, |
| const std::int64_t num_preceding = -1, |
| const std::int64_t num_following = 0) { |
| const WindowAggregateFunction &function = |
| WindowAggregateFunctionFactory::Get(WindowAggregationID::kAvg); |
| const Type &int_type = TypeFactory::GetType(kInt, false); |
| std::vector<std::unique_ptr<const Scalar>> partition_by_attributes; |
| std::vector<std::unique_ptr<const Scalar>> order_by_attributes; |
| partition_by_attributes.emplace_back( |
| new ScalarAttribute(CatalogAttribute(nullptr, "partition_key", int_type, kPartitionKeyIndex))); |
| order_by_attributes.emplace_back( |
| new ScalarAttribute(CatalogAttribute(nullptr, "order_key", int_type, kOrderKeyIndex))); |
| std::vector<const Type*> partition_key_types(1, &TypeFactory::GetType(kInt, false)); |
| |
| return function.createHandle(std::vector<const Type*>(1, &argument_type), |
| partition_by_attributes, |
| order_by_attributes, |
| is_row, |
| num_preceding, |
| num_following); |
| } |
| |
| // Test canApplyToTypes(). |
| static bool CanApplyToTypesTest(TypeID typeID) { |
| const Type &type = (typeID == kChar || typeID == kVarChar) ? |
| TypeFactory::GetType(typeID, static_cast<std::size_t>(10)) : |
| TypeFactory::GetType(typeID); |
| |
| return WindowAggregateFunctionFactory::Get(WindowAggregationID::kAvg).canApplyToTypes( |
| std::vector<const Type*>(1, &type)); |
| } |
| |
| // Test resultTypeForArgumentTypes(). |
| static bool ResultTypeForArgumentTypesTest(TypeID input_type_id, |
| TypeID output_type_id) { |
| const Type *result_type |
| = WindowAggregateFunctionFactory::Get(WindowAggregationID::kAvg).resultTypeForArgumentTypes( |
| std::vector<const Type*>(1, &TypeFactory::GetType(input_type_id))); |
| return (result_type->getTypeID() == output_type_id); |
| } |
| |
| template <typename CppType> |
| static void CheckAvgValues( |
| const std::vector<CppType*> &expected, |
| const ColumnVector *actual) { |
| EXPECT_TRUE(actual->isNative()); |
| const NativeColumnVector *native = static_cast<const NativeColumnVector*>(actual); |
| |
| EXPECT_EQ(expected.size(), native->size()); |
| for (std::size_t i = 0; i < expected.size(); ++i) { |
| if (expected[i] == nullptr) { |
| EXPECT_TRUE(native->getTypedValue(i).isNull()); |
| } else { |
| EXPECT_EQ(*expected[i], native->getTypedValue(i).getLiteral<CppType>()); |
| } |
| } |
| } |
| |
| // Static templated methods for set a meaningful value to data types. |
| template <typename CppType> |
| static void SetDataType(int value, CppType *data) { |
| *data = value; |
| } |
| |
| template <typename GenericType, typename OutputType = DoubleType> |
| void checkWindowAggregationAvgGeneric() { |
| // Create argument, partition key and cpptype vectors. |
| std::vector<typename GenericType::cpptype*> argument_cpp_vector; |
| argument_cpp_vector.reserve(kNumTuples); |
| ColumnVector *argument_type_vector = |
| createArgumentGeneric<GenericType>(&argument_cpp_vector); |
| NativeColumnVector *partition_key_vector = |
| new NativeColumnVector(IntType::InstanceNonNullable(), kNumTuples); |
| NativeColumnVector *order_key_vector = |
| new NativeColumnVector(IntType::InstanceNonNullable(), kNumTuples); |
| |
| for (int i = 0; i < kNumTuples; ++i) { |
| partition_key_vector->appendTypedValue(TypedValue(i / kNumTuplesPerPartition)); |
| order_key_vector->appendTypedValue(TypedValue(i / kNumTuplesPerOrderKey)); |
| } |
| |
| // Create tuple ValueAccessor. |
| ColumnVectorsValueAccessor *tuple_accessor = new ColumnVectorsValueAccessor(); |
| tuple_accessor->addColumn(partition_key_vector); |
| tuple_accessor->addColumn(order_key_vector); |
| tuple_accessor->addColumn(argument_type_vector); |
| |
| // Test UNBOUNDED PRECEDING AND CURRENT ROW. |
| checkAccumulate<GenericType, OutputType>(tuple_accessor, |
| argument_type_vector, |
| argument_cpp_vector); |
| // Test kNumPreceding PRECEDING AND kNumFollowing FOLLOWING. |
| checkSlidingWindow<GenericType, OutputType>(tuple_accessor, |
| argument_type_vector, |
| argument_cpp_vector); |
| } |
| |
| template <typename GenericType> |
| ColumnVector *createArgumentGeneric( |
| std::vector<typename GenericType::cpptype*> *argument_cpp_vector) { |
| const GenericType &type = GenericType::Instance(true); |
| NativeColumnVector *column = new NativeColumnVector(type, kNumTuples); |
| |
| for (int i = 0; i < kNumTuples; ++i) { |
| // Insert a NULL every kNullInterval tuples. |
| if (i % kNullInterval == 0) { |
| argument_cpp_vector->push_back(nullptr); |
| column->appendTypedValue(type.makeNullValue()); |
| continue; |
| } |
| |
| typename GenericType::cpptype *val = new typename GenericType::cpptype; |
| |
| if (type.getTypeID() == kInt || type.getTypeID() == kLong) { |
| SetDataType(i - 10, val); |
| } else { |
| SetDataType(static_cast<float>(i - 10) / 10, val); |
| } |
| |
| column->appendTypedValue(type.makeValue(val)); |
| argument_cpp_vector->push_back(val); |
| } |
| |
| return column; |
| } |
| |
| template <typename GenericType, typename OutputType> |
| void checkAccumulate(ColumnVectorsValueAccessor *tuple_accessor, |
| ColumnVector *argument_type_vector, |
| const std::vector<typename GenericType::cpptype*> &argument_cpp_vector) { |
| std::vector<ColumnVector*> arguments; |
| arguments.push_back(argument_type_vector); |
| |
| // Check ROWS mode. |
| WindowAggregationHandle *rows_handle = |
| initializeHandle(GenericType::Instance(true), |
| true /* is_row */, |
| -1 /* num_preceding: UNBOUNDED PRECEDING */, |
| 0 /* num_following: CURRENT ROW */); |
| ColumnVector *rows_result = |
| rows_handle->calculate(tuple_accessor, arguments); |
| |
| // Get the cpptype result. |
| std::vector<typename OutputType::cpptype*> rows_result_cpp_vector; |
| typename GenericType::cpptype rows_sum; |
| SetDataType(0, &rows_sum); |
| int rows_count = 0; |
| for (std::size_t i = 0; i < argument_cpp_vector.size(); ++i) { |
| // Start of new partition |
| if (i % kNumTuplesPerPartition == 0) { |
| SetDataType(0, &rows_sum); |
| rows_count = 0; |
| } |
| |
| typename GenericType::cpptype *value = argument_cpp_vector[i]; |
| if (value != nullptr) { |
| rows_sum += *value; |
| rows_count++; |
| } |
| |
| if (rows_count == 0) { |
| rows_result_cpp_vector.push_back(nullptr); |
| } else { |
| typename OutputType::cpptype *result_cpp_value = |
| new typename OutputType::cpptype; |
| *result_cpp_value = static_cast<typename OutputType::cpptype>(rows_sum) / rows_count; |
| rows_result_cpp_vector.push_back(result_cpp_value); |
| } |
| } |
| |
| CheckAvgValues(rows_result_cpp_vector, rows_result); |
| |
| // Check RANGE mode. |
| WindowAggregationHandle *range_handle = |
| initializeHandle(GenericType::Instance(true), |
| false /* is_row */, |
| -1 /* num_preceding: UNBOUNDED PRECEDING */, |
| 0 /* num_following: CURRENT ROW */); |
| ColumnVector *range_result = |
| range_handle->calculate(tuple_accessor, arguments); |
| |
| // Get the cpptype result. |
| std::vector<typename OutputType::cpptype*> range_result_cpp_vector; |
| typename GenericType::cpptype range_sum; |
| SetDataType(0, &range_sum); |
| int range_count = 0; |
| std::size_t current_tuple = 0; |
| while (current_tuple < kNumTuples) { |
| // Start of new partition |
| if (current_tuple % kNumTuplesPerPartition == 0) { |
| SetDataType(0, &range_sum); |
| range_count = 0; |
| } |
| |
| // We have to consider following tuples with the same order key value. |
| std::size_t next_tuple = current_tuple; |
| while (next_tuple < kNumTuples && |
| next_tuple / kNumTuplesPerPartition == current_tuple / kNumTuplesPerPartition && |
| next_tuple / kNumTuplesPerOrderKey == current_tuple / kNumTuplesPerOrderKey) { |
| typename GenericType::cpptype *value = argument_cpp_vector[next_tuple]; |
| if (value != nullptr) { |
| range_sum += *value; |
| range_count++; |
| } |
| |
| next_tuple++; |
| } |
| |
| // Calculate the result cpp value. |
| typename OutputType::cpptype *result_cpp_value = nullptr; |
| if (range_count != 0) { |
| result_cpp_value = new typename OutputType::cpptype; |
| *result_cpp_value = static_cast<typename OutputType::cpptype>(range_sum) / range_count; |
| } |
| |
| // Add the result values to the tuples with in the same order key value. |
| while (current_tuple != next_tuple) { |
| range_result_cpp_vector.push_back(result_cpp_value); |
| current_tuple++; |
| } |
| } |
| |
| CheckAvgValues(range_result_cpp_vector, range_result); |
| } |
| |
| template <typename GenericType, typename OutputType> |
| void checkSlidingWindow(ColumnVectorsValueAccessor *tuple_accessor, |
| ColumnVector *argument_type_vector, |
| const std::vector<typename GenericType::cpptype*> &argument_cpp_vector) { |
| std::vector<ColumnVector*> arguments; |
| arguments.push_back(argument_type_vector); |
| |
| // Check ROWS mode. |
| WindowAggregationHandle *rows_handle = |
| initializeHandle(GenericType::Instance(true), |
| true /* is_row */, |
| kNumPreceding, |
| kNumFollowing); |
| ColumnVector *rows_result = |
| rows_handle->calculate(tuple_accessor, arguments); |
| |
| // Get the cpptype result. |
| // For each value, calculate all surrounding values in the window. |
| std::vector<typename OutputType::cpptype*> rows_result_cpp_vector; |
| |
| for (std::size_t i = 0; i < argument_cpp_vector.size(); ++i) { |
| typename GenericType::cpptype sum; |
| SetDataType(0, &sum); |
| int count = 0; |
| |
| if (argument_cpp_vector[i] != nullptr) { |
| sum += *argument_cpp_vector[i]; |
| count++; |
| } |
| |
| for (std::size_t precede = 1; precede <= kNumPreceding; ++precede) { |
| // Not the same partition. |
| if (i / kNumTuplesPerPartition != (i - precede) / kNumTuplesPerPartition || |
| i < precede) { |
| break; |
| } |
| |
| if (argument_cpp_vector[i - precede] != nullptr) { |
| sum += *argument_cpp_vector[i - precede]; |
| count++; |
| } |
| } |
| |
| for (int follow = 1; follow <= kNumPreceding; ++follow) { |
| // Not the same partition. |
| if (i / kNumTuplesPerPartition != (i + follow) / kNumTuplesPerPartition || |
| i + follow >= kNumTuples) { |
| break; |
| } |
| |
| if (argument_cpp_vector[i + follow] != nullptr) { |
| sum += *argument_cpp_vector[i + follow]; |
| count++; |
| } |
| } |
| |
| if (count == 0) { |
| rows_result_cpp_vector.push_back(nullptr); |
| } else { |
| typename OutputType::cpptype *result_cpp_value = |
| new typename OutputType::cpptype; |
| *result_cpp_value = static_cast<typename OutputType::cpptype>(sum) / count; |
| rows_result_cpp_vector.push_back(result_cpp_value); |
| } |
| } |
| |
| CheckAvgValues(rows_result_cpp_vector, rows_result); |
| |
| // Check RANGE mode. |
| WindowAggregationHandle *range_handle = |
| initializeHandle(GenericType::Instance(true), |
| false /* is_row */, |
| kNumPreceding, |
| kNumFollowing); |
| ColumnVector *range_result = |
| range_handle->calculate(tuple_accessor, arguments); |
| |
| // Get the cpptype result. |
| // For each value, calculate all surrounding values in the window. |
| std::vector<typename OutputType::cpptype*> range_result_cpp_vector; |
| |
| for (std::size_t i = 0; i < argument_cpp_vector.size(); ++i) { |
| typename GenericType::cpptype sum; |
| SetDataType(0, &sum); |
| int count = 0; |
| |
| if (argument_cpp_vector[i] != nullptr) { |
| sum += *argument_cpp_vector[i]; |
| count++; |
| } |
| |
| int preceding_bound = i / kNumTuplesPerOrderKey - kNumPreceding; |
| for (std::size_t precede = 1; precede <= kNumTuples; ++precede) { |
| // Not in range or the same partition. |
| if (i / kNumTuplesPerPartition != (i - precede) / kNumTuplesPerPartition || |
| static_cast<int>((i - precede) / kNumTuplesPerOrderKey) < preceding_bound) { |
| break; |
| } |
| |
| if (argument_cpp_vector[i - precede] != nullptr) { |
| sum += *argument_cpp_vector[i - precede]; |
| count++; |
| } |
| } |
| |
| int following_bound = i / kNumTuplesPerOrderKey + kNumFollowing; |
| for (int follow = 1; follow <= kNumTuples; ++follow) { |
| // Not in range or the same partition. |
| if (i + follow >= kNumTuples || |
| i / kNumTuplesPerPartition != (i + follow) / kNumTuplesPerPartition || |
| static_cast<int>((i + follow) / kNumTuplesPerOrderKey) > following_bound) { |
| break; |
| } |
| |
| if (argument_cpp_vector[i + follow] != nullptr) { |
| sum += *argument_cpp_vector[i + follow]; |
| count++; |
| } |
| } |
| |
| if (count == 0) { |
| rows_result_cpp_vector.push_back(nullptr); |
| } else { |
| typename OutputType::cpptype *result_cpp_value = |
| new typename OutputType::cpptype; |
| *result_cpp_value = static_cast<typename OutputType::cpptype>(sum) / count; |
| range_result_cpp_vector.push_back(result_cpp_value); |
| } |
| } |
| |
| CheckAvgValues(range_result_cpp_vector, range_result); |
| } |
| }; |
| |
| template <> |
| void WindowAggregationHandleAvgTest::CheckAvgValues<double>( |
| const std::vector<double*> &expected, |
| const ColumnVector *actual) { |
| EXPECT_TRUE(actual->isNative()); |
| const NativeColumnVector *native = static_cast<const NativeColumnVector*>(actual); |
| |
| EXPECT_EQ(expected.size(), native->size()); |
| for (std::size_t i = 0; i < expected.size(); ++i) { |
| if (expected[i] == nullptr) { |
| EXPECT_TRUE(native->getTypedValue(i).isNull()); |
| } else { |
| EXPECT_EQ(*expected[i], native->getTypedValue(i).getLiteral<double>()); |
| } |
| } |
| } |
| |
| template <> |
| void WindowAggregationHandleAvgTest::SetDataType<DatetimeIntervalLit>( |
| int value, DatetimeIntervalLit *data) { |
| data->interval_ticks = value; |
| } |
| |
| template <> |
| void WindowAggregationHandleAvgTest::SetDataType<YearMonthIntervalLit>( |
| int value, YearMonthIntervalLit *data) { |
| data->months = value; |
| } |
| |
| typedef WindowAggregationHandleAvgTest WindowAggregationHandleAvgDeathTest; |
| |
| TEST_F(WindowAggregationHandleAvgTest, IntTypeTest) { |
| checkWindowAggregationAvgGeneric<IntType>(); |
| } |
| |
| TEST_F(WindowAggregationHandleAvgTest, LongTypeTest) { |
| checkWindowAggregationAvgGeneric<LongType>(); |
| } |
| |
| TEST_F(WindowAggregationHandleAvgTest, FloatTypeTest) { |
| checkWindowAggregationAvgGeneric<FloatType>(); |
| } |
| |
| TEST_F(WindowAggregationHandleAvgTest, DoubleTypeTest) { |
| checkWindowAggregationAvgGeneric<DoubleType>(); |
| } |
| |
| TEST_F(WindowAggregationHandleAvgTest, DatetimeIntervalTypeTest) { |
| checkWindowAggregationAvgGeneric<DatetimeIntervalType, DatetimeIntervalType>(); |
| } |
| |
| TEST_F(WindowAggregationHandleAvgTest, YearMonthIntervalTypeTest) { |
| checkWindowAggregationAvgGeneric<YearMonthIntervalType, YearMonthIntervalType>(); |
| } |
| |
| #ifdef QUICKSTEP_DEBUG |
| TEST_F(WindowAggregationHandleAvgDeathTest, CharTypeTest) { |
| const Type &type = CharType::Instance(true, 10); |
| EXPECT_DEATH(initializeHandle(type), ""); |
| } |
| |
| TEST_F(WindowAggregationHandleAvgDeathTest, VarTypeTest) { |
| const Type &type = VarCharType::Instance(true, 10); |
| EXPECT_DEATH(initializeHandle(type), ""); |
| } |
| #endif |
| |
| TEST_F(WindowAggregationHandleAvgDeathTest, canApplyToTypeTest) { |
| EXPECT_TRUE(CanApplyToTypesTest(kInt)); |
| EXPECT_TRUE(CanApplyToTypesTest(kLong)); |
| EXPECT_TRUE(CanApplyToTypesTest(kFloat)); |
| EXPECT_TRUE(CanApplyToTypesTest(kDouble)); |
| EXPECT_FALSE(CanApplyToTypesTest(kChar)); |
| EXPECT_FALSE(CanApplyToTypesTest(kVarChar)); |
| EXPECT_FALSE(CanApplyToTypesTest(kDatetime)); |
| EXPECT_TRUE(CanApplyToTypesTest(kDatetimeInterval)); |
| EXPECT_TRUE(CanApplyToTypesTest(kYearMonthInterval)); |
| } |
| |
| TEST_F(WindowAggregationHandleAvgDeathTest, ResultTypeForArgumentTypeTest) { |
| EXPECT_TRUE(ResultTypeForArgumentTypesTest(kInt, kDouble)); |
| EXPECT_TRUE(ResultTypeForArgumentTypesTest(kLong, kDouble)); |
| EXPECT_TRUE(ResultTypeForArgumentTypesTest(kFloat, kDouble)); |
| EXPECT_TRUE(ResultTypeForArgumentTypesTest(kDouble, kDouble)); |
| EXPECT_TRUE(ResultTypeForArgumentTypesTest(kDatetimeInterval, kDatetimeInterval)); |
| EXPECT_TRUE(ResultTypeForArgumentTypesTest(kYearMonthInterval, kYearMonthInterval)); |
| } |
| |
| } // namespace quickstep |