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]

Reply via email to