#include <gtest/gtest.h>
#include <iostream>
#include <nlohmann/json.hpp>
#include <test/util/test_util.h>
#include "runtime/taskmanager/OmniRuntimeEnvironment.h"
#include "table/runtime/operators/window/AggregateWindowOperator.h"
#include "runtime/operators/window/assigners/SessionWindowAssigner.h"
#include "runtime/operators/window/TimeWindow.h"
#include "core/graph/OperatorConfig.h"
#include "test/core/operators/OutputTest.h"
#include "streaming/api/operators/StreamOperatorFactory.h"
#include "table/utils/TimeWindowUtil.h"
using json = nlohmann::json;
std::string nexmarkQ12Description = R"DELIM({
"partition": {
"partitionName": "none",
"channelNumber": 1
},
"operators": [{
"output": {
"kind": "Row",
"type": [{
"kind": "logical",
"isNull": true,
"type": "BIGINT"
},
{
"kind": "logical",
"isNull": true,
"type": "BIGINT"
}]
},
"inputs": [{
"kind": "Row",
"type": [{
"kind": "logical",
"isNull": true,
"type": "BIGINT"
},
{
"kind": "logical",
"isNull": true,
"precision": 3,
"type": "TIMESTAMP",
"timestampKind": 1
}]
}],
"name": "GroupWindowAggregate(groupBy=[bidder], window=[SessionGroupWindow('w$, dateTime, 10000)], properties=[w$start, w$end, w$rowtime, w$proctime], select=[bidder, COUNT(*) AS bid_count, start('w$) AS w$start, end('w$) AS w$end, rowtime('w$) AS w$rowtime, proctime('w$) AS w$proctime])",
"description": {
"originDescription": null,
"inputTypes": ["BIGINT", "TIMESTAMP_WITHOUT_TIME_ZONE(3)"],
"outputTypes": ["BIGINT", "BIGINT", "TIMESTAMP_WITHOUT_TIME_ZONE(3)", "TIMESTAMP_WITHOUT_TIME_ZONE(3)", "TIMESTAMP_WITHOUT_TIME_ZONE(3)", "TIMESTAMP_WITH_LOCAL_TIME_ZONE"],
"windowPropertyTypes": ["TIMESTAMP(3) NOT NULL", "TIMESTAMP(3) NOT NULL", "TIMESTAMP(3) *ROWTIME*", "TIMESTAMP_LTZ(3) *PROCTIME*"],
"grouping": [0],
"aggInfoList": {
"aggregateCalls": [{
"name": "COUNT()",
"aggregationFunction": "Count1AggFunction",
"argIndexes": [],
"consumeRetraction": "false",
"filterArg": -1
}],
"AccTypes": ["BIGINT"],
"aggValueTypes": ["BIGINT"],
"indexOfCountStar": -1
},
"generateUpdateBefore": false,
"allowedLateness": 0,
"windowType": "SessionGroupWindow('w$, dateTime, 10000)",
"countType": "time",
"timeType": "event",
"windowSize": 10000,
"inputTimeFieldIndex": 1
},
"id": "org.apache.flink.table.runtime.operators.window.AggregateWindowOperator"
}]
})DELIM";
omnistream::VectorBatch* Q12VectorBatchInput()
{
auto* vbatch = new omnistream::VectorBatch(5);
std::vector<int64_t> bidder = {1, 1, 2, 1, 2};
std::vector<int64_t> timeStamps = {1742625600000, 1742625605000, 1742625600000, 1742625612000, 1742625800000};
vbatch->Append(omniruntime::TestUtil::CreateVector<int64_t>(5, bidder.data()));
vbatch->Append(omniruntime::TestUtil::CreateVector<int64_t>(5, timeStamps.data()));
vbatch->setRowKind(0, RowKind::INSERT);
vbatch->setRowKind(1, RowKind::INSERT);
vbatch->setRowKind(2, RowKind::INSERT);
vbatch->setRowKind(3, RowKind::INSERT);
vbatch->setRowKind(4, RowKind::INSERT);
return vbatch;
}
TEST(AggregateWindowOperatorTest, WindowStateTest)
{
std::string uniqueName = "org.apache.flink.table.runtime.operators.window.AggregateWindowOperator";
json parsedJson = json::parse(nexmarkQ12Description);
omnistream::OperatorConfig opConfig(
uniqueName,
"LocalWindowAgg_By_Simple",
parsedJson["operators"][0]["inputTypes"],
parsedJson["operators"][0]["outputTypes"],
parsedJson["operators"][0]["description"]);
auto* output = new BatchOutputTest();
auto* windowAggOperator = dynamic_cast<AggregateWindowOperator<std::shared_ptr<RowData>, TimeWindow>*>(
omnistream::StreamOperatorFactory::createOperatorAndCollector(opConfig, output));
auto env2 = new omnistream::RuntimeEnvironmentV2();
auto taskInfo = new TaskInformationPOD();
taskInfo->setStateBackend("HashMapStateBackend");
{
auto configPOD = taskInfo->getStreamConfigPOD();
auto operatorDesc = configPOD.getOperatorDescription();
operatorDesc.setOperatorId("deadbeefdeadbeefdeadbeefdeadbeef");
configPOD.setOperatorDescription(operatorDesc);
taskInfo->setStreamConfigPOD(configPOD);
}
env2->SetTaskStateManager(std::make_shared<omnistream::TaskStateManager>());
env2->setTaskConfiguration(*taskInfo);
StreamTaskStateInitializerImpl* initializer = new StreamTaskStateInitializerImpl(env2);
windowAggOperator->initializeState(initializer, new LongSerializer());
windowAggOperator->open();
auto bidIdRow = std::shared_ptr<BinaryRowData>(BinaryRowData::createBinaryRowDataWithMem(1));
bidIdRow->setLong(0, 10);
TimeWindow timeWindow(1742625620000, 1742625630000);
windowAggOperator->setCurrentKey(bidIdRow);
windowAggOperator->windowState->setCurrentNamespace(timeWindow);
BinaryRowData* countRow = BinaryRowData::createBinaryRowDataWithMem(1);
countRow->setLong(0, 1);
windowAggOperator->windowState->update(countRow);
RowData* windowResult = windowAggOperator->windowState->value();
ASSERT_EQ(windowResult->getArity(), 1);
ASSERT_EQ(*windowResult->getLong(0), 1);
BinaryRowData* countRow1 = BinaryRowData::createBinaryRowDataWithMem(1);
countRow1->setLong(0, 2);
windowAggOperator->windowState->update(countRow1);
windowResult = windowAggOperator->windowState->value();
ASSERT_EQ(windowResult->getArity(), 1);
ASSERT_EQ(*windowResult->getLong(0), 2);
bidIdRow = std::shared_ptr<BinaryRowData>(BinaryRowData::createBinaryRowDataWithMem(1));
bidIdRow->setLong(0, 20);
windowAggOperator->setCurrentKey(bidIdRow);
windowResult = windowAggOperator->windowState->value();
ASSERT_EQ(windowResult, nullptr);
countRow = BinaryRowData::createBinaryRowDataWithMem(1);
countRow->setLong(0, 2);
windowAggOperator->windowState->update(countRow);
windowResult = windowAggOperator->windowState->value();
ASSERT_EQ(windowResult->getArity(), 1);
ASSERT_EQ(*windowResult->getLong(0), 2);
timeWindow = TimeWindow(1742625630000, 1742625640000);
windowAggOperator->windowState->setCurrentNamespace(timeWindow);
windowResult = windowAggOperator->windowState->value();
ASSERT_EQ(windowResult, nullptr);
countRow = BinaryRowData::createBinaryRowDataWithMem(1);
countRow->setLong(0, 2);
windowAggOperator->windowState->update(countRow);
windowResult = windowAggOperator->windowState->value();
ASSERT_EQ(windowResult->getArity(), 1);
ASSERT_EQ(*windowResult->getLong(0), 2);
}
TEST(AggregateWindowOperatorTest, TimeWindowTest)
{
std::string uniqueName = "org.apache.flink.table.runtime.operators.window.AggregateWindowOperator";
json parsedJson = json::parse(nexmarkQ12Description);
omnistream::OperatorConfig opConfig(
uniqueName,
"LocalWindowAgg_By_Simple",
parsedJson["operators"][0]["inputTypes"],
parsedJson["operators"][0]["outputTypes"],
parsedJson["operators"][0]["description"]);
auto* output = new BatchOutputTest();
auto* windowAggOperator = dynamic_cast<AggregateWindowOperator<std::shared_ptr<RowData>, TimeWindow>*>(
omnistream::StreamOperatorFactory::createOperatorAndCollector(opConfig, output));
auto env2 = new omnistream::RuntimeEnvironmentV2();
auto taskInfo = new TaskInformationPOD();
taskInfo->setStateBackend("HashMapStateBackend");
{
auto configPOD = taskInfo->getStreamConfigPOD();
auto operatorDesc = configPOD.getOperatorDescription();
operatorDesc.setOperatorId("deadbeefdeadbeefdeadbeefdeadbeef");
configPOD.setOperatorDescription(operatorDesc);
taskInfo->setStreamConfigPOD(configPOD);
}
env2->SetTaskStateManager(std::make_shared<omnistream::TaskStateManager>());
env2->setTaskConfiguration(*taskInfo);
StreamTaskStateInitializerImpl* initializer = new StreamTaskStateInitializerImpl(env2);
windowAggOperator->initializeState(initializer, new LongSerializer());
windowAggOperator->open();
auto key = std::shared_ptr<BinaryRowData>(BinaryRowData::createBinaryRowDataWithMem(1));
key->setLong(0, 2);
windowAggOperator->setCurrentKey(key);
BinaryRowData* inputRow = BinaryRowData::createBinaryRowDataWithMem(2);
inputRow->setLong(0, 10);
const std::vector<TimeWindow> assignStateNamespace =
windowAggOperator->windowFunction->assignStateNamespace(inputRow, 1742625633598);
ASSERT_EQ(assignStateNamespace.size(), 1);
ASSERT_EQ(assignStateNamespace[0].getStart(), 1742625633598);
ASSERT_EQ(assignStateNamespace.size(), 1);
ASSERT_EQ(assignStateNamespace[0].getEnd(), 1742625643598);
BinaryRowData* inputRow1 = BinaryRowData::createBinaryRowDataWithMem(2);
inputRow->setLong(0, 10);
const std::vector<TimeWindow> assignStateNamespace1 =
windowAggOperator->windowFunction->assignStateNamespace(inputRow1, 1742625634598);
ASSERT_EQ(assignStateNamespace1.size(), 1);
ASSERT_EQ(assignStateNamespace1[0].getStart(), 1742625633598);
ASSERT_EQ(assignStateNamespace1.size(), 1);
ASSERT_EQ(assignStateNamespace1[0].getEnd(), 1742625643598);
}
TEST(AggregateWindowOperatorTest, JsonTest)
{
std::string uniqueName = "org.apache.flink.table.runtime.operators.window.AggregateWindowOperator";
json parsedJson = json::parse(nexmarkQ12Description);
omnistream::OperatorConfig opConfig(
uniqueName,
"LocalWindowAgg_By_Simple",
parsedJson["operators"][0]["inputTypes"],
parsedJson["operators"][0]["outputTypes"],
parsedJson["operators"][0]["description"]);
auto* output = new BatchOutputTest();
auto* windowAggOperator = dynamic_cast<AggregateWindowOperator<std::shared_ptr<RowData>, TimeWindow>*>(
omnistream::StreamOperatorFactory::createOperatorAndCollector(opConfig, output));
auto env2 = new omnistream::RuntimeEnvironmentV2();
auto taskInfo = new TaskInformationPOD();
taskInfo->setStateBackend("HashMapStateBackend");
{
auto configPOD = taskInfo->getStreamConfigPOD();
auto operatorDesc = configPOD.getOperatorDescription();
operatorDesc.setOperatorId("deadbeefdeadbeefdeadbeefdeadbeef");
configPOD.setOperatorDescription(operatorDesc);
taskInfo->setStreamConfigPOD(configPOD);
}
env2->SetTaskStateManager(std::make_shared<omnistream::TaskStateManager>());
env2->setTaskConfiguration(*taskInfo);
StreamTaskStateInitializerImpl* initializer = new StreamTaskStateInitializerImpl(env2);
windowAggOperator->initializeState(initializer, new LongSerializer());
windowAggOperator->open();
omnistream::VectorBatch* vBatch = Q12VectorBatchInput();
auto* streamRecord = new StreamRecord(vBatch);
windowAggOperator->processBatch(streamRecord);
windowAggOperator->internalTimerService->advanceWatermark(2842625623598);
}
TEST(AggregateWindowOperatorTest, TimestampLtzWindowBoundaryUsesShiftTimezone)
{
std::string ltzDescription = R"DELIM({
"partition": {
"partitionName": "none",
"channelNumber": 1
},
"operators": [{
"output": {
"kind": "Row",
"type": [{
"kind": "logical",
"isNull": true,
"type": "BIGINT"
}]
},
"inputs": [{
"kind": "Row",
"type": [{
"kind": "logical",
"isNull": true,
"type": "BIGINT"
},
{
"kind": "logical",
"isNull": true,
"precision": 3,
"type": "TIMESTAMP_WITH_LOCAL_TIME_ZONE",
"timestampKind": 1
}]
}],
"name": "GroupWindowAggregate(groupBy=[id], window=[TumblingGroupWindow('w$, event_time_ltz, 3600000)], properties=[w$start, w$end, w$rowtime, w$proctime], select=[id, COUNT(*) AS cnt, start('w$) AS w$start, end('w$) AS w$end, rowtime('w$) AS w$rowtime, proctime('w$) AS w$proctime])",
"description": {
"originDescription": null,
"inputTypes": ["BIGINT", "TIMESTAMP_WITH_LOCAL_TIME_ZONE(3)"],
"outputTypes": ["BIGINT", "BIGINT", "TIMESTAMP_WITHOUT_TIME_ZONE(3)", "TIMESTAMP_WITHOUT_TIME_ZONE(3)", "TIMESTAMP_WITH_LOCAL_TIME_ZONE(3)", "TIMESTAMP_WITH_LOCAL_TIME_ZONE(3)"],
"windowPropertyTypes": ["TIMESTAMP(3) NOT NULL", "TIMESTAMP(3) NOT NULL", "TIMESTAMP_LTZ(3) *ROWTIME*", "TIMESTAMP_LTZ(3) *PROCTIME*"],
"grouping": [0],
"aggInfoList": {
"aggregateCalls": [{
"name": "COUNT()",
"aggregationFunction": "Count1AggFunction",
"argIndexes": [],
"consumeRetraction": "false",
"filterArg": -1
}],
"AccTypes": ["BIGINT"],
"aggValueTypes": ["BIGINT"],
"indexOfCountStar": -1
},
"generateUpdateBefore": false,
"allowedLateness": 0,
"windowType": "TumblingGroupWindow('w$, event_time_ltz, 3600000)",
"countType": "time",
"timeType": "event",
"windowSize": 3600000,
"inputTimeFieldIndex": 1,
"shiftTimeZone": "Asia/Shanghai"
},
"id": "org.apache.flink.table.runtime.operators.window.AggregateWindowOperator"
}]
})DELIM";
std::string uniqueName = "org.apache.flink.table.runtime.operators.window.AggregateWindowOperator";
json parsedJson = json::parse(ltzDescription);
omnistream::OperatorConfig opConfig(
uniqueName,
"LocalWindowAgg_Ltz",
parsedJson["operators"][0]["inputTypes"],
parsedJson["operators"][0]["outputTypes"],
parsedJson["operators"][0]["description"]);
auto* output = new BatchOutputTest();
auto* windowAggOperator = dynamic_cast<AggregateWindowOperator<std::shared_ptr<RowData>, TimeWindow>*>(
omnistream::StreamOperatorFactory::createOperatorAndCollector(opConfig, output));
auto env2 = new omnistream::RuntimeEnvironmentV2();
auto taskInfo = new TaskInformationPOD();
taskInfo->setStateBackend("HashMapStateBackend");
{
auto configPOD = taskInfo->getStreamConfigPOD();
auto operatorDesc = configPOD.getOperatorDescription();
operatorDesc.setOperatorId("deadbeefdeadbeefdeadbeefdeadbeef");
configPOD.setOperatorDescription(operatorDesc);
taskInfo->setStreamConfigPOD(configPOD);
}
env2->SetTaskStateManager(std::make_shared<omnistream::TaskStateManager>());
env2->setTaskConfiguration(*taskInfo);
StreamTaskStateInitializerImpl* initializer = new StreamTaskStateInitializerImpl(env2);
windowAggOperator->initializeState(initializer, new LongSerializer());
windowAggOperator->open();
auto key = std::shared_ptr<BinaryRowData>(BinaryRowData::createBinaryRowDataWithMem(1));
key->setLong(0, 1);
windowAggOperator->setCurrentKey(key);
BinaryRowData* inputRow = BinaryRowData::createBinaryRowDataWithMem(2);
inputRow->setLong(0, 1);
inputRow->setLong(1, 1704067205388L);
const auto windows = windowAggOperator->windowFunction->assignStateNamespace(inputRow, 1704096005388L);
ASSERT_EQ(windows.size(), 1);
ASSERT_EQ(windows[0].getStart(), 1704096000000L);
ASSERT_EQ(windows[0].getEnd(), 1704099600000L);
}
TEST(AggregateWindowOperatorTest, WindowBoundaryWithoutShiftTimezone)
{
std::string noShiftDescription = R"DELIM({
"partition": {
"partitionName": "none",
"channelNumber": 1
},
"operators": [{
"output": {
"kind": "Row",
"type": [{
"kind": "logical",
"isNull": true,
"type": "BIGINT"
}]
},
"inputs": [{
"kind": "Row",
"type": [{
"kind": "logical",
"isNull": true,
"type": "BIGINT"
},
{
"kind": "logical",
"isNull": true,
"precision": 3,
"type": "TIMESTAMP_WITH_LOCAL_TIME_ZONE",
"timestampKind": 1
}]
}],
"name": "GroupWindowAggregate(groupBy=[id], window=[TumblingGroupWindow('w$, event_time_ltz, 3600000)], properties=[w$start, w$end, w$rowtime, w$proctime], select=[id, COUNT(*) AS cnt, start('w$) AS w$start, end('w$) AS w$end, rowtime('w$) AS w$rowtime, proctime('w$) AS w$proctime])",
"description": {
"originDescription": null,
"inputTypes": ["BIGINT", "TIMESTAMP_WITH_LOCAL_TIME_ZONE(3)"],
"outputTypes": ["BIGINT", "BIGINT", "TIMESTAMP_WITHOUT_TIME_ZONE(3)", "TIMESTAMP_WITHOUT_TIME_ZONE(3)", "TIMESTAMP_WITH_LOCAL_TIME_ZONE(3)", "TIMESTAMP_WITH_LOCAL_TIME_ZONE(3)"],
"windowPropertyTypes": ["TIMESTAMP(3) NOT NULL", "TIMESTAMP(3) NOT NULL", "TIMESTAMP_LTZ(3) *ROWTIME*", "TIMESTAMP_LTZ(3) *PROCTIME*"],
"grouping": [0],
"aggInfoList": {
"aggregateCalls": [{
"name": "COUNT()",
"aggregationFunction": "Count1AggFunction",
"argIndexes": [],
"consumeRetraction": "false",
"filterArg": -1
}],
"AccTypes": ["BIGINT"],
"aggValueTypes": ["BIGINT"],
"indexOfCountStar": -1
},
"generateUpdateBefore": false,
"allowedLateness": 0,
"windowType": "TumblingGroupWindow('w$, event_time_ltz, 3600000)",
"countType": "time",
"timeType": "event",
"windowSize": 3600000,
"inputTimeFieldIndex": 1
},
"id": "org.apache.flink.table.runtime.operators.window.AggregateWindowOperator"
}]
})DELIM";
std::string uniqueName = "org.apache.flink.table.runtime.operators.window.AggregateWindowOperator";
json parsedJson = json::parse(noShiftDescription);
omnistream::OperatorConfig opConfig(
uniqueName,
"LocalWindowAgg_NoShift",
parsedJson["operators"][0]["inputTypes"],
parsedJson["operators"][0]["outputTypes"],
parsedJson["operators"][0]["description"]);
auto* output = new BatchOutputTest();
auto* windowAggOperator = dynamic_cast<AggregateWindowOperator<std::shared_ptr<RowData>, TimeWindow>*>(
omnistream::StreamOperatorFactory::createOperatorAndCollector(opConfig, output));
auto env2 = new omnistream::RuntimeEnvironmentV2();
auto taskInfo = new TaskInformationPOD();
taskInfo->setStateBackend("HashMapStateBackend");
{
auto configPOD = taskInfo->getStreamConfigPOD();
auto operatorDesc = configPOD.getOperatorDescription();
operatorDesc.setOperatorId("deadbeefdeadbeefdeadbeefdeadbeef");
configPOD.setOperatorDescription(operatorDesc);
taskInfo->setStreamConfigPOD(configPOD);
}
env2->SetTaskStateManager(std::make_shared<omnistream::TaskStateManager>());
env2->setTaskConfiguration(*taskInfo);
StreamTaskStateInitializerImpl* initializer = new StreamTaskStateInitializerImpl(env2);
windowAggOperator->initializeState(initializer, new LongSerializer());
windowAggOperator->open();
auto key = std::shared_ptr<BinaryRowData>(BinaryRowData::createBinaryRowDataWithMem(1));
key->setLong(0, 1);
windowAggOperator->setCurrentKey(key);
constexpr int64_t epochMillis = 1704067205388L;
BinaryRowData* inputRow = BinaryRowData::createBinaryRowDataWithMem(2);
inputRow->setLong(0, 1);
inputRow->setLong(1, epochMillis);
const auto windows = windowAggOperator->windowFunction->assignStateNamespace(inputRow, epochMillis);
ASSERT_EQ(windows.size(), 1);
EXPECT_EQ(windows[0].getStart(), 1704067200000L);
EXPECT_EQ(windows[0].getEnd(), 1704070800000L);
}
TEST(AggregateWindowOperatorTest, WindowCleanupTimerUsesShiftTimezone)
{
EXPECT_EQ(TimeWindowUtil::toCleanupTimerMills(1704099599999L, 0, "Asia/Shanghai"), 1704070799999L);
EXPECT_EQ(TimeWindowUtil::toCleanupTimerMills(1704099599999L, 0, "UTC"), 1704099599999L);
}
TEST(AggregateWindowOperatorTest, TimestampLtzTimeConversionUsesFastPath)
{
constexpr int64_t shanghaiEpochMillis = 1704067205388L;
constexpr int64_t utcWallClockMillis = 1704096005388L;
constexpr int64_t windowEndUtcMillis = 1704099600000L;
EXPECT_EQ(TimeWindowUtil::toUtcTimestampMills(shanghaiEpochMillis, "Asia/Shanghai"), utcWallClockMillis);
EXPECT_EQ(TimeWindowUtil::toUtcTimestampMills(shanghaiEpochMillis, "GMT+08:00"), utcWallClockMillis);
EXPECT_EQ(TimeWindowUtil::toEpochMills(utcWallClockMillis, "Asia/Shanghai"), shanghaiEpochMillis);
EXPECT_EQ(TimeWindowUtil::toEpochMills(utcWallClockMillis, "GMT+08:00"), shanghaiEpochMillis);
EXPECT_TRUE(TimeWindowUtil::isWindowFired(windowEndUtcMillis, 1704070799999L, "Asia/Shanghai"));
EXPECT_FALSE(TimeWindowUtil::isWindowFired(windowEndUtcMillis, 1704070799998L, "Asia/Shanghai"));
}
TEST(AggregateWindowOperatorTest, TimeConversionWithoutShiftTimezone)
{
constexpr int64_t epochMillis = 1704067205388L;
constexpr int64_t windowEndUtcMillis = 1704070800000L;
EXPECT_EQ(TimeWindowUtil::toUtcTimestampMills(epochMillis, "UTC"), epochMillis);
EXPECT_EQ(TimeWindowUtil::toUtcTimestampMills(epochMillis, ""), epochMillis);
EXPECT_EQ(TimeWindowUtil::toEpochMills(epochMillis, "UTC"), epochMillis);
EXPECT_EQ(TimeWindowUtil::toEpochMillsForTimer(windowEndUtcMillis - 1, "UTC"), windowEndUtcMillis - 1);
EXPECT_TRUE(TimeWindowUtil::isWindowFired(windowEndUtcMillis, windowEndUtcMillis - 1, "UTC"));
EXPECT_FALSE(TimeWindowUtil::isWindowFired(windowEndUtcMillis, windowEndUtcMillis - 2, "UTC"));
EXPECT_TRUE(TimeWindowUtil::isWindowFired(windowEndUtcMillis, windowEndUtcMillis - 1));
EXPECT_FALSE(TimeWindowUtil::isWindowFired(windowEndUtcMillis, windowEndUtcMillis - 2));
}