@@ -3463,8 +3463,9 @@ struct ST_ExteriorRing {
34633463 }
34643464
34653465 if (geom.is_empty ()) {
3466- const sgl::geometry empty (sgl::geometry_type::LINESTRING, geom.has_z (), geom.has_m ());
3467- return lstate.Serialize (result, empty);
3466+ // Polygon empty -> return empty linestring
3467+ sgl::geometry empty_linestring (sgl::geometry_type::LINESTRING, geom.has_z (), geom.has_m ());
3468+ return lstate.Serialize (result, empty_linestring);
34683469 }
34693470
34703471 const auto shell = geom.get_first_part ();
@@ -6256,6 +6257,228 @@ struct ST_Hilbert {
62566257 }
62576258};
62586259
6260+ // ======================================================================================================================
6261+ // ST_InteriorRingN
6262+ // ======================================================================================================================
6263+
6264+ struct ST_InteriorRingN {
6265+
6266+ // ------------------------------------------------------------------------------------------------------------------
6267+ // GEOMETRY
6268+ // ------------------------------------------------------------------------------------------------------------------
6269+ static void ExecuteGeometry (DataChunk &args, ExpressionState &state, Vector &result) {
6270+ auto &lstate = LocalState::ResetAndGet (state);
6271+
6272+ BinaryExecutor::ExecuteWithNulls<string_t , int64_t , string_t >(
6273+ args.data [0 ], args.data [1 ], result, args.size (),
6274+ [&](const string_t &blob, const int64_t &n, ValidityMask &mask, idx_t idx) {
6275+ sgl::geometry geom;
6276+ lstate.Deserialize (blob, geom);
6277+
6278+ // ---- validate geometry ----
6279+ if (geom.get_type () != sgl::geometry_type::POLYGON) {
6280+ mask.SetInvalid (idx);
6281+ return string_t {};
6282+ }
6283+
6284+ if (geom.is_empty ()) {
6285+ // empty polygon → NULL because ring index must be always out of bounds then
6286+ mask.SetInvalid (idx);
6287+ return string_t {};
6288+ }
6289+
6290+ if (n < 1 ) {
6291+ // invalid index → NULL
6292+ mask.SetInvalid (idx);
6293+ return string_t {};
6294+ }
6295+
6296+ const idx_t num_parts = geom.get_part_count (); // includes shell
6297+ // parts: 0 = exterior, 1..n = interior rings
6298+ const idx_t num_interior = num_parts > 0 ? num_parts - 1 : 0 ;
6299+
6300+ if (static_cast <idx_t >(n) > num_interior) {
6301+ // ring doesn't exist → NULL
6302+ mask.SetInvalid (idx);
6303+ return string_t {};
6304+ }
6305+
6306+ // interior ring N = part N (because part 0 = shell)
6307+ const auto *ring = geom.get_first_part ();
6308+ for (idx_t i = 0 ; i < (idx_t )n; i++) {
6309+ ring = ring->get_next ();
6310+ }
6311+
6312+ D_ASSERT (ring != nullptr );
6313+ return lstate.Serialize (result, *ring);
6314+ }
6315+ );
6316+ }
6317+
6318+ // ------------------------------------------------------------------------------------------------------------------
6319+ // POLYGON_2D
6320+ // ------------------------------------------------------------------------------------------------------------------
6321+ static void ExecutePolygon (DataChunk &args, ExpressionState &state, Vector &result) {
6322+ D_ASSERT (args.data .size () == 2 );
6323+ auto &poly_vec = args.data [0 ];
6324+ auto &n_vec = args.data [1 ];
6325+
6326+ // same layout as ST_ExteriorRing::ExecutePolygon
6327+ auto poly_entries = ListVector::GetData (poly_vec);
6328+ auto &ring_vec = ListVector::GetEntry (poly_vec);
6329+ auto ring_entries = ListVector::GetData (ring_vec);
6330+ auto &vertex_vec = ListVector::GetEntry (ring_vec);
6331+ auto &vertex_vec_children = StructVector::GetEntries (vertex_vec);
6332+ auto poly_x_data = FlatVector::GetData<double >(*vertex_vec_children[0 ]);
6333+ auto poly_y_data = FlatVector::GetData<double >(*vertex_vec_children[1 ]);
6334+
6335+ auto count = args.size ();
6336+ UnifiedVectorFormat poly_format;
6337+ poly_vec.ToUnifiedFormat (count, poly_format);
6338+
6339+ // We'll need to build the result list length: sum of selected interior ring lengths
6340+ idx_t total_vertex_count = 0 ;
6341+
6342+ // To inspect n per-row, extract unified format for n (it might be constant)
6343+ UnifiedVectorFormat n_format;
6344+ n_vec.ToUnifiedFormat (count, n_format);
6345+ auto n_data = FlatVector::GetData<int64_t >(n_vec);
6346+
6347+ for (idx_t i = 0 ; i < count; i++) {
6348+ auto row_idx = poly_format.sel ->get_index (i);
6349+ if (!poly_format.validity .RowIsValid (row_idx)) {
6350+ continue ;
6351+ }
6352+ auto poly = poly_entries[row_idx];
6353+ if (poly.length == 0 ) {
6354+ // empty polygon -> nothing to add
6355+ continue ;
6356+ }
6357+
6358+ // read requested n for this row
6359+ int64_t nr = 0 ;
6360+ // handle constant / flat
6361+ if (n_format.validity .RowIsValid (n_format.sel ->get_index (i))) {
6362+ nr = n_data[n_format.sel ->get_index (i)];
6363+ } else {
6364+ // n is null -> will produce NULL result later
6365+ continue ;
6366+ }
6367+
6368+ // polygon has poly.length rings: first is exterior, rest are interior
6369+ const idx_t ring_count = poly.length ; // >=1 normally
6370+ const idx_t interior_count = (ring_count > 0 ? ring_count - 1 : 0 );
6371+
6372+ if (nr < 1 || nr > static_cast <int64_t >(interior_count)) {
6373+ // out of range or invalid -> no vertices added (result will be NULL or empty)
6374+ continue ;
6375+ }
6376+
6377+ // interior ring index in ring_entries: poly.offset + nr (since 1 -> first interior at offset+1)
6378+ auto &ring = ring_entries[poly.offset + static_cast <idx_t >(nr)];
6379+ total_vertex_count += ring.length ;
6380+ }
6381+
6382+ // Allocate result
6383+ auto &line_vec = result;
6384+ ListVector::Reserve (line_vec, total_vertex_count);
6385+ ListVector::SetListSize (line_vec, total_vertex_count);
6386+
6387+ auto line_entries = ListVector::GetData (line_vec);
6388+ auto &line_coord_vec = StructVector::GetEntries (ListVector::GetEntry (line_vec));
6389+ auto line_data_x = FlatVector::GetData<double >(*line_coord_vec[0 ]);
6390+ auto line_data_y = FlatVector::GetData<double >(*line_coord_vec[1 ]);
6391+
6392+ // Fill results
6393+ idx_t line_data_offset = 0 ;
6394+ for (idx_t i = 0 ; i < count; i++) {
6395+ auto row_idx = poly_format.sel ->get_index (i);
6396+ if (!poly_format.validity .RowIsValid (row_idx)) {
6397+ FlatVector::SetNull (line_vec, i, true );
6398+ continue ;
6399+ }
6400+
6401+ auto poly = poly_entries[row_idx];
6402+
6403+ // read requested n for this row
6404+ const auto n_idx = n_format.sel ->get_index (i);
6405+ if (!n_format.validity .RowIsValid (n_idx)) {
6406+ FlatVector::SetNull (line_vec, i, true );
6407+ continue ;
6408+ }
6409+
6410+ const auto nr = n_data[n_idx];
6411+
6412+ const idx_t ring_count = poly.length ;
6413+ const idx_t interior_count = (ring_count > 0 ? ring_count - 1 : 0 );
6414+
6415+ if (nr < 1 || nr > static_cast <int64_t >(interior_count)) {
6416+ // out of range -> NULL result
6417+ FlatVector::SetNull (line_vec, i, true );
6418+ continue ;
6419+ }
6420+
6421+ auto &ring = ring_entries[poly.offset + static_cast <idx_t >(nr)]; // offset + 1..N -> interior rings
6422+ auto &line_entry = line_entries[i];
6423+ line_entry.offset = line_data_offset;
6424+ line_entry.length = ring.length ;
6425+
6426+ for (idx_t coord_idx = 0 ; coord_idx < ring.length ; coord_idx++) {
6427+ line_data_x[line_entry.offset + coord_idx] = poly_x_data[ring.offset + coord_idx];
6428+ line_data_y[line_entry.offset + coord_idx] = poly_y_data[ring.offset + coord_idx];
6429+ }
6430+
6431+ line_data_offset += ring.length ;
6432+ }
6433+
6434+ if (count == 1 ) {
6435+ result.SetVectorType (VectorType::CONSTANT_VECTOR);
6436+ }
6437+ }
6438+
6439+ // ------------------------------------------------------------------------------------------------------------------
6440+ // Documentation
6441+ // ------------------------------------------------------------------------------------------------------------------
6442+ static constexpr auto DESCRIPTION =
6443+ " Returns the N-th interior ring (hole) of a POLYGON as a LINESTRING. Indexing is 1-based (n = 1 returns the first interior ring). "
6444+ " Returns NULL if the polygon is empty or has fewer than N interior rings." ;
6445+
6446+ static constexpr auto EXAMPLE = R"(
6447+ SELECT ST_AsText(ST_InteriorRingN(ST_GeomFromText('POLYGON((0 0,10 0,10 10,0 10,0 0),(2 2,4 2,4 4,2 4,2 2))'), 1));
6448+ )" ;
6449+
6450+ // ------------------------------------------------------------------------------------------------------------------
6451+ // Register
6452+ // ------------------------------------------------------------------------------------------------------------------
6453+ static void Register (ExtensionLoader &loader) {
6454+ FunctionBuilder::RegisterScalar (loader, " ST_InteriorRingN" , [](ScalarFunctionBuilder &func) {
6455+ func.AddVariant ([](ScalarFunctionVariantBuilder &variant) {
6456+ variant.AddParameter (" geom" , GeoTypes::GEOMETRY ());
6457+ variant.AddParameter (" n" , LogicalType::BIGINT);
6458+ variant.SetReturnType (GeoTypes::GEOMETRY ());
6459+
6460+ variant.SetInit (LocalState::Init);
6461+ variant.SetFunction (ExecuteGeometry);
6462+ });
6463+
6464+ func.AddVariant ([](ScalarFunctionVariantBuilder &variant) {
6465+ variant.AddParameter (" polygon" , GeoTypes::POLYGON_2D ());
6466+ variant.AddParameter (" n" , LogicalType::BIGINT);
6467+ variant.SetReturnType (GeoTypes::LINESTRING_2D ());
6468+
6469+ variant.SetFunction (ExecutePolygon);
6470+ });
6471+
6472+ func.SetDescription (DESCRIPTION);
6473+ func.SetExample (EXAMPLE);
6474+
6475+ func.SetTag (" ext" , " spatial" );
6476+ func.SetTag (" category" , " property" );
6477+ });
6478+ }
6479+ };
6480+
6481+
62596482// ======================================================================================================================
62606483// ST_InterpolatePoint
62616484// ======================================================================================================================
@@ -9404,6 +9627,7 @@ void RegisterSpatialScalarFunctions(ExtensionLoader &loader) {
94049627 ST_ZMFlag::Register (loader);
94059628 ST_Distance_Sphere::Register (loader);
94069629 ST_Hilbert::Register (loader);
9630+ ST_InteriorRingN::Register (loader);
94079631 ST_InterpolatePoint::Register (loader);
94089632 ST_Intersects::Register (loader);
94099633 ST_IntersectsExtent::Register (loader);
0 commit comments