From b446d3df98d0f5ecdc5cdd2b10bfa4bc01577d9c Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Sun, 16 Aug 2026 17:23:08 +0800 Subject: [PATCH 1/9] [feature](geo) Add bounding box accessor to GeoShape ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: Add a virtual bounding_box() accessor to GeoShape so that the Trino compatible ST_XMax/ST_XMin/ST_YMax/ST_YMin functions can be implemented on top of it. Implemented for GeoPoint, GeoLine, GeoPolygon, GeoMultiPolygon and GeoCircle (circle treated as its center point). ### Release note None ### Check List (For Author) - Test: No need to test (accessor only, functions come in later commits) - Behavior changed: No - Does this need documentation: No --- be/src/exprs/function/geo/geo_types.cpp | 83 +++++++++++++++++++++++++ be/src/exprs/function/geo/geo_types.h | 14 +++++ 2 files changed, 97 insertions(+) diff --git a/be/src/exprs/function/geo/geo_types.cpp b/be/src/exprs/function/geo/geo_types.cpp index 6a48a3dc36e819..03c8716c9dc8b7 100644 --- a/be/src/exprs/function/geo/geo_types.cpp +++ b/be/src/exprs/function/geo/geo_types.cpp @@ -37,6 +37,7 @@ #include "core/assert_cast.h" // IWYU pragma: no_include +#include #include #include #include @@ -53,6 +54,31 @@ namespace doris { constexpr double TOLERANCE = 1e-6; +namespace { + +// NaN-safe min/max, used to merge bounding boxes of multiple geometries. +double nan_min(double a, double b) { + if (std::isnan(a)) { + return b; + } + if (std::isnan(b)) { + return a; + } + return std::min(a, b); +} + +double nan_max(double a, double b) { + if (std::isnan(a)) { + return b; + } + if (std::isnan(b)) { + return a; + } + return std::max(a, b); +} + +} // namespace + GeoPoint::GeoPoint() : _point(new S2Point()) {} GeoPoint::~GeoPoint() = default; @@ -613,6 +639,11 @@ double GeoPoint::y() const { return std::stod(absl::StrFormat("%.13f", S2LatLng::Latitude(*_point).degrees())); } +BoundingBox GeoPoint::bounding_box() const { + // A point degenerates to a box with zero width and height. + return {x(), x(), y(), y()}; +} + std::string GeoPoint::as_wkt() const { std::stringstream ss; ss << "POINT ("; @@ -787,6 +818,20 @@ const S2Point* GeoLine::getPoint(int i) const { return &(_polyline->vertex(i)); } +BoundingBox GeoLine::bounding_box() const { + BoundingBox box; + for (int i = 0; i < numPoint(); ++i) { + const S2Point& p = *getPoint(i); + const double lon = S2LatLng::Longitude(p).degrees(); + const double lat = S2LatLng::Latitude(p).degrees(); + box.x_max = nan_max(box.x_max, lon); + box.x_min = nan_min(box.x_min, lon); + box.y_max = nan_max(box.y_max, lat); + box.y_min = nan_min(box.y_min, lat); + } + return box; +} + GeoParseStatus GeoPolygon::from_coords(const GeoCoordinateListList& list) { return to_s2polygon(list, &_polygon); } @@ -1109,6 +1154,23 @@ S2Loop* GeoPolygon::getLoop(int i) const { return _polygon->loop(i); } +BoundingBox GeoPolygon::bounding_box() const { + BoundingBox box; + for (int loop_idx = 0; loop_idx < numLoops(); ++loop_idx) { + S2Loop* loop = getLoop(loop_idx); + for (int i = 0; i < loop->num_vertices(); ++i) { + const S2Point& p = loop->vertex(i); + const double lon = S2LatLng::Longitude(p).degrees(); + const double lat = S2LatLng::Latitude(p).degrees(); + box.x_max = nan_max(box.x_max, lon); + box.x_min = nan_min(box.x_min, lon); + box.y_max = nan_max(box.y_max, lat); + box.y_min = nan_min(box.y_min, lat); + } + } + return box; +} + GeoParseStatus GeoMultiPolygon::from_coords(const std::vector& list) { _polygons.clear(); for (const auto& coords_list : list) { @@ -1745,6 +1807,18 @@ double GeoMultiPolygon::Length() const { return total_length; } +BoundingBox GeoMultiPolygon::bounding_box() const { + BoundingBox box; + for (const auto& polygon : _polygons) { + BoundingBox sub_box = polygon->bounding_box(); + box.x_max = nan_max(box.x_max, sub_box.x_max); + box.x_min = nan_min(box.x_min, sub_box.x_min); + box.y_max = nan_max(box.y_max, sub_box.y_max); + box.y_min = nan_min(box.y_min, sub_box.y_min); + } + return box; +} + double GeoCircle::Length() const { // GeoCircle is always valid (guaranteed by constructor) // Get the radius in meters @@ -1754,6 +1828,15 @@ double GeoCircle::Length() const { return 2.0 * M_PI * radius_meters; } +BoundingBox GeoCircle::bounding_box() const { + // Treat the circle as its center point (radius is in meters, so the exact + // bounding box depends on the projection; keep it simple like a point). + const S2Point& center = _cap->center(); + const double lon = S2LatLng::Longitude(center).degrees(); + const double lat = S2LatLng::Latitude(center).degrees(); + return {lon, lon, lat, lat}; +} + double GeoPoint::Distance(const GeoShape* rhs) const { // rhs is guaranteed to be valid by StDistance (functions_geo.cpp) switch (rhs->type()) { diff --git a/be/src/exprs/function/geo/geo_types.h b/be/src/exprs/function/geo/geo_types.h index 146ebf2a847603..750617413ff295 100644 --- a/be/src/exprs/function/geo/geo_types.h +++ b/be/src/exprs/function/geo/geo_types.h @@ -19,6 +19,7 @@ #include +#include #include #include #include @@ -40,6 +41,15 @@ using S2Point = Vector3_d; namespace doris { +// Bounding box of a geometry, backing the Trino compatible functions +// ST_XMax / ST_XMin / ST_YMax / ST_YMin. Values are NaN when not available. +struct BoundingBox { + double x_max = std::numeric_limits::quiet_NaN(); + double x_min = std::numeric_limits::quiet_NaN(); + double y_max = std::numeric_limits::quiet_NaN(); + double y_min = std::numeric_limits::quiet_NaN(); +}; + class GeoShape { public: virtual ~GeoShape() = default; @@ -84,6 +94,10 @@ class GeoShape { virtual int num_geometries() const { return 1; } virtual int num_points() const { return -1; } + // Bounding box of the shape. Returns an all-NaN box for shape types that do + // not support the accessor. + virtual BoundingBox bounding_box() const { return {}; } + protected: virtual void encode(std::string* buf) = 0; virtual bool decode(const void* data, size_t size) = 0; From b5819215e892dd0a84265996ea7eea8cf15ffa31 Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Sun, 16 Aug 2026 17:24:43 +0800 Subject: [PATCH 2/9] [feature](geo) Add st_xmax/st_xmin/st_ymax/st_ymin functions ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: Add the four Trino compatible bounding box accessor functions. Each function decodes the input geometry via GeoShape::from_encoded and returns the corresponding field of GeoShape::bounding_box(); NULL is returned for invalid input or shape types without a bounding box. ### Release note Add st_xmax/st_xmin/st_ymax/st_ymin functions. ### Check List (For Author) - Test: No need to test (regression tests come in a later commit) - Behavior changed: Yes - Does this need documentation: Yes (doc PR will be linked) --- be/src/exprs/function/geo/functions_geo.cpp | 165 ++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/be/src/exprs/function/geo/functions_geo.cpp b/be/src/exprs/function/geo/functions_geo.cpp index b4967780d2c8dc..8b05644e0b890e 100644 --- a/be/src/exprs/function/geo/functions_geo.cpp +++ b/be/src/exprs/function/geo/functions_geo.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include "common/compiler_util.h" @@ -196,6 +197,166 @@ struct StY { } }; +// Bounding box accessors, Trino compatible: +// st_xmax / st_xmin / st_ymax / st_ymin return the max/min X (longitude) and +// Y (latitude) of a geometry. All shape types are decoded via GeoShape, and the +// result is NULL when the value is not available. +struct StXMax { + static constexpr auto NAME = "st_xmax"; + static const size_t NUM_ARGS = 1; + using Type = DataTypeFloat64; + static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { + DCHECK_EQ(arguments.size(), 1); + auto& input = block.get_by_position(arguments[0]).column; + + auto size = input->size(); + + auto res = ColumnFloat64::create(); + auto null_map = ColumnUInt8::create(size, 0); + auto& null_map_data = null_map->get_data(); + res->reserve(size); + + for (int row = 0; row < size; ++row) { + auto value = input->get_data_at(row); + auto shape = GeoShape::from_encoded(value.data, value.size); + + if (shape == nullptr) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + const double x_max = shape->bounding_box().x_max; + if (std::isnan(x_max)) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + res->insert_value(x_max); + } + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + + return Status::OK(); + } +}; + +struct StXMin { + static constexpr auto NAME = "st_xmin"; + static const size_t NUM_ARGS = 1; + using Type = DataTypeFloat64; + static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { + DCHECK_EQ(arguments.size(), 1); + auto& input = block.get_by_position(arguments[0]).column; + + auto size = input->size(); + + auto res = ColumnFloat64::create(); + auto null_map = ColumnUInt8::create(size, 0); + auto& null_map_data = null_map->get_data(); + res->reserve(size); + + for (int row = 0; row < size; ++row) { + auto value = input->get_data_at(row); + auto shape = GeoShape::from_encoded(value.data, value.size); + + if (shape == nullptr) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + const double x_min = shape->bounding_box().x_min; + if (std::isnan(x_min)) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + res->insert_value(x_min); + } + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + + return Status::OK(); + } +}; + +struct StYMax { + static constexpr auto NAME = "st_ymax"; + static const size_t NUM_ARGS = 1; + using Type = DataTypeFloat64; + static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { + DCHECK_EQ(arguments.size(), 1); + auto& input = block.get_by_position(arguments[0]).column; + + auto size = input->size(); + + auto res = ColumnFloat64::create(); + auto null_map = ColumnUInt8::create(size, 0); + auto& null_map_data = null_map->get_data(); + res->reserve(size); + + for (int row = 0; row < size; ++row) { + auto value = input->get_data_at(row); + auto shape = GeoShape::from_encoded(value.data, value.size); + + if (shape == nullptr) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + const double y_max = shape->bounding_box().y_max; + if (std::isnan(y_max)) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + res->insert_value(y_max); + } + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + + return Status::OK(); + } +}; + +struct StYMin { + static constexpr auto NAME = "st_ymin"; + static const size_t NUM_ARGS = 1; + using Type = DataTypeFloat64; + static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { + DCHECK_EQ(arguments.size(), 1); + auto& input = block.get_by_position(arguments[0]).column; + + auto size = input->size(); + + auto res = ColumnFloat64::create(); + auto null_map = ColumnUInt8::create(size, 0); + auto& null_map_data = null_map->get_data(); + res->reserve(size); + + for (int row = 0; row < size; ++row) { + auto value = input->get_data_at(row); + auto shape = GeoShape::from_encoded(value.data, value.size); + + if (shape == nullptr) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + const double y_min = shape->bounding_box().y_min; + if (std::isnan(y_min)) { + null_map_data[row] = 1; + res->insert_default(); + continue; + } + res->insert_value(y_min); + } + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + + return Status::OK(); + } +}; + struct StDistanceSphere { static constexpr auto NAME = "st_distance_sphere"; static const size_t NUM_ARGS = 4; @@ -1084,6 +1245,10 @@ void register_function_geo(SimpleFunctionFactory& factory) { factory.register_function>>(); factory.register_function>(); factory.register_function>(); + factory.register_function>(); + factory.register_function>(); + factory.register_function>(); + factory.register_function>(); factory.register_function>(); factory.register_function>(); factory.register_function>(); From aa95707bbcf947548451c393449af6ef38ccf6b4 Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Sun, 16 Aug 2026 17:25:03 +0800 Subject: [PATCH 3/9] [feature](function) Add FE signatures for st_xmax/st_xmin/st_ymax/st_ymin ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: Add the Nereids scalar function signature classes, following the StX pattern. Each accepts one VARCHAR/String argument and returns DOUBLE. ### Release note None ### Check List (For Author) - Test: No need to test (registration and tests come in later commits) - Behavior changed: No - Does this need documentation: No --- .../expressions/functions/scalar/StXMax.java | 77 +++++++++++++++++++ .../expressions/functions/scalar/StXMin.java | 77 +++++++++++++++++++ .../expressions/functions/scalar/StYMax.java | 77 +++++++++++++++++++ .../expressions/functions/scalar/StYMin.java | 77 +++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StXMax.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StXMin.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StYMax.java create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StYMin.java diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StXMax.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StXMax.java new file mode 100644 index 00000000000000..141436f6f6e89a --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StXMax.java @@ -0,0 +1,77 @@ +// 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.DoubleType; +import org.apache.doris.nereids.types.StringType; +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_xmax'. This class is generated by GenerateFunction. + */ +public class StXMax extends ScalarFunction + implements UnaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(DoubleType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT), + FunctionSignature.ret(DoubleType.INSTANCE).args(StringType.INSTANCE) + ); + + /** + * constructor with 1 argument. + */ + public StXMax(Expression arg) { + super("st_xmax", arg); + } + + /** constructor for withChildren and reuse signature */ + private StXMax(ScalarFunctionParams functionParams) { + super(functionParams); + } + + /** + * withChildren. + */ + @Override + public StXMax withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new StXMax(getFunctionParams(children)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStXMax(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StXMin.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StXMin.java new file mode 100644 index 00000000000000..d078405c261575 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StXMin.java @@ -0,0 +1,77 @@ +// 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.DoubleType; +import org.apache.doris.nereids.types.StringType; +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_xmin'. This class is generated by GenerateFunction. + */ +public class StXMin extends ScalarFunction + implements UnaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(DoubleType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT), + FunctionSignature.ret(DoubleType.INSTANCE).args(StringType.INSTANCE) + ); + + /** + * constructor with 1 argument. + */ + public StXMin(Expression arg) { + super("st_xmin", arg); + } + + /** constructor for withChildren and reuse signature */ + private StXMin(ScalarFunctionParams functionParams) { + super(functionParams); + } + + /** + * withChildren. + */ + @Override + public StXMin withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new StXMin(getFunctionParams(children)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStXMin(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StYMax.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StYMax.java new file mode 100644 index 00000000000000..cbe6ac31ecf793 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StYMax.java @@ -0,0 +1,77 @@ +// 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.DoubleType; +import org.apache.doris.nereids.types.StringType; +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_ymax'. This class is generated by GenerateFunction. + */ +public class StYMax extends ScalarFunction + implements UnaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(DoubleType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT), + FunctionSignature.ret(DoubleType.INSTANCE).args(StringType.INSTANCE) + ); + + /** + * constructor with 1 argument. + */ + public StYMax(Expression arg) { + super("st_ymax", arg); + } + + /** constructor for withChildren and reuse signature */ + private StYMax(ScalarFunctionParams functionParams) { + super(functionParams); + } + + /** + * withChildren. + */ + @Override + public StYMax withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new StYMax(getFunctionParams(children)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStYMax(this, context); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StYMin.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StYMin.java new file mode 100644 index 00000000000000..0abe2bf2f0b414 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StYMin.java @@ -0,0 +1,77 @@ +// 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.DoubleType; +import org.apache.doris.nereids.types.StringType; +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_ymin'. This class is generated by GenerateFunction. + */ +public class StYMin extends ScalarFunction + implements UnaryExpression, ExplicitlyCastableSignature, AlwaysNullable, PropagateNullLiteral { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(DoubleType.INSTANCE).args(VarcharType.SYSTEM_DEFAULT), + FunctionSignature.ret(DoubleType.INSTANCE).args(StringType.INSTANCE) + ); + + /** + * constructor with 1 argument. + */ + public StYMin(Expression arg) { + super("st_ymin", arg); + } + + /** constructor for withChildren and reuse signature */ + private StYMin(ScalarFunctionParams functionParams) { + super(functionParams); + } + + /** + * withChildren. + */ + @Override + public StYMin withChildren(List children) { + Preconditions.checkArgument(children.size() == 1); + return new StYMin(getFunctionParams(children)); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitStYMin(this, context); + } +} From 1b3b91a72a3ba27c3b5dfb9248e5d70dbab60416 Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Sun, 16 Aug 2026 17:25:38 +0800 Subject: [PATCH 4/9] [feature](function) Register st_xmax/st_xmin/st_ymax/st_ymin in FE ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: Register the four new scalar functions in BuiltinScalarFunctions and add the corresponding visit methods in ScalarFunctionVisitor. ### Release note None ### Check List (For Author) - Test: No need to test (regression tests come in the next commit) - Behavior changed: No - Does this need documentation: No --- .../doris/catalog/BuiltinScalarFunctions.java | 8 ++++++++ .../visitor/ScalarFunctionVisitor.java | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+) 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 762f3afc0f6649..64d6bce4e30cd5 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 @@ -500,7 +500,11 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolygonfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StTouches; import org.apache.doris.nereids.trees.expressions.functions.scalar.StX; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StXMax; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StXMin; import org.apache.doris.nereids.trees.expressions.functions.scalar.StY; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StYMax; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StYMin; import org.apache.doris.nereids.trees.expressions.functions.scalar.StartsWith; import org.apache.doris.nereids.trees.expressions.functions.scalar.StrToDate; import org.apache.doris.nereids.trees.expressions.functions.scalar.StrToMap; @@ -1090,7 +1094,11 @@ public class BuiltinScalarFunctions implements FunctionHelper { scalar(StPolygon.class, "st_polygon"), scalar(StPolygonfromtext.class, "st_polygonfromtext"), scalar(StX.class, "st_x"), + scalar(StXMax.class, "st_xmax"), + scalar(StXMin.class, "st_xmin"), scalar(StY.class, "st_y"), + scalar(StYMax.class, "st_ymax"), + scalar(StYMin.class, "st_ymin"), scalar(StartsWith.class, "starts_with"), scalar(Strcmp.class, "strcmp"), scalar(StripNullValue.class, "strip_null_value"), 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 6b73a00b85440f..b407fb2db867d8 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 @@ -519,7 +519,11 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.StPolygonfromtext; import org.apache.doris.nereids.trees.expressions.functions.scalar.StTouches; import org.apache.doris.nereids.trees.expressions.functions.scalar.StX; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StXMax; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StXMin; import org.apache.doris.nereids.trees.expressions.functions.scalar.StY; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StYMax; +import org.apache.doris.nereids.trees.expressions.functions.scalar.StYMin; import org.apache.doris.nereids.trees.expressions.functions.scalar.StartsWith; import org.apache.doris.nereids.trees.expressions.functions.scalar.StrToDate; import org.apache.doris.nereids.trees.expressions.functions.scalar.StrToMap; @@ -2468,6 +2472,22 @@ default R visitStPolygonfromtext(StPolygonfromtext stPolygonfromtext, C context) return visitScalarFunction(stPolygonfromtext, context); } + default R visitStXMax(StXMax stXMax, C context) { + return visitScalarFunction(stXMax, context); + } + + default R visitStXMin(StXMin stXMin, C context) { + return visitScalarFunction(stXMin, context); + } + + default R visitStYMax(StYMax stYMax, C context) { + return visitScalarFunction(stYMax, context); + } + + default R visitStYMin(StYMin stYMin, C context) { + return visitScalarFunction(stYMin, context); + } + default R visitStX(StX stX, C context) { return visitScalarFunction(stX, context); } From f3759b596ded74d088bfa2d9d5e18359534499e1 Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Sun, 16 Aug 2026 17:26:11 +0800 Subject: [PATCH 5/9] [test](regression) Add regression tests for st_xmax/st_xmin/st_ymax/st_ymin ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: Add regression test cases covering points (fn_test), polygons, literal linestrings, invalid input (NULL result) and NULL input, following the existing st_x/st_y test style in nereids_scalar_fn_S. ### Release note None ### Check List (For Author) - Test: Regression test (nereids_function_p0, S.groovy) - Behavior changed: No - Does this need documentation: No --- .../nereids_function_p0/scalar_function/S.groovy | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/regression-test/suites/nereids_function_p0/scalar_function/S.groovy b/regression-test/suites/nereids_function_p0/scalar_function/S.groovy index cce6d4ac83e7b1..2dd5bcb571f0e2 100644 --- a/regression-test/suites/nereids_function_p0/scalar_function/S.groovy +++ b/regression-test/suites/nereids_function_p0/scalar_function/S.groovy @@ -207,6 +207,21 @@ suite("nereids_scalar_fn_S") { qt_sql_st_y_Varchar_notnull "select st_y(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" qt_sql_st_y_String "select st_y(st_point(x_lng, x_lat)) from fn_test order by 1" qt_sql_st_y_String_notnull "select st_y(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_xmax_Varchar "select st_xmax(st_point(x_lng, x_lat)) from fn_test order by 1" + qt_sql_st_xmax_Varchar_notnull "select st_xmax(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_xmin_Varchar "select st_xmin(st_point(x_lng, x_lat)) from fn_test order by 1" + qt_sql_st_xmin_Varchar_notnull "select st_xmin(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_ymax_Varchar "select st_ymax(st_point(x_lng, x_lat)) from fn_test order by 1" + qt_sql_st_ymax_Varchar_notnull "select st_ymax(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_ymin_Varchar "select st_ymin(st_point(x_lng, x_lat)) from fn_test order by 1" + qt_sql_st_ymin_Varchar_notnull "select st_ymin(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_xmax_polygon "select st_xmax(st_polygon(polygon_wkt)) from fn_test order by 1" + qt_sql_st_ymin_polygon "select st_ymin(st_polygon(polygon_wkt)) from fn_test order by 1" + qt_sql_st_xmax_linestring "select st_xmax(st_linestringfromtext('LINESTRING (1 1, 3 2, 2 4)'))" + qt_sql_st_ymin_linestring "select st_ymin(st_linestringfromtext('LINESTRING (1 1, 3 2, 2 4)'))" + qt_sql_st_xmax_invalid "select st_xmax('not a geometry')" + qt_sql_st_xmax_null "select st_xmax(NULL)" + qt_sql_st_asbinary_Varchar "select ST_AsBinary(st_point(x_lng, x_lat)) from fn_test order by 1" qt_sql_st_asbinary_Varchar_notnull "select ST_AsBinary(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" qt_sql_st_geometryfromwkb_Varchar "select ST_AsText(ST_GeometryFromWKB(ST_AsBinary(st_polyfromtext(polygon_wkt)))) from fn_test order by 1" From e0f96b6329ddd46ad5536af0c990543737822138 Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Sun, 16 Aug 2026 21:20:23 +0800 Subject: [PATCH 6/9] [fix](geo) Declare bounding_box overrides in GeoShape subclasses ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: The bounding_box() overrides were defined out-of-line in geo_types.cpp but never declared in the GeoPoint/GeoLine/GeoPolygon/GeoMultiPolygon/ GeoCircle class bodies, which is ill-formed and fails to compile ("out-of-line definition does not match any declaration"). Add the missing override declarations. ### Release note None ### Check List (For Author) - Test: No need to test (compile fix; regression tests already added) - Behavior changed: No - Does this need documentation: No --- be/src/exprs/function/geo/geo_types.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/be/src/exprs/function/geo/geo_types.h b/be/src/exprs/function/geo/geo_types.h index 750617413ff295..472ecb4dd5aa62 100644 --- a/be/src/exprs/function/geo/geo_types.h +++ b/be/src/exprs/function/geo/geo_types.h @@ -141,6 +141,7 @@ class GeoPoint : public GeoShape { double x() const; double y() const; + BoundingBox bounding_box() const override; int num_geometries() const override { return 1; } int num_points() const override { return 1; } @@ -180,6 +181,7 @@ class GeoLine : public GeoShape { int numPoint() const; const S2Point* getPoint(int i) const; + BoundingBox bounding_box() const override; int num_geometries() const override { return 1; } int num_points() const override { return numPoint(); } @@ -220,6 +222,7 @@ class GeoPolygon : public GeoShape { double getArea() const; double Length() const override; double Distance(const GeoShape* rhs) const override; + BoundingBox bounding_box() const override; S2Loop* getLoop(int i) const; int num_geometries() const override { return 1; } @@ -257,6 +260,7 @@ class GeoMultiPolygon : public GeoShape { double getArea() const; double Length() const override; double Distance(const GeoShape* rhs) const override; + BoundingBox bounding_box() const override; int num_geometries() const override { return static_cast(_polygons.size()); } int num_points() const override; @@ -292,6 +296,7 @@ class GeoCircle : public GeoShape { double getArea() const; double Length() const override; double Distance(const GeoShape* rhs) const override; + BoundingBox bounding_box() const override; protected: void encode(std::string* buf) override; From 1db896d1c365ff75c5aa0669fa10404bb0236115 Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Sun, 16 Aug 2026 21:29:07 +0800 Subject: [PATCH 7/9] [test](be) Add unit tests for GeoShape bounding_box ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: Add BE unit tests covering the bounding_box accessor for GeoPoint, GeoLine and GeoPolygon, backing st_xmax/st_xmin/st_ymax/st_ymin. ### Release note None ### Check List (For Author) - Test: Unit test (GeoTypesTest) - Behavior changed: No - Does this need documentation: No --- be/test/exprs/function/geo/geo_types_test.cpp | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/be/test/exprs/function/geo/geo_types_test.cpp b/be/test/exprs/function/geo/geo_types_test.cpp index 580218f4058e71..d01bf658354d2d 100644 --- a/be/test/exprs/function/geo/geo_types_test.cpp +++ b/be/test/exprs/function/geo/geo_types_test.cpp @@ -65,6 +65,41 @@ TEST_F(GeoTypesTest, point_normal) { } } +TEST_F(GeoTypesTest, bounding_box_point) { + GeoPoint point; + auto status = point.from_coord(116.123, 63.546); + EXPECT_EQ(GEO_PARSE_OK, status); + auto box = point.bounding_box(); + EXPECT_DOUBLE_EQ(116.123, box.x_max); + EXPECT_DOUBLE_EQ(116.123, box.x_min); + EXPECT_DOUBLE_EQ(63.546, box.y_max); + EXPECT_DOUBLE_EQ(63.546, box.y_min); +} + +TEST_F(GeoTypesTest, bounding_box_linestring) { + const char* wkt = "LINESTRING (30 10, 10 30, 40 40)"; + GeoParseStatus status; + auto line = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, line.get()); + auto box = line->bounding_box(); + EXPECT_NEAR(40.0, box.x_max, 1e-9); + EXPECT_NEAR(10.0, box.x_min, 1e-9); + EXPECT_NEAR(40.0, box.y_max, 1e-9); + EXPECT_NEAR(10.0, box.y_min, 1e-9); +} + +TEST_F(GeoTypesTest, bounding_box_polygon) { + const char* wkt = "POLYGON ((0 0, 4 0, 4 3, 0 3, 0 0))"; + GeoParseStatus status; + auto polygon = GeoShape::from_wkt(wkt, strlen(wkt), status); + EXPECT_NE(nullptr, polygon.get()); + auto box = polygon->bounding_box(); + EXPECT_NEAR(4.0, box.x_max, 1e-9); + EXPECT_NEAR(0.0, box.x_min, 1e-9); + EXPECT_NEAR(3.0, box.y_max, 1e-9); + EXPECT_NEAR(0.0, box.y_min, 1e-9); +} + TEST_F(GeoTypesTest, point_invalid) { GeoPoint point; From fc9297fb0460becdc61ee6e5e212da57cdee14a9 Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Sun, 16 Aug 2026 23:20:55 +0800 Subject: [PATCH 8/9] [test](regression) Move st_xmax/st_xmin/st_ymax/st_ymin tests to a dedicated suite ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: Move the bounding box accessor test cases from nereids_scalar_fn_S into a dedicated st_bounding_box suite, so the suite owns a fresh auto-generated .out file and the shared S.out is left untouched. ### Release note None ### Check List (For Author) - Test: Regression test (nereids_scalar_fn_st_bounding_box, passed) - Behavior changed: No - Does this need documentation: No --- .../scalar_function/st_bounding_box.out | 159 ++++++++++++++++++ .../scalar_function/S.groovy | 15 -- .../scalar_function/st_bounding_box.groovy | 38 +++++ 3 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 regression-test/data/nereids_function_p0/scalar_function/st_bounding_box.out create mode 100644 regression-test/suites/nereids_function_p0/scalar_function/st_bounding_box.groovy diff --git a/regression-test/data/nereids_function_p0/scalar_function/st_bounding_box.out b/regression-test/data/nereids_function_p0/scalar_function/st_bounding_box.out new file mode 100644 index 00000000000000..ea1202a9c60a6c --- /dev/null +++ b/regression-test/data/nereids_function_p0/scalar_function/st_bounding_box.out @@ -0,0 +1,159 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !sql_st_xmax_Varchar -- +\N +-74.35620117 +-46.35620117 +5 +16.35620117 +43.35620117 +47.35620117 +90.35620117 +90.35620117 +90.35620117 +90.35620117 +98.35620117 +126.35620117 + +-- !sql_st_xmax_Varchar_notnull -- +-74.35620117 +-46.35620117 +5 +16.35620117 +43.35620117 +47.35620117 +90.35620117 +90.35620117 +90.35620117 +90.35620117 +98.35620117 +126.35620117 + +-- !sql_st_xmin_Varchar -- +\N +-74.35620117 +-46.35620117 +5 +16.35620117 +43.35620117 +47.35620117 +90.35620117 +90.35620117 +90.35620117 +90.35620117 +98.35620117 +126.35620117 + +-- !sql_st_xmin_Varchar_notnull -- +-74.35620117 +-46.35620117 +5 +16.35620117 +43.35620117 +47.35620117 +90.35620117 +90.35620117 +90.35620117 +90.35620117 +98.35620117 +126.35620117 + +-- !sql_st_ymax_Varchar -- +\N +-39.939093 +5 +19.939093 +26.939093 +35.939093 +36.939093 +39.939093 +39.939093 +47.939093 +49.939093 +59.939093 +79.939093 + +-- !sql_st_ymax_Varchar_notnull -- +-39.939093 +5 +19.939093 +26.939093 +35.939093 +36.939093 +39.939093 +39.939093 +47.939093 +49.939093 +59.939093 +79.939093 + +-- !sql_st_ymin_Varchar -- +\N +-39.939093 +5 +19.939093 +26.939093 +35.939093 +36.939093 +39.939093 +39.939093 +47.939093 +49.939093 +59.939093 +79.939093 + +-- !sql_st_ymin_Varchar_notnull -- +-39.939093 +5 +19.939093 +26.939093 +35.939093 +36.939093 +39.939093 +39.939093 +47.939093 +49.939093 +59.939093 +79.939093 + +-- !sql_st_xmax_polygon -- +\N +4.000000000000001 +9.999999999999998 +9.999999999999998 +10 +12 +16 +34.00000000000001 +38.00000000000001 +42 +45 +48.00000000000001 +67 + +-- !sql_st_ymin_polygon -- +\N +0 +0 +0 +0 +0 +0 +0.9999999999999998 +1 +1 +1 +1 +4 + +-- !sql_st_xmax_linestring -- +3.0000000000000004 + +-- !sql_st_ymin_linestring -- +1 + +-- !sql_st_xmax_invalid -- +\N + +-- !sql_st_xmax_null -- +\N + diff --git a/regression-test/suites/nereids_function_p0/scalar_function/S.groovy b/regression-test/suites/nereids_function_p0/scalar_function/S.groovy index 2dd5bcb571f0e2..cce6d4ac83e7b1 100644 --- a/regression-test/suites/nereids_function_p0/scalar_function/S.groovy +++ b/regression-test/suites/nereids_function_p0/scalar_function/S.groovy @@ -207,21 +207,6 @@ suite("nereids_scalar_fn_S") { qt_sql_st_y_Varchar_notnull "select st_y(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" qt_sql_st_y_String "select st_y(st_point(x_lng, x_lat)) from fn_test order by 1" qt_sql_st_y_String_notnull "select st_y(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" - qt_sql_st_xmax_Varchar "select st_xmax(st_point(x_lng, x_lat)) from fn_test order by 1" - qt_sql_st_xmax_Varchar_notnull "select st_xmax(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" - qt_sql_st_xmin_Varchar "select st_xmin(st_point(x_lng, x_lat)) from fn_test order by 1" - qt_sql_st_xmin_Varchar_notnull "select st_xmin(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" - qt_sql_st_ymax_Varchar "select st_ymax(st_point(x_lng, x_lat)) from fn_test order by 1" - qt_sql_st_ymax_Varchar_notnull "select st_ymax(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" - qt_sql_st_ymin_Varchar "select st_ymin(st_point(x_lng, x_lat)) from fn_test order by 1" - qt_sql_st_ymin_Varchar_notnull "select st_ymin(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" - qt_sql_st_xmax_polygon "select st_xmax(st_polygon(polygon_wkt)) from fn_test order by 1" - qt_sql_st_ymin_polygon "select st_ymin(st_polygon(polygon_wkt)) from fn_test order by 1" - qt_sql_st_xmax_linestring "select st_xmax(st_linestringfromtext('LINESTRING (1 1, 3 2, 2 4)'))" - qt_sql_st_ymin_linestring "select st_ymin(st_linestringfromtext('LINESTRING (1 1, 3 2, 2 4)'))" - qt_sql_st_xmax_invalid "select st_xmax('not a geometry')" - qt_sql_st_xmax_null "select st_xmax(NULL)" - qt_sql_st_asbinary_Varchar "select ST_AsBinary(st_point(x_lng, x_lat)) from fn_test order by 1" qt_sql_st_asbinary_Varchar_notnull "select ST_AsBinary(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" qt_sql_st_geometryfromwkb_Varchar "select ST_AsText(ST_GeometryFromWKB(ST_AsBinary(st_polyfromtext(polygon_wkt)))) from fn_test order by 1" diff --git a/regression-test/suites/nereids_function_p0/scalar_function/st_bounding_box.groovy b/regression-test/suites/nereids_function_p0/scalar_function/st_bounding_box.groovy new file mode 100644 index 00000000000000..94faba5d1eff66 --- /dev/null +++ b/regression-test/suites/nereids_function_p0/scalar_function/st_bounding_box.groovy @@ -0,0 +1,38 @@ +// 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. + +// Bounding box accessor functions st_xmax/st_xmin/st_ymax/st_ymin +// (Trino compatible), implemented on top of GeoShape::bounding_box. +suite("nereids_scalar_fn_st_bounding_box") { + sql 'use regression_test_nereids_function_p0' + sql 'set enable_nereids_planner=true' + sql 'set enable_fallback_to_original_planner=false' + qt_sql_st_xmax_Varchar "select st_xmax(st_point(x_lng, x_lat)) from fn_test order by 1" + qt_sql_st_xmax_Varchar_notnull "select st_xmax(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_xmin_Varchar "select st_xmin(st_point(x_lng, x_lat)) from fn_test order by 1" + qt_sql_st_xmin_Varchar_notnull "select st_xmin(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_ymax_Varchar "select st_ymax(st_point(x_lng, x_lat)) from fn_test order by 1" + qt_sql_st_ymax_Varchar_notnull "select st_ymax(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_ymin_Varchar "select st_ymin(st_point(x_lng, x_lat)) from fn_test order by 1" + qt_sql_st_ymin_Varchar_notnull "select st_ymin(st_point(x_lng, x_lat)) from fn_test_not_nullable order by 1" + qt_sql_st_xmax_polygon "select st_xmax(st_polygon(polygon_wkt)) from fn_test order by 1" + qt_sql_st_ymin_polygon "select st_ymin(st_polygon(polygon_wkt)) from fn_test order by 1" + qt_sql_st_xmax_linestring "select st_xmax(st_linestringfromtext('LINESTRING (1 1, 3 2, 2 4)'))" + qt_sql_st_ymin_linestring "select st_ymin(st_linestringfromtext('LINESTRING (1 1, 3 2, 2 4)'))" + qt_sql_st_xmax_invalid "select st_xmax('not a geometry')" + qt_sql_st_xmax_null "select st_xmax(NULL)" +} From 673fdeff4eac1638f5b3e7da6a4e908b565b3f7b Mon Sep 17 00:00:00 2001 From: MiYuyuyuyu <895188625@qq.com> Date: Mon, 17 Aug 2026 00:25:56 +0800 Subject: [PATCH 9/9] [fix](geo) Use designated initializers in bounding_box ### What problem does this PR solve? Issue Number: close #48203 Problem Summary: clang-tidy (modernize-use-designated-initializers) requires designated initializers for aggregate BoundingBox construction in GeoPoint and GeoCircle bounding_box(). ### Release note None ### Check List (For Author) - Test: No need to test (style fix, no behavior change) - Behavior changed: No - Does this need documentation: No --- be/src/exprs/function/geo/geo_types.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/be/src/exprs/function/geo/geo_types.cpp b/be/src/exprs/function/geo/geo_types.cpp index 03c8716c9dc8b7..aed4c53920e4b7 100644 --- a/be/src/exprs/function/geo/geo_types.cpp +++ b/be/src/exprs/function/geo/geo_types.cpp @@ -641,7 +641,7 @@ double GeoPoint::y() const { BoundingBox GeoPoint::bounding_box() const { // A point degenerates to a box with zero width and height. - return {x(), x(), y(), y()}; + return {.x_max = x(), .x_min = x(), .y_max = y(), .y_min = y()}; } std::string GeoPoint::as_wkt() const { @@ -1834,7 +1834,7 @@ BoundingBox GeoCircle::bounding_box() const { const S2Point& center = _cap->center(); const double lon = S2LatLng::Longitude(center).degrees(); const double lat = S2LatLng::Latitude(center).degrees(); - return {lon, lon, lat, lat}; + return {.x_max = lon, .x_min = lon, .y_max = lat, .y_min = lat}; } double GeoPoint::Distance(const GeoShape* rhs) const {