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
121 changes: 121 additions & 0 deletions be/src/exprs/function/array/function_array_pop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,23 @@
#include <glog/logging.h>

#include <cstddef>
#include <limits>
#include <memory>
#include <ostream>
#include <utility>

#include "common/compiler_util.h"
#include "common/status.h"
#include "core/assert_cast.h"
#include "core/block/block.h"
#include "core/block/column_numbers.h"
#include "core/block/column_with_type_and_name.h"
#include "core/column/column.h"
#include "core/column/column_array.h"
#include "core/column/column_nullable.h"
#include "core/column/column_vector.h"
#include "core/data_type/data_type.h"
#include "core/data_type/data_type_nullable.h"
#include "core/types.h"
#include "exprs/aggregate/aggregate_function.h"
#include "exprs/function/array/function_array_utils.h"
Expand Down Expand Up @@ -104,9 +110,124 @@ class FunctionArrayPopfront : public FunctionArrayPop<FunctionArrayPopfront> {
static constexpr int start_offset = 2;
};

class FunctionArrayTrim : public IFunction {
public:
static constexpr auto name = "trim_array";
static FunctionPtr create() { return std::make_shared<FunctionArrayTrim>(); }

String get_name() const override { return name; }

bool is_variadic() const override { return false; }

size_t get_number_of_arguments() const override { return 2; }

bool use_default_implementation_for_nulls() const override { return false; }

DataTypePtr get_return_type_impl(const DataTypes& arguments) const override {
const auto array_type = remove_nullable(arguments[0]);
DCHECK(array_type->get_primitive_type() == TYPE_ARRAY)
<< "First argument for function: " << name
<< " should be DataTypeArray but it has type " << arguments[0]->get_name() << ".";
DCHECK(remove_nullable(arguments[1])->get_primitive_type() == TYPE_BIGINT)
<< "Second argument for function: " << name << " should be BigInt but it has type "
<< arguments[1]->get_name() << ".";
if (arguments[0]->is_nullable() || arguments[1]->is_nullable()) {
return make_nullable(array_type);
}
return array_type;
}

Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments,
uint32_t result, size_t input_rows_count) const override {
const auto& [array_column, array_is_const] =
unpack_if_const(block.get_by_position(arguments[0]).column);
const auto& [size_column, size_is_const] =
unpack_if_const(block.get_by_position(arguments[1]).column);

ColumnArrayExecutionData src;
if (!extract_column_array_info(*array_column, src)) {
return Status::RuntimeError(
fmt::format("execute failed, unsupported types for function {}({}, {})",
get_name(), block.get_by_position(arguments[0]).type->get_name(),
block.get_by_position(arguments[1]).type->get_name()));
}

const UInt8* size_null_map = nullptr;
const IColumn* size_data_column = size_column.get();
if (const auto* nullable_size = check_and_get_column<ColumnNullable>(size_data_column)) {
size_null_map = nullable_size->get_null_map_data().data();
size_data_column = &nullable_size->get_nested_column();
}
const auto& sizes = assert_cast<const ColumnInt64&>(*size_data_column).get_data();

auto result_array = ColumnArray::create(src.array_col->get_data_ptr()->clone_empty(),
ColumnArray::ColumnOffsets::create());
auto& result_data = result_array->get_data();
auto& result_offsets = result_array->get_offsets();
result_offsets.resize(input_rows_count);

auto result_null_map = ColumnUInt8::create(input_rows_count, 0);
auto& result_null_map_data = result_null_map->get_data();
size_t result_offset = 0;
for (size_t row = 0; row < input_rows_count; ++row) {
const size_t array_row = index_check_const(row, array_is_const);
const size_t size_row = index_check_const(row, size_is_const);
const bool is_null = (src.array_nullmap_data && src.array_nullmap_data[array_row]) ||
(size_null_map && size_null_map[size_row]);
result_null_map_data[row] = is_null;
if (is_null) {
result_offsets[row] = result_offset;
continue;
}

const auto size = sizes[size_row];
const size_t offset = (*src.offsets_ptr)[array_row - 1];
const size_t cardinality = (*src.offsets_ptr)[array_row] - offset;
if (UNLIKELY(size < 0)) {
return Status::InvalidArgument("size must not be negative: {}", size);
}
if (UNLIKELY(static_cast<size_t>(size) > cardinality)) {

@linrrzqqq linrrzqqq Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里估计得重写 use_default_implementation_for_nulls

外层默认的 null 处理,对于 array 列来说会替换为[], which cardinality == 0, 所以如果使用默认处理框架,L156应该会报错,得手动处理 null

return Status::InvalidArgument("size must not exceed array cardinality {}: {}",
cardinality, size);
}

const size_t keep = cardinality - size;
if (UNLIKELY(result_offset > std::numeric_limits<size_t>::max() - keep)) {
return Status::InvalidArgument("result array size overflows");
}
result_offset += keep;
result_offsets[row] = result_offset;
}

result_data.reserve(result_offset);
for (size_t row = 0; row < input_rows_count; ++row) {
if (result_null_map_data[row]) {
continue;
}

const size_t array_row = index_check_const(row, array_is_const);
const size_t offset = (*src.offsets_ptr)[array_row - 1];
const size_t previous_result_offset = row == 0 ? 0 : result_offsets[row - 1];
const size_t keep = result_offsets[row] - previous_result_offset;
if (keep > 0) {
result_data.insert_range_from(src.array_col->get_data(), offset, keep);
}
}

if (block.get_by_position(result).type->is_nullable()) {
block.replace_by_position(result, ColumnNullable::create(std::move(result_array),
std::move(result_null_map)));
} else {
block.replace_by_position(result, std::move(result_array));
}
return Status::OK();
}
};

void register_function_array_pop(SimpleFunctionFactory& factory) {
factory.register_function<FunctionArrayPopback>();
factory.register_function<FunctionArrayPopfront>();
factory.register_function<FunctionArrayTrim>();
}

} // namespace doris
130 changes: 130 additions & 0 deletions be/test/exprs/function/function_array_trim_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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 <memory>
#include <string>
#include <vector>

#include "core/block/block.h"
#include "core/column/column_const.h"
#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "exprs/function/function_test_util.h"
#include "exprs/function/simple_function_factory.h"

namespace doris {

static void check_array_trim_case(const DataSet& data_set, bool const_array, bool const_size) {
auto element_type = make_nullable(std::make_shared<DataTypeInt32>());
DataTypePtr array_type =
make_nullable(std::make_shared<DataTypeArray>(std::move(element_type)));
DataTypePtr size_type = make_nullable(std::make_shared<DataTypeInt64>());
const size_t row_size = data_set.size();

MutableColumnPtr array_column = array_type->create_column();
MutableColumnPtr size_column = size_type->create_column();
for (size_t row = 0; row < row_size; ++row) {
if (!const_array || row == 0) {
ASSERT_TRUE(insert_cell(array_column, array_type, data_set[row].first[0]));
}
if (!const_size || row == 0) {
ASSERT_TRUE(insert_cell(size_column, size_type, data_set[row].first[1]));
}
}

if (const_array) {
array_column = ColumnConst::create(std::move(array_column), row_size);
}
if (const_size) {
size_column = ColumnConst::create(std::move(size_column), row_size);
}

Block block;
block.insert({std::move(array_column), array_type, "array"});
block.insert({std::move(size_column), size_type, "size"});

FunctionBasePtr function = SimpleFunctionFactory::instance().get_function(
"trim_array", block.get_columns_with_type_and_name(), array_type);
ASSERT_NE(function, nullptr);

std::vector<std::shared_ptr<ColumnPtrWrapper>> constant_columns(2);
if (const_array) {
constant_columns[0] = std::make_shared<ColumnPtrWrapper>(block.get_by_position(0).column);
}
if (const_size) {
constant_columns[1] = std::make_shared<ColumnPtrWrapper>(block.get_by_position(1).column);
}

FunctionUtils function_utils(array_type, {array_type, size_type}, false);
auto* function_context = function_utils.get_fn_ctx();
function_context->set_constant_cols(constant_columns);
ASSERT_TRUE(function->open(function_context, FunctionContext::FRAGMENT_LOCAL).ok());
ASSERT_TRUE(function->open(function_context, FunctionContext::THREAD_LOCAL).ok());

block.insert({nullptr, array_type, "result"});
const auto result = block.columns() - 1;
ASSERT_TRUE(function->execute(function_context, block, {0, 1}, result, row_size).ok());

static_cast<void>(function->close(function_context, FunctionContext::THREAD_LOCAL));
static_cast<void>(function->close(function_context, FunctionContext::FRAGMENT_LOCAL));

MutableColumnPtr expected_column = array_type->create_column();
for (const auto& row : data_set) {
ASSERT_TRUE(insert_cell(expected_column, array_type, row.second));
}
const auto result_column =
block.get_by_position(result).column->convert_to_full_column_if_const();
for (size_t row = 0; row < row_size; ++row) {
EXPECT_EQ(0, result_column->compare_at(row, row, *expected_column, 1))
<< "row " << row << ", result: " << array_type->to_string(*result_column, row)
<< ", expected: " << array_type->to_string(*expected_column, row);
}
}

TEST(FunctionArrayTrimTest, all_argument_combinations) {
const TestArray empty;
const TestArray values = {Int32(1), Int32(2), Int32(3), Int32(4)};
const TestArray short_values = {Int32(5), Int32(6)};
const TestArray values_with_null = {Int32(1), Null(), Int32(3)};

check_array_trim_case(
{{{AnyType(values), Int64(0)}, AnyType(values)},
{{AnyType(values), Int64(2)}, AnyType(TestArray {Int32(1), Int32(2)})},
{{AnyType(empty), Int64(0)}, AnyType(empty)},
{{AnyType(values_with_null), Int64(1)}, AnyType(TestArray {Int32(1), Null()})},
{{Null(), Int64(1)}, Null()},
{{AnyType(values), Null()}, Null()}},
false, false);

check_array_trim_case({{{AnyType(values), Int64(0)}, AnyType(values)},
{{AnyType(values), Int64(2)}, AnyType(TestArray {Int32(1), Int32(2)})},
{{AnyType(values), Int64(4)}, AnyType(empty)}},
true, false);

check_array_trim_case(
{{{AnyType(values), Int64(1)}, AnyType(TestArray {Int32(1), Int32(2), Int32(3)})},
{{AnyType(short_values), Int64(1)}, AnyType(TestArray {Int32(5)})},
{{AnyType(values_with_null), Int64(1)}, AnyType(TestArray {Int32(1), Null()})},
{{Null(), Int64(1)}, Null()}},
false, true);

check_array_trim_case({{{AnyType(values), Int64(2)}, AnyType(TestArray {Int32(1), Int32(2)})}},
true, true);
}

} // namespace doris
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@
import org.apache.doris.nereids.trees.expressions.functions.scalar.TransformValues;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Translate;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Trim;
import org.apache.doris.nereids.trees.expressions.functions.scalar.TrimArray;
import org.apache.doris.nereids.trees.expressions.functions.scalar.TrimIn;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Truncate;
import org.apache.doris.nereids.trees.expressions.functions.scalar.TryParseToVariant;
Expand Down Expand Up @@ -1149,6 +1150,7 @@ public class BuiltinScalarFunctions implements FunctionHelper {
scalar(ToSeconds.class, "to_seconds"),
scalar(Translate.class, "translate"),
scalar(Trim.class, "trim"),
scalar(TrimArray.class, "trim_array"),
scalar(TrimIn.class, "trim_in"),
scalar(Truncate.class, "truncate"),
scalar(Unhex.class, "unhex"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ public static Expression cardinality(MapLiteral map) {
return new BigIntLiteral(map.getValue().size());
}

/** Remove a number of elements from the end of an array. */
@ExecFunction(name = "trim_array")
public static Expression trimArray(ArrayLiteral array, BigIntLiteral size) {
long trimSize = size.getValue();
int cardinality = array.getValue().size();
if (trimSize < 0) {
throw new AnalysisException("size must not be negative: " + trimSize);
}
if (trimSize > cardinality) {
throw new AnalysisException("size must not exceed array cardinality "
+ cardinality + ": " + trimSize);
}
return new ArrayLiteral(array.getValue().subList(0, cardinality - (int) trimSize),
array.getDataType());
}

/**
* Compute the cross product between two 3D float arrays.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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.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.ArrayType;
import org.apache.doris.nereids.types.BigIntType;
import org.apache.doris.nereids.types.coercion.AnyDataType;

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

import java.util.List;

/**
* Remove the specified number of elements from the end of an array.
*/
public class TrimArray extends ScalarFunction
implements BinaryExpression, ExplicitlyCastableSignature, PropagateNullable {

public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
FunctionSignature.retArgType(0)
.args(ArrayType.of(AnyDataType.INSTANCE_WITHOUT_INDEX), BigIntType.INSTANCE)
);

public TrimArray(Expression array, Expression size) {
super("trim_array", array, size);
}

private TrimArray(ScalarFunctionParams functionParams) {
super(functionParams);
}

@Override
public TrimArray withChildren(List<Expression> children) {
Preconditions.checkArgument(children.size() == 2,
"trim_array accepts 2 arguments, but got %s (%s)", children.size(), children);
return new TrimArray(getFunctionParams(children));
}

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

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