* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include <regex>
#include <stdexcept>
#include <string>
#include "table/data/vectorbatch/temp_batch_function.h"
#include "StreamCalcBatch.h"
using json = nlohmann::json;
namespace {
[[noreturn]] void ThrowUnsupportedCalcExpr(const json& expr)
{
throw std::runtime_error("Unsupported calc expression for native codegen: " + expr.dump());
}
}
static bool checkAllFieldReference(const json& jsonDesc)
{
if (jsonDesc.contains("indices") && jsonDesc["indices"].is_array()) {
for (auto& index : jsonDesc["indices"]) {
if (index["exprType"] != "FIELD_REFERENCE") {
return false;
}
}
return true;
}
return false;
}
StreamCalcBatch::StreamCalcBatch(const nlohmann::json& description, Output* output)
: description_(description),
selectedRowsBuffer(1024),
executionContext(std::make_unique<omniruntime::op::ExecutionContext>())
{
LOG("StreamCalc description: " + description.dump());
this->output = output;
}
StreamCalcBatch::~StreamCalcBatch()
{
}
void StreamCalcBatch::processBatch(StreamRecord* input)
{
LOG("===>>>>>>");
int32_t newRowCnt = 0;
auto record = reinterpret_cast<omnistream::VectorBatch*>(input->getValue());
if (replacedDescriptions_.size() != 0) {
manuallyAddNewVectors(record);
}
if (isSimpleProjection_ && (!hasFilter)) {
record->RearrangeColumns(outputIndexes_);
outputBatch = record;
timestampedCollector_->collect(outputBatch);
} else {
auto projectedVecs = this->exprEvaluator->Evaluate(record, executionContext.get(), &selectedRowsBuffer);
if (hasFilter) {
if (projectedVecs != nullptr) {
newRowCnt = projectedVecs->GetRowCount();
long* timestamps = new long[newRowCnt];
RowKind* rowkinds = new RowKind[newRowCnt];
auto oldtimes = record->getTimestamps();
auto oldkinds = record->getRowKinds();
for (int i = 0; i < newRowCnt; i++) {
int rowIndex = selectedRowsBuffer.GetValue(i);
timestamps[i] = oldtimes[rowIndex];
rowkinds[i] = oldkinds[rowIndex];
}
outputBatch = new omnistream::VectorBatch(projectedVecs, timestamps, rowkinds);
delete record;
timestampedCollector_->collect(outputBatch);
} else {
LOG("Filter selected 0 rows");
delete record;
}
} else {
outputBatch = new omnistream::VectorBatch(projectedVecs, record->getTimestamps(), record->getRowKinds());
omniruntime::codegen::VectorHelper::FreeVecBatch(record);
timestampedCollector_->collect(outputBatch);
}
}
delete input;
}
void StreamCalcBatch::open()
{
parseDescription(description_);
if (!isSimpleProjection_ || hasFilter) {
JSONParser parser = JSONParser();
if (description_.contains("indices")) {
for (auto& index : description_["indices"]) {
auto expr = parser.ParseJSON(index);
if (expr == nullptr) {
omniruntime::expressions::Expr::DeleteExprs(projExprs);
projExprs.clear();
ThrowUnsupportedCalcExpr(index);
}
projExprs.push_back(expr);
}
}
if (hasFilter) {
filterCondition = parser.ParseJSON(description_["condition"]);
if (filterCondition == nullptr) {
omniruntime::expressions::Expr::DeleteExprs(projExprs);
projExprs.clear();
ThrowUnsupportedCalcExpr(description_["condition"]);
}
}
auto ofConfig = new omniruntime::op::OverflowConfig();
if (hasFilter) {
exprEvaluator =
new omniruntime::codegen::ExpressionEvaluator(filterCondition, projExprs, inputTypes_, ofConfig);
exprEvaluator->FilterFuncGeneration();
} else {
exprEvaluator = new omniruntime::codegen::ExpressionEvaluator(projExprs, inputTypes_, ofConfig);
exprEvaluator->ProjectFuncGeneration();
}
}
timestampedCollector_ = new TimestampedCollector(this->output);
}
void StreamCalcBatch::close()
{
timestampedCollector_->close();
}
const char* StreamCalcBatch::getName()
{
return "StreamCalcBatch";
}
void StreamCalcBatch::parseDescription(json& descriptionJson)
{
std::vector<std::string> inputTypeStrs = descriptionJson["inputTypes"].get<std::vector<std::string>>();
int nextIndex = inputTypeStrs.size();
collectUnsupportedExpr(descriptionJson, nextIndex);
LOG("after codegen patches: " + description_.dump());
inputTypeStrs = descriptionJson["inputTypes"].get<std::vector<std::string>>();
std::vector<omniruntime::type::DataTypePtr> types;
for (std::string otype : inputTypeStrs) {
auto omniType = LogicalType::flinkTypeToOmniTypeId(otype);
types.push_back(std::make_shared<omniruntime::type::DataType>(omniType));
}
inputTypes_ = omniruntime::type::DataTypes(types);
if (descriptionJson.contains("condition") && (!descriptionJson["condition"].is_null())) {
hasFilter = true;
}
isSimpleProjection_ = checkAllFieldReference(descriptionJson);
if (isSimpleProjection_) {
for (auto& index : descriptionJson["indices"]) {
outputIndexes_.push_back(index["colVal"]);
}
}
}
void StreamCalcBatch::collectUnsupportedExpr(json& description, int32_t& nextIndex)
{
collectUnsupportedExprImpl(description, nextIndex);
if (description.is_object()) {
for (auto it = description.begin(); it != description.end(); ++it) {
collectUnsupportedExpr(it.value(), nextIndex);
}
} else if (description.is_array()) {
for (auto& element : description) {
collectUnsupportedExpr(element, nextIndex);
}
}
}
inline bool IsMulInt64Decimal64Decimal128(const nlohmann::json& obj)
{
return obj["exprType"] == "BINARY" && obj["operator"] == "MULTIPLY" && obj["returnType"] == OMNI_DECIMAL128 &&
obj["right"]["exprType"] == "FIELD_REFERENCE" && obj["right"]["dataType"] == OMNI_LONG &&
obj["left"]["exprType"] == "LITERAL" && obj["left"]["dataType"] == OMNI_DECIMAL64 &&
obj["left"]["scale"] == obj["scale"];
}
void StreamCalcBatch::manuallyAddNewVectors(omnistream::VectorBatch* vb) const
{
LOG("=======>>>");
int row = vb->GetRowCount();
for (const auto& obj : replacedDescriptions_) {
if (obj["exprType"] == "FUNCTION" && obj["function_name"] == "regex_extract_null") {
int32_t inputIndex = obj["arguments"][0]["colVal"].get<int32_t>();
std::string regexToMatch = obj["arguments"][1]["value"].get<std::string>();
int32_t group = obj["arguments"][2]["value"].get<int32_t>();
vb->Append(omnistream::RegexpExtract(vb->Get(inputIndex), regexToMatch, group));
} else if (IsMulInt64Decimal64Decimal128(obj)) {
LOG("IsMulInt64Decimal64Decimal128");
auto vec = new omniruntime::vec::Vector<Decimal128>(row);
int64_t constant = obj["left"]["value"];
int32_t oldCol = obj["right"]["colVal"];
auto mulCol = reinterpret_cast<omniruntime::vec::Vector<int64_t>*>(vb->Get(oldCol));
for (int i = 0; i < row; i++) {
int128_t value = mulCol->GetValue(i) * constant;
vec->SetValue(i, Decimal128(value));
}
vb->Append(vec);
} else if (
obj["exprType"] == "PROCTIME" && obj["returnType"] == DataTypeId::OMNI_TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
auto curTime = std::chrono::system_clock::now();
auto curTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(curTime.time_since_epoch()).count();
auto vec = new omniruntime::vec::Vector<int64_t>(row);
for (int i = 0; i < row; i++) {
vec->SetValue(i, curTimeMs);
}
vb->Append(vec);
} else {
std::runtime_error("This unsupported expr does not have temp solution!");
}
}
}
void StreamCalcBatch::collectUnsupportedExprImpl(nlohmann::json& field, int32_t& nextIndex)
{
if (!field.is_object() || !field.contains("exprType")) {
return;
}
if (field["exprType"] == "FUNCTION" && field["function_name"] == "regex_extract_null") {
bool existed = false;
for (size_t i = 0; i < replacedDescriptions_.size(); i++) {
if (field == replacedDescriptions_[i]) {
field = newRefDescriptions_[i];
existed = true;
break;
}
}
if (!existed) {
replacedDescriptions_.push_back(field);
field["exprType"] = "FIELD_REFERENCE";
field["colVal"] = nextIndex;
field.erase("arguments");
field.erase("function_name");
field["dataType"] = field["returnType"];
nextIndex++;
newRefDescriptions_.push_back(field);
description_["inputTypes"].push_back("VARCHAR(2147483647)");
}
} else if (
field["exprType"] == "PROCTIME" && field["returnType"] == DataTypeId::OMNI_TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
replacedDescriptions_.push_back(field);
field["exprType"] = "FIELD_REFERENCE";
field["colVal"] = nextIndex;
field["dataType"] = (int)OMNI_LONG;
nextIndex++;
newRefDescriptions_.push_back(field);
description_["inputTypes"].push_back("BIGINT");
} else if (IsMulInt64Decimal64Decimal128(field)) {
replacedDescriptions_.push_back(field);
field["exprType"] = "FIELD_REFERENCE";
field["colVal"] = nextIndex;
field["dataType"] = field["returnType"];
field.erase("left");
field.erase("right");
field.erase("returnType");
field.erase("operator");
nextIndex++;
newRefDescriptions_.push_back(field);
description_["inputTypes"].push_back(
"DECIMAL(" + std::to_string(field["precision"].get<int>()) + ", " +
std::to_string(field["scale"].get<int>()) + ")");
}
}