diff --git a/be/src/exprs/function/geo/functions_geo.cpp b/be/src/exprs/function/geo/functions_geo.cpp index b4967780d2c8dc..00b2735ace29ce 100644 --- a/be/src/exprs/function/geo/functions_geo.cpp +++ b/be/src/exprs/function/geo/functions_geo.cpp @@ -18,6 +18,7 @@ #include "exprs/function/geo/functions_geo.h" #include +#include #include #include @@ -780,6 +781,45 @@ struct StGeometryType { } }; +struct StIsClosed { + static constexpr auto NAME = "st_isclosed"; + static const size_t NUM_ARGS = 1; + using Type = DataTypeUInt8; + + static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { + DCHECK_EQ(arguments.size(), 1); + + auto col = ColumnView::create(block.get_by_position(arguments[0]).column); + const auto size = col.size(); + + auto res = ColumnUInt8::create(size, 0); + auto null_map = ColumnUInt8::create(size, 0); + auto& result_data = res->get_data(); + auto& null_map_data = null_map->get_data(); + + GeoLine line; + for (int row = 0; row < size; ++row) { + auto value = col.value_at(row); + if (!line.decode_from(value.data, value.size)) { + null_map_data[row] = 1; + continue; + } + + const auto num_points = line.numPoint(); + if (num_points < 2) { + null_map_data[row] = 1; + continue; + } + + result_data[row] = *line.getPoint(0) == *line.getPoint(num_points - 1); + } + + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + return Status::OK(); + } +}; + struct StDistance { static constexpr auto NAME = "st_distance"; static const size_t NUM_ARGS = 2; @@ -1107,6 +1147,7 @@ void register_function_geo(SimpleFunctionFactory& factory) { factory.register_function>(); factory.register_function>(); factory.register_function>(); + factory.register_function>(); factory.register_function>(); factory.register_function>(); factory.register_function>(); diff --git a/be/test/exprs/function/geo/functions_geo_test.cpp b/be/test/exprs/function/geo/functions_geo_test.cpp index 338df011af0a39..0dce1c876ab8eb 100644 --- a/be/test/exprs/function/geo/functions_geo_test.cpp +++ b/be/test/exprs/function/geo/functions_geo_test.cpp @@ -218,6 +218,38 @@ TEST(VGeoFunctionsTest, function_geo_st_numpoints_invalid) { static_cast(check_function(func_name, input_types, data_set)); } +// ==================== ST_IsClosed Tests ==================== + +TEST(VGeoFunctionsTest, function_geo_st_isclosed) { + std::string func_name = "st_isclosed"; + InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR}; + + auto encode_wkt = [](const std::string& wkt) { + GeoParseStatus status; + auto shape = GeoShape::from_wkt(wkt.data(), wkt.size(), status); + EXPECT_EQ(status, GEO_PARSE_OK); + EXPECT_NE(shape, nullptr); + + std::string buf; + shape->encode_to(&buf); + return buf; + }; + + auto closed_line = encode_wkt("LINESTRING (0 0, 1 1, 0 0)"); + auto open_line = encode_wkt("LINESTRING (0 0, 1 1, 2 2)"); + auto nearly_closed_line = encode_wkt("LINESTRING (0 0, 1 1, 0 0.0000000000001)"); + auto point = encode_wkt("POINT (0 0)"); + + DataSet data_set = {{{closed_line}, uint8_t(1)}, + {{open_line}, uint8_t(0)}, + {{nearly_closed_line}, uint8_t(0)}, + {{point}, Null()}, + {{std::string("invalid_geometry_data")}, Null()}, + {{Null()}, Null()}}; + + check_function_all_arg_comb(func_name, input_types, data_set); +} + // ==================== ST_Geometries Tests ==================== TEST(VGeoFunctionsTest, function_geo_st_geometries_point) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java index 62369888db9e23..24cee717046d81 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java @@ -491,6 +491,7 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeomfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StIntersects; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StIsClosed; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLength; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinefromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinestringfromtext; @@ -1073,6 +1074,7 @@ public class BuiltinScalarFunctions implements FunctionHelper { scalar(StTouches.class, "st_touches"), scalar(StLength.class, "st_length"), scalar(StGeometryType.class, "st_geometrytype"), + scalar(StIsClosed.class, "st_isclosed"), scalar(StNumGeometries.class, "st_numgeometries"), scalar(StGeometries.class, "st_geometries"), scalar(StNumPoints.class, "st_numpoints", "st_npoints"), diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StIsClosed.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StIsClosed.java new file mode 100644 index 00000000000000..10802f77c1166a --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StIsClosed.java @@ -0,0 +1,75 @@ +// 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.AlwaysNullable; +import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature; +import org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral; +import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression; +import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.types.BooleanType; +import org.apache.doris.nereids.types.VarcharType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * ScalarFunction 'st_isclosed'. + */ +public class StIsClosed extends ScalarFunction + implements UnaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(BooleanType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT) + ); + + /** + * constructor with 1 argument. + */ + public StIsClosed(Expression arg0) { + super("st_isclosed", arg0); + } + + /** constructor for withChildren and reuse signature */ + private StIsClosed(ScalarFunctionParams functionParams) { + super(functionParams); + } + + /** + * withChildren. + */ + @Override + public StIsClosed withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new StIsClosed(getFunctionParams(children)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStIsClosed(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java index c0f03da1789b48..2ee49e2dece737 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java @@ -510,6 +510,7 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StGeomfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StIntersects; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StIsClosed; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLength; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinefromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StLinestringfromtext; @@ -2398,6 +2399,10 @@ default R visitStGeometryType(StGeometryType stGeometryType, C context) { return visitScalarFunction(stGeometryType, context); } + default R visitStIsClosed(StIsClosed stIsClosed, C context) { + return visitScalarFunction(stIsClosed, context); + } + default R visitStNumGeometries(StNumGeometries stNumGeometries, C context) { return visitScalarFunction(stNumGeometries, context); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeoComponentFunctionsTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeoComponentFunctionsTest.java index a976e15aeb7b46..583b96748a1d9e 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeoComponentFunctionsTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeoComponentFunctionsTest.java @@ -22,6 +22,7 @@ import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral; import org.apache.doris.nereids.types.ArrayType; import org.apache.doris.nereids.types.BigIntType; +import org.apache.doris.nereids.types.BooleanType; import org.apache.doris.nereids.types.VarcharType; import com.google.common.collect.ImmutableList; @@ -31,7 +32,7 @@ import java.util.List; /** - * Unit tests for ST_NumGeometries, ST_NumPoints, and ST_Geometries scalar functions. + * Unit tests for ST_NumGeometries, ST_NumPoints, ST_Geometries, and ST_IsClosed scalar functions. */ public class StGeoComponentFunctionsTest { @@ -133,4 +134,32 @@ public void testStGeometriesReturnType() { Assertions.assertTrue(arrayType.getItemType() instanceof VarcharType); } + @Test + public void testStIsClosedBasicProperties() { + Expression arg = new VarcharLiteral("test"); + StIsClosed func = new StIsClosed(arg); + + Assertions.assertEquals("st_isclosed", func.getName()); + Assertions.assertEquals(1, func.arity()); + Assertions.assertTrue(func.nullable()); + + List signatures = func.getSignatures(); + Assertions.assertEquals(1, signatures.size()); + Assertions.assertEquals(BooleanType.INSTANCE, signatures.get(0).returnType); + Assertions.assertEquals(VarcharType.SYSTEM_DEFAULT, signatures.get(0).getArgType(0)); + } + + @Test + public void testStIsClosedWithChildren() { + Expression arg = new VarcharLiteral("test"); + StIsClosed func = new StIsClosed(arg); + + Expression newArg = new VarcharLiteral("new_test"); + StIsClosed newFunc = func.withChildren(ImmutableList.of(newArg)); + + Assertions.assertNotSame(func, newFunc); + Assertions.assertEquals("st_isclosed", newFunc.getName()); + Assertions.assertEquals(1, newFunc.arity()); + } + } diff --git a/regression-test/suites/query_p0/sql_functions/spatial_functions/test_st_isclosed.groovy b/regression-test/suites/query_p0/sql_functions/spatial_functions/test_st_isclosed.groovy new file mode 100644 index 00000000000000..b786df1e2614bc --- /dev/null +++ b/regression-test/suites/query_p0/sql_functions/spatial_functions/test_st_isclosed.groovy @@ -0,0 +1,30 @@ +// 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. + +suite("test_st_isclosed") { + def result = sql """ + SELECT ST_IsClosed(ST_LineFromText('LINESTRING (0 0, 1 1, 0 0)')), + ST_IsClosed(ST_LineFromText('LINESTRING (0 0, 1 1, 2 2)')), + ST_IsClosed(NULL), + ST_IsClosed(ST_Point(0, 0)) + """ + + assertEquals(true, result[0][0]) + assertEquals(false, result[0][1]) + assertEquals(null, result[0][2]) + assertEquals(null, result[0][3]) +}