This is an automated email from the ASF dual-hosted git repository.
Mryange pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new b1c01331a6f [feature](function) Add ST_NumGeometries, ST_NumPoints,
ST_Geometries functions (#63049)
b1c01331a6f is described below
commit b1c01331a6f16a4ee06a833d3d7482b936c3ad3e
Author: acarofpigs <[email protected]>
AuthorDate: Tue May 26 14:22:50 2026 +0800
[feature](function) Add ST_NumGeometries, ST_NumPoints, ST_Geometries
functions (#63049)
… functions
### What problem does this PR solve?
Issue Number: ref #48203
Related PR: apache/doris-website#3623
Problem Summary:
Add three new spatial functions for geometry collection operations:
- `ST_NumGeometries`: Returns the number of sub-geometries in a geometry
object.
- `ST_NumPoints`: Returns the total number of vertices (points) in a
geometry object.
- `ST_Geometries`: Decomposes a geometry object into an array of its
sub-geometries.
---
be/src/exprs/function/geo/functions_geo.cpp | 165 +++++++
be/src/exprs/function/geo/geo_types.cpp | 15 +
be/src/exprs/function/geo/geo_types.h | 15 +
be/test/exprs/function/geo/functions_geo_test.cpp | 455 +++++++++++++++++
.../doris/catalog/BuiltinScalarFunctions.java | 6 +
.../expressions/functions/scalar/StGeometries.java | 69 +++
.../functions/scalar/StNumGeometries.java | 68 +++
.../expressions/functions/scalar/StNumPoints.java | 68 +++
.../expressions/visitor/ScalarFunctionVisitor.java | 15 +
.../scalar/StGeoComponentFunctionsTest.java | 136 ++++++
...st_num_geometries_num_points_and_geometries.out | 336 +++++++++++++
...num_geometries_num_points_and_geometries.groovy | 544 +++++++++++++++++++++
12 files changed, 1892 insertions(+)
diff --git a/be/src/exprs/function/geo/functions_geo.cpp
b/be/src/exprs/function/geo/functions_geo.cpp
index 6a191f133e5..b4967780d2c 100644
--- a/be/src/exprs/function/geo/functions_geo.cpp
+++ b/be/src/exprs/function/geo/functions_geo.cpp
@@ -28,8 +28,10 @@
#include "core/block/block.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_execute_util.h"
#include "core/column/column_nullable.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 "core/data_type/data_type_string.h"
@@ -917,6 +919,165 @@ private:
}
};
+struct StNumGeometries {
+ static constexpr auto NAME = "st_numgeometries";
+ static const size_t NUM_ARGS = 1;
+ using Type = DataTypeInt64;
+
+ static Status execute(Block& block, const ColumnNumbers& arguments, size_t
result) {
+ DCHECK_EQ(arguments.size(), 1);
+
+ auto col =
ColumnView<TYPE_STRING>::create(block.get_by_position(arguments[0]).column);
+ const auto size = col.size();
+
+ auto res = ColumnInt64::create();
+ res->reserve(size);
+
+ auto null_map = ColumnUInt8::create(size, 0);
+ auto& null_map_data = null_map->get_data();
+
+ for (int row = 0; row < size; ++row) {
+ auto value = col.value_at(row);
+ auto shape = GeoShape::from_encoded(value.data, value.size);
+ if (!shape) {
+ null_map_data[row] = 1;
+ res->insert_default();
+ continue;
+ }
+
+ res->insert_value(shape->num_geometries());
+ }
+
+ block.replace_by_position(result,
+ ColumnNullable::create(std::move(res),
std::move(null_map)));
+ return Status::OK();
+ }
+};
+
+class FunctionStGeometries final : public IFunction {
+public:
+ static constexpr auto name = "st_geometries";
+
+ static FunctionPtr create() { return
std::make_shared<FunctionStGeometries>(); }
+
+ String get_name() const override { return name; }
+
+ size_t get_number_of_arguments() const override { return 1; }
+
+ bool is_variadic() const override { return false; }
+
+ DataTypePtr get_return_type_impl(const DataTypes& arguments) const
override {
+ return make_nullable(
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeString>())));
+ }
+
+ Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
+ uint32_t result, size_t input_rows_count) const
override {
+ DCHECK_EQ(arguments.size(), 1);
+
+ auto col =
ColumnView<TYPE_STRING>::create(block.get_by_position(arguments[0]).column);
+ const auto size = col.size();
+
+ auto nested_data = ColumnString::create();
+ auto offsets_col = ColumnArray::ColumnOffsets::create();
+ auto& offsets = offsets_col->get_data();
+ offsets.reserve(size);
+
+ auto null_map = ColumnUInt8::create(size, 0);
+ auto& null_map_data = null_map->get_data();
+
+ size_t current_offset = 0;
+ std::string buf;
+
+ for (size_t row = 0; row < size; ++row) {
+ auto shape_value = col.value_at(row);
+ auto shape = GeoShape::from_encoded(shape_value.data,
shape_value.size);
+
+ if (!shape) {
+ null_map_data[row] = 1;
+ offsets.push_back(current_offset);
+ continue;
+ }
+
+ if (shape->type() == GEO_SHAPE_MULTI_POLYGON) {
+ auto* multi_polygon =
static_cast<GeoMultiPolygon*>(shape.get());
+ const auto& polygons = multi_polygon->polygons();
+
+ if (polygons.empty()) {
+ null_map_data[row] = 1;
+ offsets.push_back(current_offset);
+ continue;
+ }
+
+ for (const auto& polygon : polygons) {
+ DCHECK(polygon != nullptr);
+ buf.clear();
+ polygon->encode_to(&buf);
+ nested_data->insert_data(buf.data(), buf.size());
+ ++current_offset;
+ }
+ } else {
+ nested_data->insert_data(shape_value.data, shape_value.size);
+ ++current_offset;
+ }
+
+ offsets.push_back(current_offset);
+ }
+
+ auto nested_null_map = ColumnUInt8::create(nested_data->size(), 0);
+ auto nested_nullable =
+ ColumnNullable::create(std::move(nested_data),
std::move(nested_null_map));
+ auto array_col = ColumnArray::create(std::move(nested_nullable),
std::move(offsets_col));
+
+ block.replace_by_position(
+ result, ColumnNullable::create(std::move(array_col),
std::move(null_map)));
+
+ return Status::OK();
+ }
+};
+
+struct StNumPoints {
+ static constexpr auto NAME = "st_numpoints";
+ static const size_t NUM_ARGS = 1;
+ using Type = DataTypeInt64;
+
+ static Status execute(Block& block, const ColumnNumbers& arguments, size_t
result) {
+ DCHECK_EQ(arguments.size(), 1);
+
+ auto col =
ColumnView<TYPE_STRING>::create(block.get_by_position(arguments[0]).column);
+ const auto size = col.size();
+
+ auto res = ColumnInt64::create();
+ res->reserve(size);
+
+ auto null_map = ColumnUInt8::create(size, 0);
+ auto& null_map_data = null_map->get_data();
+
+ for (int row = 0; row < size; ++row) {
+ auto value = col.value_at(row);
+ auto shape = GeoShape::from_encoded(value.data, value.size);
+ if (!shape) {
+ null_map_data[row] = 1;
+ res->insert_default();
+ continue;
+ }
+
+ auto num_points = shape->num_points();
+ if (num_points < 0) {
+ null_map_data[row] = 1;
+ res->insert_default();
+ continue;
+ }
+
+ res->insert_value(num_points);
+ }
+
+ block.replace_by_position(result,
+ ColumnNullable::create(std::move(res),
std::move(null_map)));
+ return Status::OK();
+ }
+};
+
void register_function_geo(SimpleFunctionFactory& factory) {
factory.register_function<GeoFunction<StPoint>>();
factory.register_function<GeoFunction<StAsText<StAsWktName>>>();
@@ -947,6 +1108,10 @@ void register_function_geo(SimpleFunctionFactory&
factory) {
factory.register_function<GeoFunction<StLength>>();
factory.register_function<GeoFunction<StGeometryType>>();
factory.register_function<GeoFunction<StDistance>>();
+ factory.register_function<GeoFunction<StNumGeometries>>();
+ factory.register_function<GeoFunction<StNumPoints>>();
+ factory.register_alias("st_numpoints", "st_npoints");
+ factory.register_function<FunctionStGeometries>();
}
} // namespace doris
diff --git a/be/src/exprs/function/geo/geo_types.cpp
b/be/src/exprs/function/geo/geo_types.cpp
index 03dd5c080c3..6a48a3dc36e 100644
--- a/be/src/exprs/function/geo/geo_types.cpp
+++ b/be/src/exprs/function/geo/geo_types.cpp
@@ -1929,6 +1929,21 @@ double GeoMultiPolygon::Distance(const GeoShape* rhs)
const {
return (min_distance == std::numeric_limits<double>::max()) ? -1.0 :
min_distance;
}
+int GeoPolygon::num_points() const {
+ return _polygon->num_vertices() + _polygon->num_loops();
+}
+
+int GeoMultiPolygon::num_points() const {
+ int total = 0;
+ for (const auto& polygon : _polygons) {
+ DCHECK(polygon != nullptr);
+ int point_count = polygon->num_points();
+ DCHECK_GE(point_count, 0);
+ total += point_count;
+ }
+ return total;
+}
+
double GeoCircle::Distance(const GeoShape* rhs) const {
// Both rhs and self are guaranteed to be valid by StDistance
(functions_geo.cpp)
double circle_radius = S2Earth::ToMeters(_cap->radius());
diff --git a/be/src/exprs/function/geo/geo_types.h
b/be/src/exprs/function/geo/geo_types.h
index f6d8e04e018..146ebf2a847 100644
--- a/be/src/exprs/function/geo/geo_types.h
+++ b/be/src/exprs/function/geo/geo_types.h
@@ -81,6 +81,9 @@ public:
static bool ComputeArea(GeoShape* rhs, double* angle, std::string
square_unit);
+ virtual int num_geometries() const { return 1; }
+ virtual int num_points() const { return -1; }
+
protected:
virtual void encode(std::string* buf) = 0;
virtual bool decode(const void* data, size_t size) = 0;
@@ -125,6 +128,9 @@ public:
double x() const;
double y() const;
+ int num_geometries() const override { return 1; }
+ int num_points() const override { return 1; }
+
protected:
void encode(std::string* buf) override;
bool decode(const void* data, size_t size) override;
@@ -161,6 +167,9 @@ public:
int numPoint() const;
const S2Point* getPoint(int i) const;
+ int num_geometries() const override { return 1; }
+ int num_points() const override { return numPoint(); }
+
protected:
void encode(std::string* buf) override;
bool decode(const void* data, size_t size) override;
@@ -199,6 +208,9 @@ public:
double Distance(const GeoShape* rhs) const override;
S2Loop* getLoop(int i) const;
+ int num_geometries() const override { return 1; }
+ int num_points() const override;
+
protected:
void encode(std::string* buf) override;
bool decode(const void* data, size_t size) override;
@@ -232,6 +244,9 @@ public:
double Length() const override;
double Distance(const GeoShape* rhs) const override;
+ int num_geometries() const override { return
static_cast<int>(_polygons.size()); }
+ int num_points() const override;
+
protected:
void encode(std::string* buf) override;
bool decode(const void* data, size_t size) override;
diff --git a/be/test/exprs/function/geo/functions_geo_test.cpp
b/be/test/exprs/function/geo/functions_geo_test.cpp
new file mode 100644
index 00000000000..dcea4939fe1
--- /dev/null
+++ b/be/test/exprs/function/geo/functions_geo_test.cpp
@@ -0,0 +1,455 @@
+// 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 <gtest/gtest-message.h>
+#include <gtest/gtest-test-part.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "common/status.h"
+#include "core/block/block.h"
+#include "core/column/column_array.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.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 "core/data_type/data_type_string.h"
+#include "core/types.h"
+#include "exprs/function/function_test_util.h"
+#include "exprs/function/geo/geo_common.h"
+#include "exprs/function/geo/geo_types.h"
+#include "exprs/function/simple_function_factory.h"
+#include "gtest/gtest_pred_impl.h"
+#include "testutil/any_type.h"
+
+namespace doris {
+using namespace ut_type;
+
+// ==================== ST_NumGeometries Tests ====================
+
+TEST(VGeoFunctionsTest, function_geo_st_numgeometries_point) {
+ std::string func_name = "st_numgeometries";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ GeoPoint point;
+ auto cur_res = point.from_coord(24.7, 56.7);
+ EXPECT_TRUE(cur_res == GEO_PARSE_OK);
+ std::string buf;
+ point.encode_to(&buf);
+
+ DataSet data_set = {{{buf}, (int64_t)1}, {{Null()}, Null()}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_numgeometries_linestring) {
+ std::string func_name = "st_numgeometries";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ GeoParseStatus status;
+ std::string linestring_wkt = "LINESTRING (30 10, 10 30, 40 40)";
+ auto shape = GeoShape::from_wkt(linestring_wkt.data(),
linestring_wkt.size(), status);
+ EXPECT_TRUE(status == GEO_PARSE_OK);
+ EXPECT_TRUE(shape != nullptr);
+
+ std::string buf;
+ shape->encode_to(&buf);
+
+ // A single linestring has 1 geometry
+ DataSet data_set = {{{buf}, (int64_t)1}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_numgeometries_polygon) {
+ std::string func_name = "st_numgeometries";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ GeoParseStatus status;
+ std::string polygon_wkt = "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))";
+ auto shape = GeoShape::from_wkt(polygon_wkt.data(), polygon_wkt.size(),
status);
+ EXPECT_TRUE(status == GEO_PARSE_OK);
+ EXPECT_TRUE(shape != nullptr);
+
+ std::string buf;
+ shape->encode_to(&buf);
+
+ // A single polygon has 1 geometry
+ DataSet data_set = {{{buf}, (int64_t)1}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_numgeometries_multipolygon) {
+ std::string func_name = "st_numgeometries";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ GeoParseStatus status;
+ std::string multi_polygon_wkt =
+ "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)), ((20 20, 30 20, 30
30, 20 30, 20 "
+ "20)))";
+ auto shape = GeoShape::from_wkt(multi_polygon_wkt.data(),
multi_polygon_wkt.size(), status);
+ EXPECT_TRUE(status == GEO_PARSE_OK);
+ EXPECT_TRUE(shape != nullptr);
+
+ std::string buf;
+ shape->encode_to(&buf);
+
+ // Multi-polygon with 2 polygons
+ DataSet data_set = {{{buf}, (int64_t)2}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_numgeometries_invalid) {
+ std::string func_name = "st_numgeometries";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ std::string invalid_buf = "invalid_geometry_data";
+
+ DataSet data_set = {{{invalid_buf}, Null()}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+// ==================== ST_NumPoints Tests ====================
+
+TEST(VGeoFunctionsTest, function_geo_st_numpoints_point) {
+ std::string func_name = "st_numpoints";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ GeoPoint point;
+ auto cur_res = point.from_coord(24.7, 56.7);
+ EXPECT_TRUE(cur_res == GEO_PARSE_OK);
+ std::string buf;
+ point.encode_to(&buf);
+
+ // A single point has 1 point
+ DataSet data_set = {{{buf}, (int64_t)1}, {{Null()}, Null()}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_numpoints_linestring) {
+ std::string func_name = "st_numpoints";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ GeoParseStatus status;
+ std::string linestring_wkt = "LINESTRING (30 10, 10 30, 40 40)";
+ auto shape = GeoShape::from_wkt(linestring_wkt.data(),
linestring_wkt.size(), status);
+ EXPECT_TRUE(status == GEO_PARSE_OK);
+ EXPECT_TRUE(shape != nullptr);
+
+ std::string buf;
+ shape->encode_to(&buf);
+
+ // Linestring with 3 points
+ DataSet data_set = {{{buf}, (int64_t)3}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_numpoints_polygon) {
+ std::string func_name = "st_numpoints";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ GeoParseStatus status;
+ std::string polygon_wkt = "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))";
+ auto shape = GeoShape::from_wkt(polygon_wkt.data(), polygon_wkt.size(),
status);
+ EXPECT_TRUE(status == GEO_PARSE_OK);
+ EXPECT_TRUE(shape != nullptr);
+
+ std::string buf;
+ shape->encode_to(&buf);
+
+ // Polygon with 5 points (closed ring)
+ DataSet data_set = {{{buf}, (int64_t)5}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_numpoints_multipolygon) {
+ std::string func_name = "st_numpoints";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ GeoParseStatus status;
+ std::string multi_polygon_wkt =
+ "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)), ((20 20, 30 20, 30
30, 20 30, 20 "
+ "20)))";
+ auto shape = GeoShape::from_wkt(multi_polygon_wkt.data(),
multi_polygon_wkt.size(), status);
+ EXPECT_TRUE(status == GEO_PARSE_OK);
+ EXPECT_TRUE(shape != nullptr);
+
+ std::string buf;
+ shape->encode_to(&buf);
+
+ // Multi-polygon with 2 polygons, each with 5 points = 10 total
+ DataSet data_set = {{{buf}, (int64_t)10}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_numpoints_invalid) {
+ std::string func_name = "st_numpoints";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR};
+
+ std::string invalid_buf = "invalid_geometry_data";
+
+ DataSet data_set = {{{invalid_buf}, Null()}};
+
+ static_cast<void>(check_function<DataTypeInt64, true>(func_name,
input_types, data_set));
+}
+
+// ==================== ST_Geometries Tests ====================
+
+TEST(VGeoFunctionsTest, function_geo_st_geometries_point) {
+ std::string func_name = "st_geometries";
+
+ // Prepare input: a single point
+ GeoPoint point;
+ auto cur_res = point.from_coord(24.7, 56.7);
+ EXPECT_TRUE(cur_res == GEO_PARSE_OK);
+ std::string point_buf;
+ point.encode_to(&point_buf);
+
+ // Build input column
+ auto input_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
+ auto return_type = make_nullable(
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeString>())));
+
+ Block block;
+ auto input_col = input_type->create_column();
+ input_col->insert_data(point_buf.data(), point_buf.size());
+ block.insert({std::move(input_col), input_type, "shape"});
+
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ func_name, block.get_columns_with_type_and_name(), return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ FunctionUtils fn_utils(return_type, {input_type}, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ auto st = func->execute(fn_ctx, block, {0}, block.columns() - 1, 1);
+ EXPECT_TRUE(st.ok());
+
+ // Verify result: should be an array with one element (the point itself)
+ auto result_col = block.get_by_position(block.columns() - 1).column;
+ ASSERT_TRUE(result_col);
+ EXPECT_EQ(result_col->size(), 1);
+ // Result should not be null
+ auto* nullable_col = assert_cast<const ColumnNullable*>(result_col.get());
+ EXPECT_FALSE(nullable_col->is_null_at(0));
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_geometries_multipolygon) {
+ std::string func_name = "st_geometries";
+
+ // Prepare input: a multi-polygon with 2 polygons
+ GeoParseStatus status;
+ std::string multi_polygon_wkt =
+ "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)), ((20 20, 30 20, 30
30, 20 30, 20 "
+ "20)))";
+ auto shape = GeoShape::from_wkt(multi_polygon_wkt.data(),
multi_polygon_wkt.size(), status);
+ EXPECT_TRUE(status == GEO_PARSE_OK);
+ EXPECT_TRUE(shape != nullptr);
+
+ std::string multi_buf;
+ shape->encode_to(&multi_buf);
+
+ // Build input column
+ auto input_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
+ auto return_type = make_nullable(
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeString>())));
+
+ Block block;
+ auto input_col = input_type->create_column();
+ input_col->insert_data(multi_buf.data(), multi_buf.size());
+ block.insert({std::move(input_col), input_type, "shape"});
+
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ func_name, block.get_columns_with_type_and_name(), return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ FunctionUtils fn_utils(return_type, {input_type}, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ auto st = func->execute(fn_ctx, block, {0}, block.columns() - 1, 1);
+ EXPECT_TRUE(st.ok());
+
+ // Verify result: should be an array with 2 elements (2 polygons)
+ auto result_col = block.get_by_position(block.columns() - 1).column;
+ ASSERT_TRUE(result_col);
+ EXPECT_EQ(result_col->size(), 1);
+
+ auto* nullable_col = assert_cast<const ColumnNullable*>(result_col.get());
+ EXPECT_FALSE(nullable_col->is_null_at(0));
+
+ // Get the array column and check it has 2 elements
+ auto& nested_col = nullable_col->get_nested_column();
+ auto* array_col = assert_cast<const ColumnArray*>(&nested_col);
+ auto& offsets = array_col->get_offsets();
+ EXPECT_EQ(offsets[0], 2); // 2 polygons in the array
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_geometries_null_input) {
+ std::string func_name = "st_geometries";
+
+ // Build input column with null
+ auto input_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
+ auto return_type = make_nullable(
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeString>())));
+
+ Block block;
+ auto input_col = input_type->create_column();
+ input_col->insert_default(); // insert null
+ block.insert({std::move(input_col), input_type, "shape"});
+
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ func_name, block.get_columns_with_type_and_name(), return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ FunctionUtils fn_utils(return_type, {input_type}, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ auto st = func->execute(fn_ctx, block, {0}, block.columns() - 1, 1);
+ EXPECT_TRUE(st.ok());
+
+ // Verify result: should be null
+ auto result_col = block.get_by_position(block.columns() - 1).column;
+ ASSERT_TRUE(result_col);
+ // Result column may be ColumnConst (optimization for all-null input) or
ColumnNullable
+ EXPECT_TRUE(result_col->is_null_at(0));
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_geometries_invalid) {
+ std::string func_name = "st_geometries";
+
+ // Build input column with invalid geometry data
+ auto input_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
+ auto return_type = make_nullable(
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeString>())));
+
+ std::string invalid_buf = "invalid_geometry_data";
+
+ Block block;
+ auto input_col = input_type->create_column();
+ // Insert non-null but invalid data
+ auto* nullable_input = assert_cast<ColumnNullable*>(input_col.get());
+ nullable_input->get_nested_column_ptr()->insert_data(invalid_buf.data(),
invalid_buf.size());
+
assert_cast<ColumnUInt8*>(nullable_input->get_null_map_column_ptr().get())->insert_value(0);
+ block.insert({std::move(input_col), input_type, "shape"});
+
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ func_name, block.get_columns_with_type_and_name(), return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ FunctionUtils fn_utils(return_type, {input_type}, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ auto st = func->execute(fn_ctx, block, {0}, block.columns() - 1, 1);
+ EXPECT_TRUE(st.ok());
+
+ // Verify result: should be null for invalid geometry
+ auto result_col = block.get_by_position(block.columns() - 1).column;
+ ASSERT_TRUE(result_col);
+ auto* nullable_col = assert_cast<const ColumnNullable*>(result_col.get());
+ EXPECT_TRUE(nullable_col->is_null_at(0));
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL));
+}
+
+TEST(VGeoFunctionsTest, function_geo_st_geometries_single_polygon) {
+ std::string func_name = "st_geometries";
+
+ // Prepare input: a single polygon (not multi)
+ GeoParseStatus status;
+ std::string polygon_wkt = "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))";
+ auto shape = GeoShape::from_wkt(polygon_wkt.data(), polygon_wkt.size(),
status);
+ EXPECT_TRUE(status == GEO_PARSE_OK);
+ EXPECT_TRUE(shape != nullptr);
+
+ std::string polygon_buf;
+ shape->encode_to(&polygon_buf);
+
+ // Build input column
+ auto input_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
+ auto return_type = make_nullable(
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeString>())));
+
+ Block block;
+ auto input_col = input_type->create_column();
+ input_col->insert_data(polygon_buf.data(), polygon_buf.size());
+ block.insert({std::move(input_col), input_type, "shape"});
+
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ func_name, block.get_columns_with_type_and_name(), return_type);
+ ASSERT_TRUE(func != nullptr);
+
+ FunctionUtils fn_utils(return_type, {input_type}, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok());
+ ASSERT_TRUE(func->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok());
+
+ block.insert({nullptr, return_type, "result"});
+ auto st = func->execute(fn_ctx, block, {0}, block.columns() - 1, 1);
+ EXPECT_TRUE(st.ok());
+
+ // Verify result: should be an array with 1 element (the polygon itself)
+ auto result_col = block.get_by_position(block.columns() - 1).column;
+ ASSERT_TRUE(result_col);
+ EXPECT_EQ(result_col->size(), 1);
+
+ auto* nullable_col = assert_cast<const ColumnNullable*>(result_col.get());
+ EXPECT_FALSE(nullable_col->is_null_at(0));
+
+ // Get the array column and check it has 1 element
+ auto& nested_col = nullable_col->get_nested_column();
+ auto* array_col = assert_cast<const ColumnArray*>(&nested_col);
+ auto& offsets = array_col->get_offsets();
+ EXPECT_EQ(offsets[0], 1); // 1 polygon in the array
+
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL));
+}
+
+} // namespace doris
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 32d5fd746e1..eb09cb7ab27 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
@@ -480,6 +480,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.StDisjoint;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StDistance;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StDistanceSphere;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeomFromWKB;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometries;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryFromWKB;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryType;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryfromtext;
@@ -488,6 +489,8 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.StIntersects;
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;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.StNumGeometries;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.StNumPoints;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StPoint;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StPolyfromtext;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolygon;
@@ -1058,6 +1061,9 @@ public class BuiltinScalarFunctions implements
FunctionHelper {
scalar(StTouches.class, "st_touches"),
scalar(StLength.class, "st_length"),
scalar(StGeometryType.class, "st_geometrytype"),
+ scalar(StNumGeometries.class, "st_numgeometries"),
+ scalar(StGeometries.class, "st_geometries"),
+ scalar(StNumPoints.class, "st_numpoints", "st_npoints"),
scalar(StDistance.class, "st_distance"),
scalar(StDistanceSphere.class, "st_distance_sphere"),
scalar(StAngleSphere.class, "st_angle_sphere"),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeometries.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeometries.java
new file mode 100644
index 00000000000..4a7fa629fe8
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeometries.java
@@ -0,0 +1,69 @@
+// 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.ArrayType;
+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_geometries'.
+ */
+public class StGeometries extends ScalarFunction
+ implements UnaryExpression, ExplicitlyCastableSignature,
AlwaysNullable, PropagateNullLiteral {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+ FunctionSignature.ret(ArrayType.of(VarcharType.SYSTEM_DEFAULT))
+ .args(VarcharType.SYSTEM_DEFAULT)
+ );
+
+ public StGeometries(Expression arg0) {
+ super("st_geometries", arg0);
+ }
+
+ private StGeometries(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ @Override
+ public StGeometries withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 1);
+ return new StGeometries(getFunctionParams(children));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitStGeometries(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StNumGeometries.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StNumGeometries.java
new file mode 100644
index 00000000000..0dcdb2c8a27
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StNumGeometries.java
@@ -0,0 +1,68 @@
+// 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.BigIntType;
+import org.apache.doris.nereids.types.VarcharType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Scalar function 'st_numgeometries'. This class is generated by
GenerateFunction.
+ */
+public class StNumGeometries extends ScalarFunction
+ implements UnaryExpression, ExplicitlyCastableSignature,
AlwaysNullable, PropagateNullLiteral {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+
FunctionSignature.ret(BigIntType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT)
+ );
+
+ public StNumGeometries(Expression arg0) {
+ super("st_numgeometries", arg0);
+ }
+
+ private StNumGeometries(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ @Override
+ public StNumGeometries withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 1);
+ return new StNumGeometries(getFunctionParams(children));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitStNumGeometries(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StNumPoints.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StNumPoints.java
new file mode 100644
index 00000000000..15fa580016e
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StNumPoints.java
@@ -0,0 +1,68 @@
+// 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.BigIntType;
+import org.apache.doris.nereids.types.VarcharType;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Scalar function 'st_numpoints'. This class is generated by GenerateFunction.
+ */
+public class StNumPoints extends ScalarFunction
+ implements UnaryExpression, ExplicitlyCastableSignature,
AlwaysNullable, PropagateNullLiteral {
+
+ public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
+
FunctionSignature.ret(BigIntType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT)
+ );
+
+ public StNumPoints(Expression arg0) {
+ super("st_numpoints", arg0);
+ }
+
+ private StNumPoints(ScalarFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ @Override
+ public StNumPoints withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() == 1);
+ return new StNumPoints(getFunctionParams(children));
+ }
+
+ @Override
+ public List<FunctionSignature> getSignatures() {
+ return SIGNATURES;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitStNumPoints(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 2359cddd1e9..52194f5bc5b 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
@@ -500,6 +500,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.StDisjoint;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StDistance;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StDistanceSphere;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeomFromWKB;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometries;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryFromWKB;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryType;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StGeometryfromtext;
@@ -508,6 +509,8 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.StIntersects;
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;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.StNumGeometries;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.StNumPoints;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StPoint;
import
org.apache.doris.nereids.trees.expressions.functions.scalar.StPolyfromtext;
import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolygon;
@@ -2383,6 +2386,18 @@ public interface ScalarFunctionVisitor<R, C> {
return visitScalarFunction(stGeometryType, context);
}
+ default R visitStNumGeometries(StNumGeometries stNumGeometries, C context)
{
+ return visitScalarFunction(stNumGeometries, context);
+ }
+
+ default R visitStNumPoints(StNumPoints stNumPoints, C context) {
+ return visitScalarFunction(stNumPoints, context);
+ }
+
+ default R visitStGeometries(StGeometries stGeometries, C context) {
+ return visitScalarFunction(stGeometries, context);
+ }
+
default R visitStDistance(StDistance stDistance, C context) {
return visitScalarFunction(stDistance, 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
new file mode 100644
index 00000000000..a976e15aeb7
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeoComponentFunctionsTest.java
@@ -0,0 +1,136 @@
+// 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.literal.VarcharLiteral;
+import org.apache.doris.nereids.types.ArrayType;
+import org.apache.doris.nereids.types.BigIntType;
+import org.apache.doris.nereids.types.VarcharType;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+/**
+ * Unit tests for ST_NumGeometries, ST_NumPoints, and ST_Geometries scalar
functions.
+ */
+public class StGeoComponentFunctionsTest {
+
+ @Test
+ public void testStNumGeometriesBasicProperties() {
+ Expression arg = new VarcharLiteral("test");
+ StNumGeometries func = new StNumGeometries(arg);
+
+ Assertions.assertEquals("st_numgeometries", func.getName());
+ Assertions.assertEquals(1, func.arity());
+ Assertions.assertTrue(func.nullable());
+
+ List<FunctionSignature> signatures = func.getSignatures();
+ Assertions.assertEquals(1, signatures.size());
+ Assertions.assertEquals(BigIntType.INSTANCE,
signatures.get(0).returnType);
+ Assertions.assertEquals(VarcharType.SYSTEM_DEFAULT,
signatures.get(0).getArgType(0));
+ }
+
+ @Test
+ public void testStNumGeometriesWithChildren() {
+ Expression arg = new VarcharLiteral("test");
+ StNumGeometries func = new StNumGeometries(arg);
+
+ Expression newArg = new VarcharLiteral("new_test");
+ StNumGeometries newFunc = func.withChildren(ImmutableList.of(newArg));
+
+ Assertions.assertNotSame(func, newFunc);
+ Assertions.assertEquals("st_numgeometries", newFunc.getName());
+ Assertions.assertEquals(1, newFunc.arity());
+ }
+
+ @Test
+ public void testStNumPointsBasicProperties() {
+ Expression arg = new VarcharLiteral("test");
+ StNumPoints func = new StNumPoints(arg);
+
+ Assertions.assertEquals("st_numpoints", func.getName());
+ Assertions.assertEquals(1, func.arity());
+ Assertions.assertTrue(func.nullable());
+
+ List<FunctionSignature> signatures = func.getSignatures();
+ Assertions.assertEquals(1, signatures.size());
+ Assertions.assertEquals(BigIntType.INSTANCE,
signatures.get(0).returnType);
+ Assertions.assertEquals(VarcharType.SYSTEM_DEFAULT,
signatures.get(0).getArgType(0));
+ }
+
+ @Test
+ public void testStNumPointsWithChildren() {
+ Expression arg = new VarcharLiteral("test");
+ StNumPoints func = new StNumPoints(arg);
+
+ Expression newArg = new VarcharLiteral("new_test");
+ StNumPoints newFunc = func.withChildren(ImmutableList.of(newArg));
+
+ Assertions.assertNotSame(func, newFunc);
+ Assertions.assertEquals("st_numpoints", newFunc.getName());
+ Assertions.assertEquals(1, newFunc.arity());
+ }
+
+ @Test
+ public void testStGeometriesBasicProperties() {
+ Expression arg = new VarcharLiteral("test");
+ StGeometries func = new StGeometries(arg);
+
+ Assertions.assertEquals("st_geometries", func.getName());
+ Assertions.assertEquals(1, func.arity());
+ Assertions.assertTrue(func.nullable());
+
+ List<FunctionSignature> signatures = func.getSignatures();
+ Assertions.assertEquals(1, signatures.size());
+ Assertions.assertTrue(signatures.get(0).returnType instanceof
ArrayType);
+ Assertions.assertEquals(VarcharType.SYSTEM_DEFAULT,
signatures.get(0).getArgType(0));
+ }
+
+ @Test
+ public void testStGeometriesWithChildren() {
+ Expression arg = new VarcharLiteral("test");
+ StGeometries func = new StGeometries(arg);
+
+ Expression newArg = new VarcharLiteral("new_test");
+ StGeometries newFunc = func.withChildren(ImmutableList.of(newArg));
+
+ Assertions.assertNotSame(func, newFunc);
+ Assertions.assertEquals("st_geometries", newFunc.getName());
+ Assertions.assertEquals(1, newFunc.arity());
+ }
+
+ @Test
+ public void testStGeometriesReturnType() {
+ Expression arg = new VarcharLiteral("test");
+ StGeometries func = new StGeometries(arg);
+
+ List<FunctionSignature> signatures = func.getSignatures();
+ FunctionSignature sig = signatures.get(0);
+
+ // Return type should be Array<Varchar>
+ Assertions.assertTrue(sig.returnType instanceof ArrayType);
+ ArrayType arrayType = (ArrayType) sig.returnType;
+ Assertions.assertTrue(arrayType.getItemType() instanceof VarcharType);
+ }
+
+}
diff --git
a/regression-test/data/query_p0/sql_functions/spatial_functions/test_st_num_geometries_num_points_and_geometries.out
b/regression-test/data/query_p0/sql_functions/spatial_functions/test_st_num_geometries_num_points_and_geometries.out
new file mode 100644
index 00000000000..5ed11d8b18f
--- /dev/null
+++
b/regression-test/data/query_p0/sql_functions/spatial_functions/test_st_num_geometries_num_points_and_geometries.out
@@ -0,0 +1,336 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !st_numpoints_point_constructor --
+1
+
+-- !st_numpoints_point_wkt --
+1
+
+-- !st_numpoints_linestring_two_points --
+2
+
+-- !st_numpoints_linestring_three_points --
+3
+
+-- !st_numpoints_linestring_four_points --
+4
+
+-- !st_numpoints_triangle_polygon --
+4
+
+-- !st_numpoints_square_polygon --
+5
+
+-- !st_numpoints_polygon_with_one_hole --
+10
+
+-- !st_numpoints_polygon_with_two_holes --
+15
+
+-- !st_numpoints_multipolygon_one_polygon --
+5
+
+-- !st_numpoints_multipolygon_two_polygons --
+10
+
+-- !st_numpoints_multipolygon_three_polygons --
+14
+
+-- !st_numpoints_multipolygon_with_hole --
+15
+
+-- !st_numpoints_circle --
+\N
+
+-- !st_numgeometries_point_constructor --
+1
+
+-- !st_numgeometries_point_wkt --
+1
+
+-- !st_numgeometries_linestring --
+1
+
+-- !st_numgeometries_polygon --
+1
+
+-- !st_numgeometries_polygon_with_hole --
+1
+
+-- !st_numgeometries_multipolygon_one_polygon --
+1
+
+-- !st_numgeometries_multipolygon_two_polygons --
+2
+
+-- !st_numgeometries_multipolygon_three_polygons --
+3
+
+-- !st_numgeometries_multipolygon_with_hole --
+2
+
+-- !st_numgeometries_circle --
+1
+
+-- !st_geometries_point_size --
+1
+
+-- !st_geometries_point_element_type --
+ST_POINT
+
+-- !st_geometries_point_element_text --
+POINT (1 2)
+
+-- !st_geometries_linestring_size --
+1
+
+-- !st_geometries_linestring_element_type --
+ST_LINESTRING
+
+-- !st_geometries_linestring_element_text --
+LINESTRING (0 0, 1 1, 2 2)
+
+-- !st_geometries_polygon_size --
+1
+
+-- !st_geometries_polygon_element_type --
+ST_POLYGON
+
+-- !st_geometries_polygon_element_text --
+POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))
+
+-- !st_geometries_polygon_with_hole_size --
+1
+
+-- !st_geometries_polygon_with_hole_element_type --
+ST_POLYGON
+
+-- !st_geometries_multipolygon_one_polygon_size --
+1
+
+-- !st_geometries_multipolygon_one_polygon_element_1_type --
+ST_POLYGON
+
+-- !st_geometries_multipolygon_two_polygons_size --
+2
+
+-- !st_geometries_multipolygon_two_polygons_element_1_type --
+ST_POLYGON
+
+-- !st_geometries_multipolygon_two_polygons_element_2_type --
+ST_POLYGON
+
+-- !st_geometries_multipolygon_two_polygons_element_1_text --
+POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))
+
+-- !st_geometries_multipolygon_two_polygons_element_2_text --
+POLYGON ((3 2, 3 3, 2 3, 2 2, 3 2))
+
+-- !st_geometries_multipolygon_two_polygons_element_3_out_of_bound --
+\N
+
+-- !st_geometries_multipolygon_three_polygons_size --
+3
+
+-- !st_geometries_multipolygon_three_polygons_element_3_type --
+ST_POLYGON
+
+-- !st_geometries_multipolygon_with_hole_size --
+2
+
+-- !st_geometries_multipolygon_with_hole_element_1_type --
+ST_POLYGON
+
+-- !st_geometries_multipolygon_with_hole_element_2_type --
+ST_POLYGON
+
+-- !st_geometries_circle_size --
+1
+
+-- !st_geometries_circle_element_type --
+ST_CIRCLE
+
+-- !st_geometries_circle_element_2_out_of_bound --
+\N
+
+-- !st_numpoints_null_literal --
+\N
+
+-- !st_numgeometries_null_literal --
+\N
+
+-- !st_geometries_null_literal_size --
+\N
+
+-- !st_numpoints_geomfromtext_null --
+\N
+
+-- !st_numgeometries_geomfromtext_null --
+\N
+
+-- !st_geometries_geomfromtext_null_size --
+\N
+
+-- !st_numpoints_invalid_encoded_string --
+\N
+
+-- !st_numgeometries_invalid_encoded_string --
+\N
+
+-- !st_geometries_invalid_encoded_string_size --
+\N
+
+-- !st_numpoints_invalid_wkt --
+\N
+
+-- !st_numgeometries_invalid_wkt --
+\N
+
+-- !st_geometries_invalid_wkt_size --
+\N
+
+-- !st_numpoints_invalid_polygon_not_closed --
+\N
+
+-- !st_numgeometries_invalid_polygon_not_closed --
+\N
+
+-- !st_geometries_invalid_polygon_not_closed_size --
+\N
+
+-- !st_numpoints_lowercase_name --
+3
+
+-- !st_numgeometries_lowercase_name --
+2
+
+-- !st_geometries_lowercase_name_size --
+2
+
+-- !st_numpoints_geometryfromtext_alias --
+4
+
+-- !st_numgeometries_geometryfromtext_alias --
+1
+
+-- !st_geometries_geometryfromtext_alias_element_2_type --
+ST_POLYGON
+
+-- !st_num_points_table_ordered --
+1 1
+2 2
+3 4
+4 5
+5 10
+6 5
+7 10
+8 15
+9 \N
+
+-- !st_num_geometries_table_ordered --
+1 1
+2 1
+3 1
+4 1
+5 1
+6 1
+7 2
+8 2
+9 \N
+
+-- !st_geometries_table_size_ordered --
+1 1
+2 1
+3 1
+4 1
+5 1
+6 1
+7 2
+8 2
+9 \N
+
+-- !st_geometries_table_first_element_type_ordered --
+1 ST_POINT
+2 ST_LINESTRING
+3 ST_LINESTRING
+4 ST_POLYGON
+5 ST_POLYGON
+6 ST_POLYGON
+7 ST_POLYGON
+8 ST_POLYGON
+9 \N
+
+-- !st_geometries_table_second_element_type_ordered --
+1 \N
+2 \N
+3 \N
+4 \N
+5 \N
+6 \N
+7 ST_POLYGON
+8 ST_POLYGON
+9 \N
+
+-- !st_all_three_functions_table_summary_ordered --
+1 ST_POINT 1 1 1 ST_POINT \N
+2 ST_LINESTRING 2 1 1 ST_LINESTRING \N
+3 ST_LINESTRING 4 1 1 ST_LINESTRING \N
+4 ST_POLYGON 5 1 1 ST_POLYGON \N
+5 ST_POLYGON 10 1 1 ST_POLYGON \N
+6 ST_MULTIPOLYGON 5 1 1 ST_POLYGON \N
+7 ST_MULTIPOLYGON 10 2 2 ST_POLYGON ST_POLYGON
+8 ST_MULTIPOLYGON 15 2 2 ST_POLYGON ST_POLYGON
+9 \N \N \N \N \N \N
+
+-- !st_numpoints_arithmetic --
+13
+
+-- !st_numgeometries_arithmetic --
+12
+
+-- !st_numpoints_predicate --
+4
+5
+6
+7
+8
+
+-- !st_numgeometries_predicate --
+7
+8
+
+-- !st_geometries_predicate --
+7
+8
+
+-- !st_npoints_point --
+1
+
+-- !st_npoints_linestring --
+3
+
+-- !st_npoints_polygon --
+5
+
+-- !st_npoints_multipolygon --
+10
+
+-- !st_npoints_circle --
+\N
+
+-- !st_npoints_null --
+\N
+
+-- !st_npoints_invalid --
+\N
+
+-- !st_npoints_table_ordered --
+1 1
+2 2
+3 4
+4 5
+5 10
+6 5
+7 10
+8 15
+9 \N
+
diff --git
a/regression-test/suites/query_p0/sql_functions/spatial_functions/test_st_num_geometries_num_points_and_geometries.groovy
b/regression-test/suites/query_p0/sql_functions/spatial_functions/test_st_num_geometries_num_points_and_geometries.groovy
new file mode 100644
index 00000000000..8dee896f4bf
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/spatial_functions/test_st_num_geometries_num_points_and_geometries.groovy
@@ -0,0 +1,544 @@
+// 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_num_geometries_num_points_and_geometries", "arrow_flight_sql") {
+ sql "set batch_size = 4096;"
+
+ // ----------------------------------------------------------------------
+ // ST_NumPoints: simple geometry inputs
+ // ----------------------------------------------------------------------
+
+ qt_st_numpoints_point_constructor """
+ select ST_NumPoints(ST_Point(1, 2));
+ """
+
+ qt_st_numpoints_point_wkt """
+ select ST_NumPoints(ST_GeomFromText('POINT(1 2)'));
+ """
+
+ qt_st_numpoints_linestring_two_points """
+ select ST_NumPoints(ST_GeomFromText('LINESTRING(0 0, 1 1)'));
+ """
+
+ qt_st_numpoints_linestring_three_points """
+ select ST_NumPoints(ST_GeomFromText('LINESTRING(0 0, 1 1, 2 2)'));
+ """
+
+ qt_st_numpoints_linestring_four_points """
+ select ST_NumPoints(ST_GeomFromText('LINESTRING(0 0, 1 0, 1 1, 0 1)'));
+ """
+
+ qt_st_numpoints_triangle_polygon """
+ select ST_NumPoints(ST_GeomFromText('POLYGON((0 0, 1 0, 0 1, 0 0))'));
+ """
+
+ qt_st_numpoints_square_polygon """
+ select ST_NumPoints(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0
0))'));
+ """
+
+ qt_st_numpoints_polygon_with_one_hole """
+ select ST_NumPoints(ST_GeomFromText(
+ 'POLYGON((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 1 3, 3 3, 3 1, 1 1))'
+ ));
+ """
+
+ qt_st_numpoints_polygon_with_two_holes """
+ select ST_NumPoints(ST_GeomFromText(
+ 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1),
(7 7, 7 8, 8 8, 8 7, 7 7))'
+ ));
+ """
+
+ qt_st_numpoints_multipolygon_one_polygon """
+ select ST_NumPoints(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))'
+ ));
+ """
+
+ qt_st_numpoints_multipolygon_two_polygons """
+ select ST_NumPoints(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ ));
+ """
+
+ qt_st_numpoints_multipolygon_three_polygons """
+ select ST_NumPoints(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)), ((5 5, 6 5, 5 6, 5 5)))'
+ ));
+ """
+
+ qt_st_numpoints_multipolygon_with_hole """
+ select ST_NumPoints(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 10, 10 10, 10 0, 0 0), (2 2, 2 8, 8 8, 8 2,
2 2)), ((20 20, 20 21, 21 21, 21 20, 20 20)))'
+ ));
+ """
+
+ qt_st_numpoints_circle """
+ select ST_NumPoints(ST_Circle(0, 0, 1000));
+ """
+
+ // ----------------------------------------------------------------------
+ // ST_NumGeometries: simple geometry and multi geometry inputs
+ // ----------------------------------------------------------------------
+
+ qt_st_numgeometries_point_constructor """
+ select ST_NumGeometries(ST_Point(1, 2));
+ """
+
+ qt_st_numgeometries_point_wkt """
+ select ST_NumGeometries(ST_GeomFromText('POINT(1 2)'));
+ """
+
+ qt_st_numgeometries_linestring """
+ select ST_NumGeometries(ST_GeomFromText('LINESTRING(0 0, 1 1, 2 2)'));
+ """
+
+ qt_st_numgeometries_polygon """
+ select ST_NumGeometries(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1,
0 0))'));
+ """
+
+ qt_st_numgeometries_polygon_with_hole """
+ select ST_NumGeometries(ST_GeomFromText(
+ 'POLYGON((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 1 3, 3 3, 3 1, 1 1))'
+ ));
+ """
+
+ qt_st_numgeometries_multipolygon_one_polygon """
+ select ST_NumGeometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))'
+ ));
+ """
+
+ qt_st_numgeometries_multipolygon_two_polygons """
+ select ST_NumGeometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ ));
+ """
+
+ qt_st_numgeometries_multipolygon_three_polygons """
+ select ST_NumGeometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)), ((5 5, 6 5, 5 6, 5 5)))'
+ ));
+ """
+
+ qt_st_numgeometries_multipolygon_with_hole """
+ select ST_NumGeometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 10, 10 10, 10 0, 0 0), (2 2, 2 8, 8 8, 8 2,
2 2)), ((20 20, 20 21, 21 21, 21 20, 20 20)))'
+ ));
+ """
+
+ qt_st_numgeometries_circle """
+ select ST_NumGeometries(ST_Circle(0, 0, 1000));
+ """
+
+ // ----------------------------------------------------------------------
+ // ST_Geometries: simple geometry should return array with one element
+ // ----------------------------------------------------------------------
+
+ qt_st_geometries_point_size """
+ select ARRAY_SIZE(ST_Geometries(ST_Point(1, 2)));
+ """
+
+ qt_st_geometries_point_element_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_Point(1, 2)), 1));
+ """
+
+ qt_st_geometries_point_element_text """
+ select ST_AsText(ELEMENT_AT(ST_Geometries(ST_Point(1, 2)), 1));
+ """
+
+ qt_st_geometries_linestring_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText('LINESTRING(0 0, 1 1,
2 2)')));
+ """
+
+ qt_st_geometries_linestring_element_type """
+ select
ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText('LINESTRING(0 0, 1 1,
2 2)')), 1));
+ """
+
+ qt_st_geometries_linestring_element_text """
+ select
ST_AsText(ELEMENT_AT(ST_Geometries(ST_GeomFromText('LINESTRING(0 0, 1 1, 2
2)')), 1));
+ """
+
+ qt_st_geometries_polygon_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText('POLYGON((0 0, 1 0, 1
1, 0 1, 0 0))')));
+ """
+
+ qt_st_geometries_polygon_element_type """
+ select
ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText('POLYGON((0 0, 1 0, 1
1, 0 1, 0 0))')), 1));
+ """
+
+ qt_st_geometries_polygon_element_text """
+ select ST_AsText(ELEMENT_AT(ST_Geometries(ST_GeomFromText('POLYGON((0
0, 1 0, 1 1, 0 1, 0 0))')), 1));
+ """
+
+ qt_st_geometries_polygon_with_hole_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText(
+ 'POLYGON((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 1 3, 3 3, 3 1, 1 1))'
+ )));
+ """
+
+ qt_st_geometries_polygon_with_hole_element_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'POLYGON((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 1 3, 3 3, 3 1, 1 1))'
+ )), 1));
+ """
+
+ // ----------------------------------------------------------------------
+ // ST_Geometries: multipolygon should be split into polygon elements
+ // ----------------------------------------------------------------------
+
+ qt_st_geometries_multipolygon_one_polygon_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))'
+ )));
+ """
+
+ qt_st_geometries_multipolygon_one_polygon_element_1_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))'
+ )), 1));
+ """
+
+ qt_st_geometries_multipolygon_two_polygons_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )));
+ """
+
+ qt_st_geometries_multipolygon_two_polygons_element_1_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )), 1));
+ """
+
+ qt_st_geometries_multipolygon_two_polygons_element_2_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )), 2));
+ """
+
+ qt_st_geometries_multipolygon_two_polygons_element_1_text """
+ select ST_AsText(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )), 1));
+ """
+
+ qt_st_geometries_multipolygon_two_polygons_element_2_text """
+ select ST_AsText(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )), 2));
+ """
+
+ qt_st_geometries_multipolygon_two_polygons_element_3_out_of_bound """
+ select ST_AsText(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )), 3));
+ """
+
+ qt_st_geometries_multipolygon_three_polygons_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)), ((5 5, 6 5, 5 6, 5 5)))'
+ )));
+ """
+
+ qt_st_geometries_multipolygon_three_polygons_element_3_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)), ((5 5, 6 5, 5 6, 5 5)))'
+ )), 3));
+ """
+
+ qt_st_geometries_multipolygon_with_hole_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 10, 10 10, 10 0, 0 0), (2 2, 2 8, 8 8, 8 2,
2 2)), ((20 20, 20 21, 21 21, 21 20, 20 20)))'
+ )));
+ """
+
+ qt_st_geometries_multipolygon_with_hole_element_1_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 10, 10 10, 10 0, 0 0), (2 2, 2 8, 8 8, 8 2,
2 2)), ((20 20, 20 21, 21 21, 21 20, 20 20)))'
+ )), 1));
+ """
+
+ qt_st_geometries_multipolygon_with_hole_element_2_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 10, 10 10, 10 0, 0 0), (2 2, 2 8, 8 8, 8 2,
2 2)), ((20 20, 20 21, 21 21, 21 20, 20 20)))'
+ )), 2));
+ """
+
+ // ----------------------------------------------------------------------
+ // ST_Geometries: circle behavior
+ // Circle is a Doris-specific shape. It should be treated as one geometry.
+ // ----------------------------------------------------------------------
+
+ qt_st_geometries_circle_size """
+ select ARRAY_SIZE(ST_Geometries(ST_Circle(0, 0, 1000)));
+ """
+
+ qt_st_geometries_circle_element_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_Circle(0, 0,
1000)), 1));
+ """
+
+ qt_st_geometries_circle_element_2_out_of_bound """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_Circle(0, 0,
1000)), 2));
+ """
+
+ // ----------------------------------------------------------------------
+ // NULL and invalid inputs
+ // ----------------------------------------------------------------------
+
+ qt_st_numpoints_null_literal """
+ select ST_NumPoints(null);
+ """
+
+ qt_st_numgeometries_null_literal """
+ select ST_NumGeometries(null);
+ """
+
+ qt_st_geometries_null_literal_size """
+ select ARRAY_SIZE(ST_Geometries(null));
+ """
+
+ qt_st_numpoints_geomfromtext_null """
+ select ST_NumPoints(ST_GeomFromText(null));
+ """
+
+ qt_st_numgeometries_geomfromtext_null """
+ select ST_NumGeometries(ST_GeomFromText(null));
+ """
+
+ qt_st_geometries_geomfromtext_null_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText(null)));
+ """
+
+ qt_st_numpoints_invalid_encoded_string """
+ select ST_NumPoints('abc');
+ """
+
+ qt_st_numgeometries_invalid_encoded_string """
+ select ST_NumGeometries('abc');
+ """
+
+ qt_st_geometries_invalid_encoded_string_size """
+ select ARRAY_SIZE(ST_Geometries('abc'));
+ """
+
+ qt_st_numpoints_invalid_wkt """
+ select ST_NumPoints(ST_GeomFromText('INVALID(0 0)'));
+ """
+
+ qt_st_numgeometries_invalid_wkt """
+ select ST_NumGeometries(ST_GeomFromText('INVALID(0 0)'));
+ """
+
+ qt_st_geometries_invalid_wkt_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText('INVALID(0 0)')));
+ """
+
+ qt_st_numpoints_invalid_polygon_not_closed """
+ select ST_NumPoints(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1))'));
+ """
+
+ qt_st_numgeometries_invalid_polygon_not_closed """
+ select ST_NumGeometries(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0
1))'));
+ """
+
+ qt_st_geometries_invalid_polygon_not_closed_size """
+ select ARRAY_SIZE(ST_Geometries(ST_GeomFromText('POLYGON((0 0, 1 0, 1
1, 0 1))')));
+ """
+
+ // ----------------------------------------------------------------------
+ // Lowercase function names and ST_GeometryFromText alias coverage
+ // ----------------------------------------------------------------------
+
+ qt_st_numpoints_lowercase_name """
+ select st_numpoints(ST_GeomFromText('LINESTRING(0 0, 1 1, 2 2)'));
+ """
+
+ qt_st_numgeometries_lowercase_name """
+ select st_numgeometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ ));
+ """
+
+ qt_st_geometries_lowercase_name_size """
+ select ARRAY_SIZE(st_geometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )));
+ """
+
+ qt_st_numpoints_geometryfromtext_alias """
+ select ST_NumPoints(ST_GeometryFromText('LINESTRING(0 0, 1 1, 2 2, 3
3)'));
+ """
+
+ qt_st_numgeometries_geometryfromtext_alias """
+ select ST_NumGeometries(ST_GeometryFromText('POLYGON((0 0, 1 0, 1 1, 0
1, 0 0))'));
+ """
+
+ qt_st_geometries_geometryfromtext_alias_element_2_type """
+ select ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeometryFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )), 2));
+ """
+
+ // ----------------------------------------------------------------------
+ // Table-driven tests: column input, ordering, NULL row, mixed geometry
types
+ // ----------------------------------------------------------------------
+
+ sql "drop table if exists test_st_geometry_component_functions_cases"
+ sql """
+ CREATE TABLE test_st_geometry_component_functions_cases (
+ `id` int NOT NULL,
+ `wkt` varchar(2048) NULL
+ ) ENGINE=OLAP
+ UNIQUE KEY(`id`)
+ COMMENT 'OLAP'
+ DISTRIBUTED BY HASH(`id`) BUCKETS 4
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );
+ """
+
+ sql """
+ insert into test_st_geometry_component_functions_cases values
+ (1, 'POINT(0 0)'),
+ (2, 'LINESTRING(0 0, 0 1)'),
+ (3, 'LINESTRING(0 0, 1 0, 1 1, 0 1)'),
+ (4, 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'),
+ (5, 'POLYGON((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 1 3, 3 3, 3 1, 1 1))'),
+ (6, 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))'),
+ (7, 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'),
+ (8, 'MULTIPOLYGON(((0 0, 0 10, 10 10, 10 0, 0 0), (2 2, 2 8, 8 8, 8 2,
2 2)), ((20 20, 20 21, 21 21, 21 20, 20 20)))'),
+ (9, null);
+ """
+
+ qt_st_num_points_table_ordered """
+ select id, ST_NumPoints(ST_GeomFromText(wkt))
+ from test_st_geometry_component_functions_cases
+ order by id;
+ """
+
+ qt_st_num_geometries_table_ordered """
+ select id, ST_NumGeometries(ST_GeomFromText(wkt))
+ from test_st_geometry_component_functions_cases
+ order by id;
+ """
+
+ qt_st_geometries_table_size_ordered """
+ select id, ARRAY_SIZE(ST_Geometries(ST_GeomFromText(wkt)))
+ from test_st_geometry_component_functions_cases
+ order by id;
+ """
+
+ qt_st_geometries_table_first_element_type_ordered """
+ select id,
ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(wkt)), 1))
+ from test_st_geometry_component_functions_cases
+ order by id;
+ """
+
+ qt_st_geometries_table_second_element_type_ordered """
+ select id,
ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(wkt)), 2))
+ from test_st_geometry_component_functions_cases
+ order by id;
+ """
+
+ qt_st_all_three_functions_table_summary_ordered """
+ select
+ id,
+ ST_GeometryType(ST_GeomFromText(wkt)),
+ ST_NumPoints(ST_GeomFromText(wkt)),
+ ST_NumGeometries(ST_GeomFromText(wkt)),
+ ARRAY_SIZE(ST_Geometries(ST_GeomFromText(wkt))),
+ ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(wkt)),
1)),
+ ST_GeometryType(ELEMENT_AT(ST_Geometries(ST_GeomFromText(wkt)), 2))
+ from test_st_geometry_component_functions_cases
+ order by id;
+ """
+
+ // ----------------------------------------------------------------------
+ // Return values used in arithmetic and predicates
+ // ----------------------------------------------------------------------
+
+ qt_st_numpoints_arithmetic """
+ select ST_NumPoints(ST_GeomFromText('LINESTRING(0 0, 1 1, 2 2)')) + 10;
+ """
+
+ qt_st_numgeometries_arithmetic """
+ select ST_NumGeometries(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ )) + 10;
+ """
+
+ qt_st_numpoints_predicate """
+ select id
+ from test_st_geometry_component_functions_cases
+ where ST_NumPoints(ST_GeomFromText(wkt)) >= 5
+ order by id;
+ """
+
+ qt_st_numgeometries_predicate """
+ select id
+ from test_st_geometry_component_functions_cases
+ where ST_NumGeometries(ST_GeomFromText(wkt)) >= 2
+ order by id;
+ """
+
+ qt_st_geometries_predicate """
+ select id
+ from test_st_geometry_component_functions_cases
+ where ARRAY_SIZE(ST_Geometries(ST_GeomFromText(wkt))) >= 2
+ order by id;
+ """
+
+ // ----------------------------------------------------------------------
+ // ST_NPoints: alias for ST_NumPoints
+ // ----------------------------------------------------------------------
+
+ qt_st_npoints_point """
+ select ST_NPoints(ST_Point(1, 2));
+ """
+
+ qt_st_npoints_linestring """
+ select ST_NPoints(ST_GeomFromText('LINESTRING(0 0, 1 1, 2 2)'));
+ """
+
+ qt_st_npoints_polygon """
+ select ST_NPoints(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0
0))'));
+ """
+
+ qt_st_npoints_multipolygon """
+ select ST_NPoints(ST_GeomFromText(
+ 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2
2)))'
+ ));
+ """
+
+ qt_st_npoints_circle """
+ select ST_NPoints(ST_Circle(0, 0, 1000));
+ """
+
+ qt_st_npoints_null """
+ select ST_NPoints(null);
+ """
+
+ qt_st_npoints_invalid """
+ select ST_NPoints('abc');
+ """
+
+ qt_st_npoints_table_ordered """
+ select id, ST_NPoints(ST_GeomFromText(wkt))
+ from test_st_geometry_component_functions_cases
+ order by id;
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]