diff --git a/Cargo.lock b/Cargo.lock index d2baa93fa10c9..dbe9391e4200d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3092,9 +3092,9 @@ dependencies = [ [[package]] name = "crs-definitions" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a5c99491ee16d7f1549494bcab90bdfb2f283ef48a25fc6c870e7e7c9f12bb" +checksum = "5229c5b63bdc8e24d60e5c4b6115fbb4d11a8978661b4ecc57d6068ddb073983" [[package]] name = "crunchy" @@ -3709,6 +3709,7 @@ dependencies = [ "num-derive", "num-traits", "pretty_assertions", + "proj4rs", "proptest", "rand 0.8.5", "rand_distr", @@ -7921,9 +7922,9 @@ dependencies = [ [[package]] name = "geo-index" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27fb4e4ffdde62f8aa7a4165b30f6fe8e8337d35b657ebf86c98d884ee7c750" +checksum = "f310ad245e63f6b5fcf4a7c2663139708eae1374045396eef95ea3badd242d57" dependencies = [ "bytemuck", "float_next_after", @@ -7961,9 +7962,9 @@ dependencies = [ [[package]] name = "geographiclib-rs" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e5ed84f8089c70234b0a8e0aedb6dc733671612ddc0d37c6066052f9781960" +checksum = "c5a7f08910fd98737a6eda7568e7c5e645093e073328eeef49758cfe8b0489c7" dependencies = [ "libm", ] @@ -13478,15 +13479,14 @@ dependencies = [ [[package]] name = "proj4rs" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3c4a66ce46a8b4514d0dce1e7a39b3d2b3a6125f7968093020f96a8a5ad09b" +checksum = "8a9a8e6a70c71f11595fa32f9128ebe56f1d986ad380db858799640a1039a0e2" dependencies = [ "console_log", "crs-definitions", "geo-types", "js-sys", - "lazy_static", "thiserror 2.0.18", "wasm-bindgen", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index bcd6b17f7c12f..53c15ca59963d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -270,7 +270,7 @@ futures = "0.3.24" futures-async-stream = { version = "0.2.7" } futures-util = "0.3.24" geo = { version = "0.32.0", features = ["use-serde"] } -geo-index = "0.3.2" +geo-index = "0.3.4" geohash = "0.13.1" geozero = { version = "0.15.1", features = ["with-geo", "with-geojson", "with-wkb", "with-wkt"] } gimli = "0.31.0" @@ -400,7 +400,7 @@ pprof = { git = "https://github.com/datafuse-extras/pprof-rs", rev = "edecd74", ] } pretty_assertions = "1.3.0" procfs = { version = "0.17.0" } -proj4rs = { version = "0.1.9", features = ["geo-types", "crs-definitions"] } +proj4rs = { version = "0.1.10", features = ["geo-types", "crs-definitions"] } proptest = { version = "1", default-features = false, features = ["std"] } prost = { version = "0.13" } prost-build = { version = "0.13" } diff --git a/src/common/io/src/geography.rs b/src/common/io/src/geography.rs index 9975b6069118c..55960bd8cfa2e 100644 --- a/src/common/io/src/geography.rs +++ b/src/common/io/src/geography.rs @@ -25,9 +25,16 @@ use geozero::ToGeo; use geozero::ToWkb; use geozero::geojson::GeoJson; use geozero::wkb::Ewkb; +use hex::encode_upper; use crate::ewkb_to_geo; +use crate::geometry::GeometryDataType; use crate::geometry::ewkt_str_to_geo; +use crate::geometry::geo_to_ewkb; +use crate::geometry::geo_to_ewkt; +use crate::geometry::geo_to_json; +use crate::geometry::geo_to_wkb; +use crate::geometry::geo_to_wkt; pub const LONGITUDE_MIN: f64 = -180.0; pub const LONGITUDE_MAX: f64 = 180.0; @@ -69,6 +76,20 @@ pub fn geography_from_geojson(json_str: &str) -> Result> { .map_err(|e| ErrorCode::GeometryError(e.to_string())) } +pub fn geography_format(ewkb: &[u8], format_type: GeometryDataType) -> Result { + let geo = Ewkb(ewkb) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string()))?; + + match format_type { + GeometryDataType::WKB => geo_to_wkb(geo).map(encode_upper), + GeometryDataType::EWKB => geo_to_ewkb(geo, Some(GEOGRAPHY_SRID)).map(encode_upper), + GeometryDataType::WKT => geo_to_wkt(geo), + GeometryDataType::EWKT => geo_to_ewkt(geo, Some(GEOGRAPHY_SRID)), + GeometryDataType::GEOJSON => geo_to_json(geo), + } +} + pub fn check_srid(srid: Option) -> Result<()> { if let Some(srid) = srid && srid != GEOGRAPHY_SRID diff --git a/src/common/io/src/geometry.rs b/src/common/io/src/geometry.rs index 3ab2f08a31abf..d8778fcf4cd81 100644 --- a/src/common/io/src/geometry.rs +++ b/src/common/io/src/geometry.rs @@ -19,7 +19,10 @@ use databend_common_exception::ErrorCode; use databend_common_exception::Result; use geo::BoundingRect; use geo::Geometry; +use geo::LineString; use geo::Point; +use geo::Polygon; +use geo::Rect; use geohash::encode; use geozero::CoordDimensions; use geozero::GeomProcessor; @@ -31,6 +34,7 @@ use geozero::ToWkt; use geozero::geo_types::GeoWriter; use geozero::geojson::GeoJson; use geozero::wkb::Ewkb; +use hex::encode_upper; use serde::Deserialize; use serde::Serialize; use wkt::TryFromWkt; @@ -185,49 +189,18 @@ pub(crate) fn ewkt_str_to_geo(input: &str) -> Result<(Geometry, Option)> { } } -pub trait GeometryFormatOutput { - fn format(self, data_type: GeometryDataType) -> Result; -} -impl> GeometryFormatOutput for Ewkb { - fn format(self, format_type: GeometryDataType) -> Result { - match format_type { - GeometryDataType::WKB => self - .to_wkb(CoordDimensions::xy()) - .map(|bytes| { - bytes - .iter() - .map(|b| format!("{:02X}", b)) - .collect::>() - .join("") - }) - .map_err(|e| ErrorCode::GeometryError(e.to_string())), - GeometryDataType::EWKB => Ok(self - .0 - .as_ref() - .iter() - .map(|b| format!("{:02X}", b)) - .collect::>() - .join("")), - GeometryDataType::WKT => self - .to_wkt() - .map_err(|e| ErrorCode::GeometryError(e.to_string())), - GeometryDataType::EWKT => self - .to_ewkt(self.srid()) - .map_err(|e| ErrorCode::GeometryError(e.to_string())), - GeometryDataType::GEOJSON => self - .to_json() - .map_err(|e| ErrorCode::GeometryError(e.to_string())), - } +pub fn geometry_format(ewkb: &[u8], format_type: GeometryDataType) -> Result { + let (geo, srid) = ewkb_to_geo(&mut Ewkb(ewkb))?; + let srid = srid.unwrap_or(0); + match format_type { + GeometryDataType::WKB => geo_to_wkb(geo).map(encode_upper), + GeometryDataType::EWKB => geo_to_ewkb(geo, Some(srid)).map(encode_upper), + GeometryDataType::WKT => geo_to_wkt(geo), + GeometryDataType::EWKT => geo_to_ewkt(geo, Some(srid)), + GeometryDataType::GEOJSON => geo_to_json(geo), } } -pub fn geometry_format( - geometry: T, - format_type: GeometryDataType, -) -> Result { - geometry.format(format_type) -} - /// Convert Geometry object to GEOJSON format. pub fn geo_to_json(geo: Geometry) -> Result { geo.to_json() @@ -258,6 +231,19 @@ pub fn geo_to_ewkt(geo: Geometry, srid: Option) -> Result { .map_err(|e| ErrorCode::GeometryError(e.to_string())) } +pub fn rect_to_polygon(rect: Rect) -> Polygon { + let min = rect.min(); + let max = rect.max(); + let exterior = LineString::from(vec![ + (min.x, min.y), + (max.x, min.y), + (max.x, max.y), + (min.x, max.y), + (min.x, min.y), + ]); + Polygon::new(exterior, vec![]) +} + /// Process EWKB input and return SRID. pub fn read_srid>(ewkb: &mut Ewkb) -> Option { let mut srid_processor = SridProcessor::new(); diff --git a/src/common/io/src/lib.rs b/src/common/io/src/lib.rs index 38d9166b31f7b..f21b7d2567f8d 100644 --- a/src/common/io/src/lib.rs +++ b/src/common/io/src/lib.rs @@ -62,6 +62,7 @@ pub use decimal::display_decimal_256_trimmed; pub use escape::escape_string; pub use escape::escape_string_with_quote; pub use geography::GEOGRAPHY_SRID; +pub use geography::geography_format; pub use geometry::Axis; pub use geometry::Extremum; pub use geometry::GeometryDataType; diff --git a/src/query/expression/Cargo.toml b/src/query/expression/Cargo.toml index 1d46b2f147f12..accd23eaad1e8 100644 --- a/src/query/expression/Cargo.toml +++ b/src/query/expression/Cargo.toml @@ -52,6 +52,7 @@ micromarshal = { workspace = true } num-bigint = { workspace = true } num-derive = { workspace = true } num-traits = { workspace = true } +proj4rs = { workspace = true } rand = { workspace = true } rand_distr = { workspace = true } recursive = { workspace = true } diff --git a/src/query/expression/src/geographic/aggregate.rs b/src/query/expression/src/geographic/aggregate.rs new file mode 100644 index 0000000000000..436d3178b048f --- /dev/null +++ b/src/query/expression/src/geographic/aggregate.rs @@ -0,0 +1,184 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use databend_common_exception::ErrorCode; +use databend_common_exception::Result; +use databend_common_io::geometry::rect_to_polygon; +use geo::BoundingRect; +use geo::Coord; +use geo::Geometry; +use geo::GeometryCollection; +use geo::LineString; +use geo::MultiLineString; +use geo::MultiPoint; +use geo::MultiPolygon; +use geo::Point; +use geo::Polygon; +use geo::Rect; + +use crate::geographic::GeometryOverlay; +use crate::geographic::OverlayMode; + +pub trait GeoAggOp: Send + Sync + 'static { + fn compute(geos: Vec>) -> Result>>; + fn binary_compute(l_geo: Geometry, r_geo: Geometry) -> Result>>; +} + +pub struct GeometryUnionAggOp; + +impl GeoAggOp for GeometryUnionAggOp { + fn compute(geos: Vec>) -> Result>> { + apply_geometry_overlay(geos, OverlayMode::Union) + } + fn binary_compute(l_geo: Geometry, r_geo: Geometry) -> Result>> { + apply_binary_geometry_overlay(l_geo, r_geo, OverlayMode::Union) + } +} + +pub struct GeometryIntersectionAggOp; + +impl GeoAggOp for GeometryIntersectionAggOp { + fn compute(geos: Vec>) -> Result>> { + apply_geometry_overlay(geos, OverlayMode::Intersection) + } + fn binary_compute(l_geo: Geometry, r_geo: Geometry) -> Result>> { + apply_binary_geometry_overlay(l_geo, r_geo, OverlayMode::Intersection) + } +} + +pub struct GeometryDifferenceAggOp; + +impl GeoAggOp for GeometryDifferenceAggOp { + fn compute(geos: Vec>) -> Result>> { + apply_geometry_overlay(geos, OverlayMode::Difference) + } + fn binary_compute(l_geo: Geometry, r_geo: Geometry) -> Result>> { + apply_binary_geometry_overlay(l_geo, r_geo, OverlayMode::Difference) + } +} + +pub struct GeometrySymDifferenceAggOp; + +impl GeoAggOp for GeometrySymDifferenceAggOp { + fn compute(geos: Vec>) -> Result>> { + apply_geometry_overlay(geos, OverlayMode::SymDifference) + } + fn binary_compute(l_geo: Geometry, r_geo: Geometry) -> Result>> { + apply_binary_geometry_overlay(l_geo, r_geo, OverlayMode::SymDifference) + } +} + +pub struct CollectAggOp; + +impl GeoAggOp for CollectAggOp { + fn compute(geos: Vec>) -> Result>> { + if geos.is_empty() { + return Ok(None); + } + + if geos.iter().all(|geo| matches!(geo, Geometry::Point(_))) { + let points: Vec> = geos + .into_iter() + .map(|geo| geo.try_into().expect("point geometry")) + .collect(); + let multi_point = MultiPoint::from_iter(points); + return Ok(Some(Geometry::MultiPoint(multi_point))); + } + if geos + .iter() + .all(|geo| matches!(geo, Geometry::LineString(_))) + { + let lines: Vec> = geos + .into_iter() + .map(|geo| geo.try_into().expect("linestring geometry")) + .collect(); + let multi_line_string = MultiLineString::from_iter(lines); + return Ok(Some(Geometry::MultiLineString(multi_line_string))); + } + if geos.iter().all(|geo| matches!(geo, Geometry::Polygon(_))) { + let polygons: Vec> = geos + .into_iter() + .map(|geo| geo.try_into().expect("polygon geometry")) + .collect(); + let multi_polygon = MultiPolygon::from_iter(polygons); + return Ok(Some(Geometry::MultiPolygon(multi_polygon))); + } + + let collection = GeometryCollection::from_iter(geos); + Ok(Some(Geometry::GeometryCollection(collection))) + } + + fn binary_compute(l_geo: Geometry, r_geo: Geometry) -> Result>> { + Self::compute(vec![l_geo, r_geo]) + } +} + +pub struct EnvelopeAggOp; + +impl GeoAggOp for EnvelopeAggOp { + fn compute(geos: Vec>) -> Result>> { + let mut has_rect = false; + let mut min_x = 0.0_f64; + let mut min_y = 0.0_f64; + let mut max_x = 0.0_f64; + let mut max_y = 0.0_f64; + + for geo in geos { + if let Some(rect) = geo.bounding_rect() { + let min = rect.min(); + let max = rect.max(); + if !has_rect { + min_x = min.x; + min_y = min.y; + max_x = max.x; + max_y = max.y; + has_rect = true; + } else { + min_x = min_x.min(min.x); + min_y = min_y.min(min.y); + max_x = max_x.max(max.x); + max_y = max_y.max(max.y); + } + } + } + + if !has_rect { + return Ok(None); + } + + let rect = Rect::new(Coord { x: min_x, y: min_y }, Coord { x: max_x, y: max_y }); + Ok(Some(Geometry::Polygon(rect_to_polygon(rect)))) + } + + fn binary_compute(l_geo: Geometry, r_geo: Geometry) -> Result>> { + Self::compute(vec![l_geo, r_geo]) + } +} + +fn apply_geometry_overlay( + geos: Vec>, + mode: OverlayMode, +) -> Result>> { + let overlay = GeometryOverlay::new(mode); + overlay.apply_iter(geos) +} + +fn apply_binary_geometry_overlay( + l_geo: Geometry, + r_geo: Geometry, + mode: OverlayMode, +) -> Result>> { + let overlay = GeometryOverlay::new(mode); + overlay.apply(&l_geo, &r_geo) +} diff --git a/src/query/expression/src/geographic/mod.rs b/src/query/expression/src/geographic/mod.rs new file mode 100644 index 0000000000000..651fa078641fa --- /dev/null +++ b/src/query/expression/src/geographic/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +mod aggregate; +mod overlay; + +pub use aggregate::*; +pub use overlay::*; diff --git a/src/query/expression/src/geographic/overlay.rs b/src/query/expression/src/geographic/overlay.rs new file mode 100644 index 0000000000000..406e4ce369a14 --- /dev/null +++ b/src/query/expression/src/geographic/overlay.rs @@ -0,0 +1,1406 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use databend_common_exception::Result; +use geo::BoundingRect; +use geo::Contains; +use geo::Coord; +use geo::Geometry; +use geo::GeometryCollection; +use geo::Intersects; +use geo::Line; +use geo::LineString; +use geo::MultiLineString; +use geo::MultiPoint; +use geo::MultiPolygon; +use geo::Point; +use geo::Polygon; +use geo::Rect; +use geo::Triangle; +use geo::algorithm::bool_ops::BooleanOps; +use geo::algorithm::bool_ops::unary_union; +use geo::algorithm::line_intersection::LineIntersection; +use geo::algorithm::line_intersection::line_intersection; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum OverlayMode { + Intersection, + Union, + Difference, + SymDifference, +} + +pub struct GeometryOverlay { + mode: OverlayMode, +} + +#[derive(Clone, Default)] +struct OverlayParts { + points: Vec>, + lines: Vec>, + polys: Vec>, +} + +struct LineGroup { + origin: Coord, + dir: Coord, + intervals: Vec<(f64, f64)>, +} + +struct SegmentInfo { + start: Coord, + end: Coord, +} + +impl GeometryOverlay { + const LINE_OVERLAY_EPS: f64 = 1e-9; + + pub fn new(mode: OverlayMode) -> Self { + Self { mode } + } + + pub fn apply(&self, g0: &Geometry, g1: &Geometry) -> Result>> { + if self.is_union_like() && Self::can_fast_merge(g0, g1) { + let units = vec![g0.clone(), g1.clone()]; + let result = Self::build_geometry_from_elements(units); + return Ok(result); + } + + let left = Self::prepare_operand(g0)?; + let right = Self::prepare_operand(g1)?; + + match self.mode { + OverlayMode::Union => self.do_union(&left, &right), + OverlayMode::Intersection => self.do_intersection(&left, &right), + OverlayMode::Difference => self.do_difference(&left, &right), + OverlayMode::SymDifference => self.do_symdifference(&left, &right), + } + } + + pub fn apply_iter(&self, geos: I) -> Result>> + where I: IntoIterator> { + match self.mode { + OverlayMode::Union => self.apply_union_iter(geos), + OverlayMode::Intersection => self.apply_intersection_iter(geos), + OverlayMode::Difference | OverlayMode::SymDifference => { + let mut iter = geos.into_iter(); + let Some(mut acc) = iter.next() else { + return Ok(None); + }; + + for geo in iter { + let Some(next) = self.apply(&acc, &geo)? else { + return Ok(None); + }; + acc = next; + } + + Ok(Some(acc)) + } + } + } + + fn is_union_like(&self) -> bool { + matches!(self.mode, OverlayMode::Union | OverlayMode::SymDifference) + } + + fn empty_geometry() -> Geometry { + Geometry::GeometryCollection(GeometryCollection::empty()) + } + + fn can_fast_merge(g0: &Geometry, g1: &Geometry) -> bool { + let (Some(r0), Some(r1)) = (g0.bounding_rect(), g1.bounding_rect()) else { + return false; + }; + if r0.intersects(&r1) { + return false; + } + + Self::is_single_geometry(g0) && Self::is_single_geometry(g1) + } + + fn is_single_geometry(geom: &Geometry) -> bool { + matches!( + geom, + Geometry::Point(_) | Geometry::LineString(_) | Geometry::Polygon(_) + ) + } + + fn build_geometry_from_elements(elements: Vec>) -> Option> { + if elements.is_empty() { + return None; + } + + if elements.iter().all(|geo| matches!(geo, Geometry::Point(_))) { + let points: Vec> = elements + .into_iter() + .map(|geo| geo.try_into().expect("point geometry")) + .collect(); + return Some(Geometry::MultiPoint(MultiPoint::from_iter(points))); + } + + if elements + .iter() + .all(|geo| matches!(geo, Geometry::LineString(_))) + { + let lines: Vec> = elements + .into_iter() + .map(|geo| geo.try_into().expect("linestring geometry")) + .collect(); + return Some(Geometry::MultiLineString(MultiLineString::from_iter(lines))); + } + + if elements + .iter() + .all(|geo| matches!(geo, Geometry::Polygon(_))) + { + let polygons: Vec> = elements + .into_iter() + .map(|geo| geo.try_into().expect("polygon geometry")) + .collect(); + return Some(Geometry::MultiPolygon(MultiPolygon::from_iter(polygons))); + } + + Some(Geometry::GeometryCollection(GeometryCollection::from_iter( + elements, + ))) + } + + fn apply_union_iter(&self, geos: I) -> Result>> + where I: IntoIterator> { + let mut merged = OverlayParts::default(); + let mut has_input = false; + + for geo in geos { + let parts = Self::prepare_operand(&geo)?; + Self::extend_parts(&mut merged, parts); + has_input = true; + } + + if !has_input { + return Ok(None); + } + + Self::assemble_result(merged.polys, merged.lines, merged.points) + } + + fn apply_intersection_iter(&self, geos: I) -> Result>> + where I: IntoIterator> { + let mut iter = geos.into_iter(); + let Some(first) = iter.next() else { + return Ok(None); + }; + + let mut acc = Self::prepare_operand(&first)?; + for geo in iter { + let next = Self::prepare_operand(&geo)?; + acc = self.intersect_parts(acc, next)?; + if Self::parts_are_empty(&acc) { + return Ok(None); + } + } + + Self::assemble_result(acc.polys, acc.lines, acc.points) + } + + fn prepare_operand(geom: &Geometry) -> Result { + let mut parts = OverlayParts::default(); + Self::collect_overlay_parts(geom, &mut parts); + // Normalize each dimension independently before binary overlay: + // - points are deduplicated + // - lines are split/merged into a stable line set + // - polygons are unary-unioned into a stable polygon set + parts.points = Self::union_points(parts.points); + parts.lines = Self::union_lines(parts.lines)?; + parts.polys = Self::union_polys(parts.polys)?; + Ok(parts) + } + + fn normalize_parts(mut parts: OverlayParts) -> Result { + parts.points = Self::union_points(parts.points); + parts.lines = Self::union_lines(parts.lines)?; + parts.polys = Self::union_polys(parts.polys)?; + Ok(parts) + } + + fn extend_parts(target: &mut OverlayParts, mut source: OverlayParts) { + target.points.append(&mut source.points); + target.lines.append(&mut source.lines); + target.polys.append(&mut source.polys); + } + + fn parts_are_empty(parts: &OverlayParts) -> bool { + parts.points.is_empty() && parts.lines.is_empty() && parts.polys.is_empty() + } + + fn collect_overlay_parts(geom: &Geometry, parts: &mut OverlayParts) { + match geom { + Geometry::GeometryCollection(collection) => { + for sub in &collection.0 { + Self::collect_overlay_parts(sub, parts); + } + } + Geometry::Point(point) => { + parts.points.push(*point); + } + Geometry::MultiPoint(multi_point) => { + parts.points.extend(multi_point.0.iter().copied()); + } + Geometry::Line(line) => { + let line = LineString::from(vec![line.start, line.end]); + if !line.0.is_empty() { + parts.lines.push(line); + } + } + Geometry::LineString(line_string) => { + if !line_string.0.is_empty() { + parts.lines.push(line_string.clone()); + } + } + Geometry::MultiLineString(multi_line) => { + for line in &multi_line.0 { + if !line.0.is_empty() { + parts.lines.push(line.clone()); + } + } + } + Geometry::Polygon(polygon) => { + if !polygon.exterior().0.is_empty() { + parts.polys.push(polygon.clone()); + } + } + Geometry::MultiPolygon(multi_polygon) => { + for polygon in &multi_polygon.0 { + if !polygon.exterior().0.is_empty() { + parts.polys.push(polygon.clone()); + } + } + } + Geometry::Rect(rect) => { + parts.polys.push(rect.to_polygon()); + } + Geometry::Triangle(triangle) => { + parts.polys.push(triangle.to_polygon()); + } + } + } + + // Final assembly always applies the same dimensional rules: + // 1. multiple polygons are merged with unary union + // 2. multiple lines are split/merged/deduplicated into a normalized line set + // 3. multiple points are deduplicated + // 4. points covered by lines are removed + // 5. points covered by polygons are removed + // 6. line segments covered by polygons are removed + fn assemble_result( + polys: Vec>, + lines: Vec>, + points: Vec>, + ) -> Result>> { + let polys = Self::union_polys(polys)?; + let mut lines = Self::union_lines(lines)?; + let mut points = Self::union_points(points); + + if !polys.is_empty() { + lines = Self::difference_lines_by_polys(lines, &polys)?; + points = Self::difference_points_by_polys(points, &polys); + } + + if !lines.is_empty() { + points = Self::difference_points_by_lines(points, &lines); + } + + let mut outputs = Vec::new(); + if let Some(geom) = Self::geometry_from_polys(&polys) { + outputs.push(geom); + } + if let Some(geom) = Self::geometry_from_lines(&lines) { + outputs.push(geom); + } + if let Some(geom) = Self::geometry_from_points(&points) { + outputs.push(geom); + } + + match outputs.len() { + 0 => Ok(None), + 1 => Ok(Some(outputs.remove(0))), + _ => Ok(Self::build_geometry_from_elements(outputs)), + } + } + + fn do_union(&self, left: &OverlayParts, right: &OverlayParts) -> Result>> { + let mut polys = left.polys.clone(); + polys.extend(right.polys.iter().cloned()); + + let mut lines = left.lines.clone(); + lines.extend(right.lines.iter().cloned()); + + let mut points = left.points.clone(); + points.extend(right.points.iter().copied()); + + Self::assemble_result(polys, lines, points) + } + + fn do_intersection( + &self, + left: &OverlayParts, + right: &OverlayParts, + ) -> Result>> { + let parts = self.intersect_parts(left.clone(), right.clone())?; + Self::assemble_result(parts.polys, parts.lines, parts.points) + } + + fn intersect_parts(&self, left: OverlayParts, right: OverlayParts) -> Result { + let polys = Self::intersection_polys(&left.polys, &right.polys)?; + + let mut lines = Self::clip_lines_to_polys(left.lines.clone(), &right.polys); + lines.extend(Self::clip_lines_to_polys(right.lines.clone(), &left.polys)); + let line_parts = Self::intersection_lines_parts(&left.lines, &right.lines); + lines.extend(line_parts.lines); + + let mut points = line_parts.points; + points.extend(Self::intersection_points_in_polys( + &left.points, + &right.polys, + )); + points.extend(Self::intersection_points_in_polys( + &right.points, + &left.polys, + )); + points.extend(Self::intersection_points_on_lines( + &left.points, + &right.lines, + )); + points.extend(Self::intersection_points_on_lines( + &right.points, + &left.lines, + )); + points.extend(Self::intersection_points(&left.points, &right.points)); + + Self::normalize_parts(OverlayParts { + points, + lines, + polys, + }) + } + + fn do_difference( + &self, + left: &OverlayParts, + right: &OverlayParts, + ) -> Result>> { + let parts = self.difference_parts(left, right)?; + Self::assemble_result(parts.polys, parts.lines, parts.points) + } + + fn do_symdifference( + &self, + left: &OverlayParts, + right: &OverlayParts, + ) -> Result>> { + if left.points.is_empty() + && right.points.is_empty() + && left.polys.is_empty() + && right.polys.is_empty() + { + let shared = Self::intersection_lines_parts(&left.lines, &right.lines); + if shared.lines.is_empty() { + let mut lines = left.lines.clone(); + lines.extend(right.lines.iter().cloned()); + return Self::assemble_result(Vec::new(), lines, Vec::new()); + } + } + + let left_only = self.difference_parts(left, right)?; + let right_only = self.difference_parts(right, left)?; + + let mut polys = left_only.polys; + polys.extend(right_only.polys); + + let mut lines = left_only.lines; + lines.extend(right_only.lines); + + let mut points = left_only.points; + points.extend(right_only.points); + + Self::assemble_result(polys, lines, points) + } + + fn difference_parts(&self, left: &OverlayParts, right: &OverlayParts) -> Result { + let polys = Self::difference_polys(&left.polys, &right.polys)?; + let line_diff_poly = Self::difference_lines_by_polys(left.lines.clone(), &right.polys)?; + let lines = Self::difference_lines(&line_diff_poly, &right.lines); + + let mut points = Self::difference_points_by_polys(left.points.clone(), &right.polys); + points = Self::difference_points_by_lines(points, &right.lines); + points = Self::difference_points(points, &right.points); + + Ok(OverlayParts { + points, + lines, + polys, + }) + } + + fn intersection_polys( + left: &[Polygon], + right: &[Polygon], + ) -> Result>> { + if left.is_empty() || right.is_empty() { + return Ok(Vec::new()); + } + + let left_mp = MultiPolygon::new(left.to_vec()); + let right_mp = MultiPolygon::new(right.to_vec()); + Ok(left_mp.intersection(&right_mp).0) + } + + fn intersection_lines_parts( + left: &[LineString], + right: &[LineString], + ) -> OverlayParts { + if left.is_empty() || right.is_empty() { + return OverlayParts::default(); + } + + let mut lines = Vec::new(); + for line in left { + let segments = Self::split_line_by_cutters(line, right); + for segment in segments { + if Self::segment_is_on_lines(&segment, right) { + lines.push(segment); + } + } + } + + let points = Self::dedup_points(Self::intersection_points_from_lines(left, right)); + + OverlayParts { + points, + lines, + polys: Vec::new(), + } + } + + fn intersection_points_in_polys( + points: &[Point], + polys: &[Polygon], + ) -> Vec> { + if points.is_empty() || polys.is_empty() { + return Vec::new(); + } + + points + .iter() + .copied() + .filter(|point| polys.iter().any(|poly| poly.intersects(point))) + .collect() + } + + fn intersection_points_on_lines( + points: &[Point], + lines: &[LineString], + ) -> Vec> { + if points.is_empty() || lines.is_empty() { + return Vec::new(); + } + + points + .iter() + .copied() + .filter(|point| lines.iter().any(|line| Self::line_has_point(line, *point))) + .collect() + } + + fn geometry_from_points(points: &[Point]) -> Option> { + match points.len() { + 0 => None, + 1 => Some(Geometry::Point(points[0])), + _ => Some(Geometry::MultiPoint(MultiPoint::from_iter( + points.iter().copied(), + ))), + } + } + + fn geometry_from_lines(lines: &[LineString]) -> Option> { + match lines.len() { + 0 => None, + 1 => Some(Geometry::LineString(lines[0].clone())), + _ => Some(Geometry::MultiLineString(MultiLineString::new( + lines.to_vec(), + ))), + } + } + + fn geometry_from_polys(polys: &[Polygon]) -> Option> { + match polys.len() { + 0 => None, + 1 => Some(Geometry::Polygon(polys[0].clone())), + _ => Some(Geometry::MultiPolygon(MultiPolygon::new(polys.to_vec()))), + } + } + + fn union_points(points: Vec>) -> Vec> { + Self::dedup_points(points) + } + + fn union_lines(lines: Vec>) -> Result>> { + if lines.is_empty() { + return Ok(Vec::new()); + } + let merged = Self::merge_collinear_lines(lines); + let split = Self::split_lines_at_intersections(merged); + let merged = Self::merge_collinear_lines(split); + Ok(Self::dedup_lines(merged)) + } + + fn union_polys(polys: Vec>) -> Result>> { + if polys.is_empty() { + return Ok(Vec::new()); + } + let merged = unary_union(&polys); + Ok(merged.into_iter().collect()) + } + + fn difference_polys( + left: &[Polygon], + right: &[Polygon], + ) -> Result>> { + if left.is_empty() { + return Ok(Vec::new()); + } + if right.is_empty() { + return Ok(left.to_vec()); + } + let left_mp = MultiPolygon::new(left.to_vec()); + let right_mp = MultiPolygon::new(right.to_vec()); + Ok(left_mp.difference(&right_mp).0) + } + + fn difference_lines_by_polys( + lines: Vec>, + polys: &[Polygon], + ) -> Result>> { + if lines.is_empty() { + return Ok(Vec::new()); + } + if polys.is_empty() { + return Ok(lines); + } + + let mut outside = lines; + for poly in polys { + let mut next = Vec::new(); + for line in outside { + let (_, outside_parts) = Self::split_line_by_polygon(&line, poly); + next.extend(outside_parts); + } + outside = next; + if outside.is_empty() { + break; + } + } + Ok(outside) + } + + fn difference_lines( + lines: &[LineString], + cutters: &[LineString], + ) -> Vec> { + if cutters.is_empty() { + return lines.to_vec(); + } + + let mut result = Vec::new(); + for line in lines { + let segments = Self::split_line_by_cutters(line, cutters); + for segment in segments { + if Self::segment_is_on_lines(&segment, cutters) { + continue; + } + result.push(segment); + } + } + result + } + + fn difference_points(points: Vec>, cutters: &[Point]) -> Vec> { + if cutters.is_empty() { + return Self::dedup_points(points); + } + let cutters = Self::dedup_points(cutters.to_vec()); + let filtered: Vec> = points + .into_iter() + .filter(|point| !cutters.iter().any(|cut| Self::point_eq(*point, *cut))) + .collect(); + Self::dedup_points(filtered) + } + + fn difference_points_by_lines( + points: Vec>, + lines: &[LineString], + ) -> Vec> { + if lines.is_empty() { + return Self::dedup_points(points); + } + let filtered: Vec> = points + .into_iter() + .filter(|point| !lines.iter().any(|line| Self::line_has_point(line, *point))) + .collect(); + Self::dedup_points(filtered) + } + + fn difference_points_by_polys( + points: Vec>, + polys: &[Polygon], + ) -> Vec> { + if polys.is_empty() { + return Self::dedup_points(points); + } + let filtered: Vec> = points + .into_iter() + .filter(|point| !polys.iter().any(|poly| poly.intersects(point))) + .collect(); + Self::dedup_points(filtered) + } + + fn intersection_points(left: &[Point], right: &[Point]) -> Vec> { + if left.is_empty() || right.is_empty() { + return Vec::new(); + } + let left = Self::dedup_points(left.to_vec()); + let right = Self::dedup_points(right.to_vec()); + left.into_iter() + .filter(|point| right.iter().any(|other| Self::point_eq(*point, *other))) + .collect() + } + + fn intersection_points_from_lines( + left: &[LineString], + right: &[LineString], + ) -> Vec> { + let mut points = Vec::new(); + for line in left { + let segments_left = Self::line_segments(line); + for other in right { + let segments_right = Self::line_segments(other); + for seg_a in &segments_left { + for seg_b in &segments_right { + let line_a = Line::new(seg_a.0, seg_a.1); + let line_b = Line::new(seg_b.0, seg_b.1); + let Some(intersection) = line_intersection(line_a, line_b) else { + continue; + }; + if let LineIntersection::SinglePoint { intersection, .. } = intersection { + points.push(Point(intersection)); + } + } + } + } + } + points + } + + fn merge_collinear_lines(lines: Vec>) -> Vec> { + let mut merged_lines = Vec::new(); + let mut non_linear_lines = Vec::new(); + let mut groups = Vec::new(); + + for line in lines { + let (origin, dir) = match Self::line_direction(&line) { + Some(res) => res, + None => { + non_linear_lines.push(line); + continue; + } + }; + + if !Self::line_is_collinear(&line, origin, dir) { + non_linear_lines.push(line); + continue; + } + + let mut inserted = false; + for group in &mut groups { + if Self::line_belongs_to_group(&origin, &dir, group) { + group.add_line(&line); + inserted = true; + break; + } + } + if !inserted { + let mut group = LineGroup::new(origin, dir); + group.add_line(&line); + groups.push(group); + } + } + + for group in groups { + merged_lines.extend(group.merge()); + } + + merged_lines.extend(non_linear_lines); + merged_lines + } + + fn split_lines_at_intersections(lines: Vec>) -> Vec> { + if lines.len() < 2 { + return lines; + } + + let mut segments = Vec::new(); + for line in &lines { + if line.0.len() < 2 { + continue; + } + for seg_idx in 0..(line.0.len() - 1) { + let start = line.0[seg_idx]; + let end = line.0[seg_idx + 1]; + if !Self::coord_eq(start, end) { + segments.push(SegmentInfo { start, end }); + } + } + } + + let mut split_params: Vec> = vec![vec![0.0, 1.0]; segments.len()]; + for i in 0..segments.len() { + for j in (i + 1)..segments.len() { + let seg_i = &segments[i]; + let seg_j = &segments[j]; + let line_i = Line::new(seg_i.start, seg_i.end); + let line_j = Line::new(seg_j.start, seg_j.end); + let Some(intersection) = line_intersection(line_i, line_j) else { + continue; + }; + + let point = match intersection { + LineIntersection::SinglePoint { intersection, .. } => intersection, + LineIntersection::Collinear { .. } => continue, + }; + + if let Some(t_i) = Self::line_param(seg_i.start, seg_i.end, point) { + if (-Self::LINE_OVERLAY_EPS..=1.0 + Self::LINE_OVERLAY_EPS).contains(&t_i) { + split_params[i].push(t_i.clamp(0.0, 1.0)); + } + } + if let Some(t_j) = Self::line_param(seg_j.start, seg_j.end, point) { + if (-Self::LINE_OVERLAY_EPS..=1.0 + Self::LINE_OVERLAY_EPS).contains(&t_j) { + split_params[j].push(t_j.clamp(0.0, 1.0)); + } + } + } + } + + let mut out = Vec::new(); + for (seg_idx, seg) in segments.iter().enumerate() { + let mut params = split_params[seg_idx].clone(); + params.sort_by(|a, b| a.total_cmp(b)); + params.dedup_by(|a, b| (*a - *b).abs() <= Self::LINE_OVERLAY_EPS); + + for win in params.windows(2) { + let t0 = win[0]; + let t1 = win[1]; + if t1 - t0 <= Self::LINE_OVERLAY_EPS { + continue; + } + let start = Self::interp(seg.start, seg.end, t0); + let end = Self::interp(seg.start, seg.end, t1); + if Self::coord_eq(start, end) { + continue; + } + out.push(LineString::from(vec![start, end])); + } + } + + if out.is_empty() { + return lines; + } + out + } + + fn clip_lines_to_polys( + lines: Vec>, + polys: &[Polygon], + ) -> Vec> { + if polys.is_empty() { + return Vec::new(); + } + + let mut inside = Vec::new(); + for line in lines { + for poly in polys { + let (inside_parts, _) = Self::split_line_by_polygon(&line, poly); + inside.extend(inside_parts); + } + } + inside + } + + fn line_direction(line: &LineString) -> Option<(Coord, Coord)> { + if line.0.len() < 2 { + return None; + } + + let origin = line.0[0]; + let mut idx = 1; + while idx < line.0.len() { + let coord = line.0[idx]; + if !Self::coord_eq(origin, coord) { + let dir = Coord { + x: coord.x - origin.x, + y: coord.y - origin.y, + }; + if dir.x != 0.0 || dir.y != 0.0 { + return Some((origin, dir)); + } + } + idx += 1; + } + None + } + + fn line_is_collinear(line: &LineString, origin: Coord, dir: Coord) -> bool { + line.0 + .iter() + .all(|coord| Self::point_on_line(origin, dir, *coord)) + } + + fn line_belongs_to_group(origin: &Coord, dir: &Coord, group: &LineGroup) -> bool { + Self::is_parallel(*dir, group.dir) && Self::point_on_line(group.origin, group.dir, *origin) + } + + fn point_on_line(origin: Coord, dir: Coord, coord: Coord) -> bool { + let dx = coord.x - origin.x; + let dy = coord.y - origin.y; + let cross = dx * dir.y - dy * dir.x; + let len = (dir.x * dir.x + dir.y * dir.y).sqrt().max(1.0); + cross.abs() <= Self::LINE_OVERLAY_EPS * len + } + + fn is_parallel(a: Coord, b: Coord) -> bool { + let cross = a.x * b.y - a.y * b.x; + let len = (a.x * a.x + a.y * a.y).sqrt().max(1.0) * (b.x * b.x + b.y * b.y).sqrt().max(1.0); + cross.abs() <= Self::LINE_OVERLAY_EPS * len + } + + fn line_segments(line: &LineString) -> Vec<(Coord, Coord)> { + if line.0.len() < 2 { + return Vec::new(); + } + + let mut segments = Vec::with_capacity(line.0.len() - 1); + for idx in 0..(line.0.len() - 1) { + let start = line.0[idx]; + let end = line.0[idx + 1]; + if !Self::coord_eq(start, end) { + segments.push((start, end)); + } + } + segments + } + + fn dedup_lines(lines: Vec>) -> Vec> { + let mut output = Vec::new(); + for line in lines { + if output.iter().any(|existing| Self::line_eq(existing, &line)) { + continue; + } + output.push(line); + } + output + } + + fn line_eq(a: &LineString, b: &LineString) -> bool { + if a.0.len() != b.0.len() { + return false; + } + if a.0 + .iter() + .zip(&b.0) + .all(|(ca, cb)| Self::coord_eq(*ca, *cb)) + { + return true; + } + a.0.iter() + .zip(b.0.iter().rev()) + .all(|(ca, cb)| Self::coord_eq(*ca, *cb)) + } + + fn split_line_by_cutters( + line: &LineString, + cutters: &[LineString], + ) -> Vec> { + if line.0.len() < 2 { + return Vec::new(); + } + + let mut output = Vec::new(); + for idx in 0..(line.0.len() - 1) { + let start = line.0[idx]; + let end = line.0[idx + 1]; + if Self::coord_eq(start, end) { + continue; + } + + let mut params = vec![0.0_f64, 1.0_f64]; + Self::collect_line_intersections(start, end, cutters, &mut params); + params.sort_by(|a, b| a.total_cmp(b)); + params.dedup_by(|a, b| (*a - *b).abs() <= Self::LINE_OVERLAY_EPS); + + for win in params.windows(2) { + let t0 = win[0]; + let t1 = win[1]; + if t1 - t0 <= Self::LINE_OVERLAY_EPS { + continue; + } + let s = Self::interp(start, end, t0); + let e = Self::interp(start, end, t1); + if Self::coord_eq(s, e) { + continue; + } + output.push(LineString::from(vec![s, e])); + } + } + output + } + + fn split_line_by_polygon( + line: &LineString, + polygon: &Polygon, + ) -> (Vec>, Vec>) { + if line.0.len() < 2 { + return (Vec::new(), Vec::new()); + } + + let mut inside = Vec::new(); + let mut outside = Vec::new(); + for idx in 0..(line.0.len() - 1) { + let start = line.0[idx]; + let end = line.0[idx + 1]; + if Self::coord_eq(start, end) { + continue; + } + + let mut params = vec![0.0_f64, 1.0_f64]; + Self::collect_polygon_intersections(start, end, polygon, &mut params); + params.sort_by(|a, b| a.total_cmp(b)); + params.dedup_by(|a, b| (*a - *b).abs() <= Self::LINE_OVERLAY_EPS); + + for win in params.windows(2) { + let t0 = win[0]; + let t1 = win[1]; + if t1 - t0 <= Self::LINE_OVERLAY_EPS { + continue; + } + + let s = Self::interp(start, end, t0); + let e = Self::interp(start, end, t1); + if Self::coord_eq(s, e) { + continue; + } + + let mid = Self::interp(start, end, (t0 + t1) * 0.5); + let point = Point(mid); + if polygon.intersects(&point) { + inside.push(LineString::from(vec![s, e])); + } else { + outside.push(LineString::from(vec![s, e])); + } + } + } + + (inside, outside) + } + + fn collect_polygon_intersections( + start: Coord, + end: Coord, + polygon: &Polygon, + params: &mut Vec, + ) { + Self::collect_ring_intersections(start, end, polygon.exterior(), params); + for ring in polygon.interiors() { + Self::collect_ring_intersections(start, end, ring, params); + } + } + + fn collect_ring_intersections( + start: Coord, + end: Coord, + ring: &LineString, + params: &mut Vec, + ) { + if ring.0.len() < 2 { + return; + } + + let line_a = Line::new(start, end); + for idx in 0..(ring.0.len() - 1) { + let a = ring.0[idx]; + let b = ring.0[idx + 1]; + let line_b = Line::new(a, b); + let Some(intersection) = line_intersection(line_a, line_b) else { + continue; + }; + match intersection { + LineIntersection::SinglePoint { intersection, .. } => { + if let Some(t) = Self::line_param(start, end, intersection) { + params.push(t); + } + } + LineIntersection::Collinear { intersection } => { + if let Some(t1) = Self::line_param(start, end, intersection.start) { + params.push(t1); + } + if let Some(t2) = Self::line_param(start, end, intersection.end) { + params.push(t2); + } + } + } + } + } + + fn collect_line_intersections( + start: Coord, + end: Coord, + cutters: &[LineString], + params: &mut Vec, + ) { + let line_a = Line::new(start, end); + for cutter in cutters { + if cutter.0.len() < 2 { + continue; + } + for idx in 0..(cutter.0.len() - 1) { + let a = cutter.0[idx]; + let b = cutter.0[idx + 1]; + let line_b = Line::new(a, b); + let Some(intersection) = line_intersection(line_a, line_b) else { + continue; + }; + match intersection { + LineIntersection::SinglePoint { intersection, .. } => { + if let Some(t) = Self::line_param(start, end, intersection) { + params.push(t); + } + } + LineIntersection::Collinear { intersection } => { + if let Some(t) = Self::line_param(start, end, intersection.start) { + params.push(t); + } + if let Some(t) = Self::line_param(start, end, intersection.end) { + params.push(t); + } + } + } + } + } + } + + fn segment_is_on_lines(segment: &LineString, lines: &[LineString]) -> bool { + if segment.0.len() < 2 { + return true; + } + let mid = Self::interp(segment.0[0], segment.0[1], 0.5); + let point = Point(mid); + lines.iter().any(|line| Self::line_has_point(line, point)) + } + + fn line_has_point(line: &LineString, point: Point) -> bool { + if line.0.len() < 2 { + return false; + } + + line.0.windows(2).any(|segment| { + let start = segment[0]; + let end = segment[1]; + Self::segment_has_point(start, end, point.0) + }) + } + + fn segment_has_point(start: Coord, end: Coord, point: Coord) -> bool { + if Self::coord_eq(start, end) { + return Self::coord_eq(start, point); + } + + if Self::coord_eq(start, point) || Self::coord_eq(end, point) { + return true; + } + + let dir = Coord { + x: end.x - start.x, + y: end.y - start.y, + }; + if !Self::point_on_line(start, dir, point) { + return false; + } + + let Some(t) = Self::line_param(start, end, point) else { + return false; + }; + + (-Self::LINE_OVERLAY_EPS..=1.0 + Self::LINE_OVERLAY_EPS).contains(&t) + } + + fn line_param(start: Coord, end: Coord, point: Coord) -> Option { + let dx = end.x - start.x; + let dy = end.y - start.y; + let denom = dx * dx + dy * dy; + if denom == 0.0 { + return None; + } + Some(((point.x - start.x) * dx + (point.y - start.y) * dy) / denom) + } + + fn interp(start: Coord, end: Coord, t: f64) -> Coord { + Coord { + x: start.x + (end.x - start.x) * t, + y: start.y + (end.y - start.y) * t, + } + } + + fn coord_eq(a: Coord, b: Coord) -> bool { + (a.x - b.x).abs() <= Self::LINE_OVERLAY_EPS && (a.y - b.y).abs() <= Self::LINE_OVERLAY_EPS + } + + fn point_eq(a: Point, b: Point) -> bool { + Self::coord_eq(a.0, b.0) + } + + fn dedup_points(mut points: Vec>) -> Vec> { + points.sort_by(|a, b| { + let dx = a.x().total_cmp(&b.x()); + if dx == std::cmp::Ordering::Equal { + a.y().total_cmp(&b.y()) + } else { + dx + } + }); + points.dedup_by(|a, b| Self::point_eq(*a, *b)); + points + } +} + +impl LineGroup { + fn new(origin: Coord, dir: Coord) -> Self { + Self { + origin, + dir, + intervals: Vec::new(), + } + } + + fn add_line(&mut self, line: &LineString) { + let denom = self.dir.x * self.dir.x + self.dir.y * self.dir.y; + if denom == 0.0 { + return; + } + + let mut min_t = f64::INFINITY; + let mut max_t = f64::NEG_INFINITY; + for coord in &line.0 { + let t = ((coord.x - self.origin.x) * self.dir.x + + (coord.y - self.origin.y) * self.dir.y) + / denom; + min_t = min_t.min(t); + max_t = max_t.max(t); + } + + if min_t.is_finite() && max_t.is_finite() { + self.intervals.push((min_t, max_t)); + } + } + + fn merge(mut self) -> Vec> { + if self.intervals.is_empty() { + return Vec::new(); + } + + self.intervals.sort_by(|a, b| a.0.total_cmp(&b.0)); + + let mut merged = Vec::new(); + let mut current = self.intervals[0]; + for interval in self.intervals.into_iter().skip(1) { + if interval.0 <= current.1 + GeometryOverlay::LINE_OVERLAY_EPS { + current.1 = current.1.max(interval.1); + } else { + merged.push(current); + current = interval; + } + } + merged.push(current); + + merged + .into_iter() + .map(|(start, end)| { + let start = Coord { + x: self.origin.x + self.dir.x * start, + y: self.origin.y + self.dir.y * start, + }; + let end = Coord { + x: self.origin.x + self.dir.x * end, + y: self.origin.y + self.dir.y * end, + }; + LineString::from(vec![start, end]) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn point(x: f64, y: f64) -> Geometry { + Geometry::Point(Point::new(x, y)) + } + + fn line(coords: &[(f64, f64)]) -> Geometry { + Geometry::LineString(LineString::from( + coords + .iter() + .map(|(x, y)| Coord { x: *x, y: *y }) + .collect::>(), + )) + } + + #[test] + fn symdifference_keeps_points_from_both_sides() { + let overlay = GeometryOverlay::new(OverlayMode::SymDifference); + let result = overlay.apply(&point(0.0, 0.0), &point(1.0, 1.0)).unwrap(); + assert!(result.is_some()); + let result = result.unwrap(); + + match result { + Geometry::MultiPoint(multi_point) => { + assert_eq!(multi_point.0.len(), 2); + assert!( + multi_point + .0 + .iter() + .any(|p| GeometryOverlay::point_eq(*p, Point::new(0.0, 0.0))) + ); + assert!( + multi_point + .0 + .iter() + .any(|p| GeometryOverlay::point_eq(*p, Point::new(1.0, 1.0))) + ); + } + other => panic!("unexpected geometry: {other:?}"), + } + } + + #[test] + fn difference_removes_point_covered_by_right_line() { + let overlay = GeometryOverlay::new(OverlayMode::Difference); + let result = overlay + .apply(&point(1.0, 0.0), &line(&[(0.0, 0.0), (2.0, 0.0)])) + .unwrap(); + assert!(result.is_none()); + } + + #[test] + fn difference_removes_point_at_line_endpoint() { + let overlay = GeometryOverlay::new(OverlayMode::Difference); + let result = overlay + .apply(&point(0.0, 0.0), &line(&[(0.0, 0.0), (2.0, 0.0)])) + .unwrap(); + assert!(result.is_none()); + } + + #[test] + fn difference_removes_point_on_polygon_boundary() { + let overlay = GeometryOverlay::new(OverlayMode::Difference); + let polygon = Geometry::Polygon(Polygon::new( + LineString::from(vec![ + Coord { x: 1.0, y: 1.0 }, + Coord { x: 2.0, y: 1.0 }, + Coord { x: 2.0, y: 2.0 }, + Coord { x: 1.0, y: 2.0 }, + Coord { x: 1.0, y: 1.0 }, + ]), + vec![], + )); + let result = overlay.apply(&point(1.0, 1.0), &polygon).unwrap(); + assert!(result.is_none()); + } + + #[test] + fn intersection_keeps_point_at_line_endpoint() { + let overlay = GeometryOverlay::new(OverlayMode::Intersection); + let result = overlay + .apply(&point(2.0, 2.0), &line(&[(0.0, 0.0), (2.0, 2.0)])) + .unwrap(); + assert!(result.is_some()); + + match result.unwrap() { + Geometry::Point(point) => { + assert!(GeometryOverlay::point_eq(point, Point::new(2.0, 2.0))); + } + other => panic!("unexpected geometry: {other:?}"), + } + } + + #[test] + fn symdifference_returns_exclusive_line_segments() { + let overlay = GeometryOverlay::new(OverlayMode::SymDifference); + let result = overlay + .apply( + &line(&[(0.0, 0.0), (4.0, 0.0)]), + &line(&[(2.0, 0.0), (6.0, 0.0)]), + ) + .unwrap(); + assert!(result.is_some()); + let result = result.unwrap(); + + match result { + Geometry::MultiLineString(multi_line) => { + assert_eq!(multi_line.0.len(), 2); + assert!(multi_line.0.iter().any(|line| GeometryOverlay::line_eq( + line, + &LineString::from(vec![Coord { x: 0.0, y: 0.0 }, Coord { x: 2.0, y: 0.0 },]) + ))); + assert!(multi_line.0.iter().any(|line| GeometryOverlay::line_eq( + line, + &LineString::from(vec![Coord { x: 4.0, y: 0.0 }, Coord { x: 6.0, y: 0.0 },]) + ))); + } + other => panic!("unexpected geometry: {other:?}"), + } + } + + #[test] + fn symdifference_merges_lines_touching_at_endpoint() { + let overlay = GeometryOverlay::new(OverlayMode::SymDifference); + let result = overlay + .apply( + &line(&[(0.0, 0.0), (2.0, 2.0)]), + &line(&[(2.0, 2.0), (4.0, 4.0)]), + ) + .unwrap(); + assert!(result.is_some()); + let result = result.unwrap(); + + match result { + Geometry::LineString(line) => { + assert!(GeometryOverlay::line_eq( + &line, + &LineString::from(vec![Coord { x: 0.0, y: 0.0 }, Coord { x: 4.0, y: 4.0 },]), + )); + } + other => panic!("unexpected geometry: {other:?}"), + } + } +} diff --git a/src/query/expression/src/lib.rs b/src/query/expression/src/lib.rs index 2b6a416a41ba1..afe61f1f40ed9 100755 --- a/src/query/expression/src/lib.rs +++ b/src/query/expression/src/lib.rs @@ -61,6 +61,7 @@ mod evaluator; mod expression; pub mod filter; mod function; +pub mod geographic; pub mod hash_util; mod hilbert; mod kernels; diff --git a/src/query/expression/src/utils/display.rs b/src/query/expression/src/utils/display.rs index 033021e87cf08..2418cb54150a5 100755 --- a/src/query/expression/src/utils/display.rs +++ b/src/query/expression/src/utils/display.rs @@ -23,11 +23,15 @@ use databend_common_ast::ast::quote::QuotedString; use databend_common_ast::ast::quote::display_ident; use databend_common_ast::parser::Dialect; use databend_common_column::binary::BinaryColumn; +use databend_common_exception::ErrorCode; +use databend_common_io::GEOGRAPHY_SRID; use databend_common_io::deserialize_bitmap; use databend_common_io::display_decimal_128; use databend_common_io::display_decimal_256; use databend_common_io::ewkb_to_geo; use databend_common_io::geo_to_ewkt; +use databend_common_io::geo_to_wkt; +use geozero::ToGeo; use geozero::wkb::Ewkb; use itertools::Itertools; use jiff::tz::TimeZone; @@ -193,8 +197,10 @@ impl Debug for ScalarRef<'_> { write!(f, "{geom:?}") } ScalarRef::Geography(v) => { - let geog = ewkb_to_geo(&mut Ewkb(v.0)) - .and_then(|(geo, srid)| geo_to_ewkt(geo, srid)) + let geog = Ewkb(v.0) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(|geo| geo_to_ewkt(geo, Some(GEOGRAPHY_SRID))) .unwrap_or_else(|e| format!("GeozeroError: {:?}", e)); write!(f, "{geog:?}") } @@ -335,8 +341,10 @@ impl Display for ScalarRef<'_> { write!(f, "{}", QuotedString(geom, '\'')) } ScalarRef::Geography(v) => { - let geog = ewkb_to_geo(&mut Ewkb(v.0)) - .and_then(|(geo, srid)| geo_to_ewkt(geo, srid)) + let geog = Ewkb(v.0) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(|geo| geo_to_ewkt(geo, Some(GEOGRAPHY_SRID))) .unwrap_or_else(|e| format!("GeozeroError: {:?}", e)); write!(f, "{}", QuotedString(geog, '\'')) } @@ -377,8 +385,10 @@ pub fn scalar_ref_to_string(value: &ScalarRef) -> String { format!("{}", geom) } ScalarRef::Geography(v) => { - let geog = ewkb_to_geo(&mut Ewkb(v.0)) - .and_then(|(geo, srid)| geo_to_ewkt(geo, srid)) + let geog = Ewkb(v.0) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(|geo| geo_to_ewkt(geo, Some(GEOGRAPHY_SRID))) .unwrap_or_else(|e| format!("GeozeroError: {:?}", e)); format!("{}", geog) } diff --git a/src/query/formats/src/field_encoder/bytes.rs b/src/query/formats/src/field_encoder/bytes.rs index 005c9113a713e..2983f756fb825 100644 --- a/src/query/formats/src/field_encoder/bytes.rs +++ b/src/query/formats/src/field_encoder/bytes.rs @@ -36,6 +36,7 @@ use databend_common_expression::types::nullable::NullableColumn; use databend_common_expression::types::opaque::OpaqueColumn; use databend_common_expression::types::string::StringColumn; use databend_common_expression::types::timestamp::timestamp_to_string; +use databend_common_io::GEOGRAPHY_SRID; use databend_common_io::GeometryDataType; use databend_common_io::constants::FALSE_BYTES_LOWER; use databend_common_io::constants::FALSE_BYTES_NUM; @@ -54,6 +55,7 @@ use databend_common_io::prelude::BinaryDisplayFormat; use databend_common_io::prelude::OutputFormatSettings; use databend_common_meta_app::principal::CsvFileFormatParams; use databend_common_meta_app::principal::TextFileFormatParams; +use geozero::ToGeo; use geozero::wkb::Ewkb; use jsonb::RawJsonb; use lexical_core::ToLexical; @@ -370,19 +372,22 @@ setting binary_output_format to 'UTF-8-LOSSY'." ) { let v = unsafe { column.index_unchecked(row_index) }; let s = ewkb_to_geo(&mut Ewkb(v)) - .and_then( - |(geo, srid)| match self.common_settings.settings.geometry_format { + .and_then(|(geo, srid)| { + let srid = srid.unwrap_or(0); + match self.common_settings.settings.geometry_format { GeometryDataType::WKB => { geo_to_wkb(geo).map(|v| hex::encode_upper(v).into_bytes()) } GeometryDataType::WKT => geo_to_wkt(geo).map(|v| v.as_bytes().to_vec()), GeometryDataType::EWKB => { - geo_to_ewkb(geo, srid).map(|v| hex::encode_upper(v).into_bytes()) + geo_to_ewkb(geo, Some(srid)).map(|v| hex::encode_upper(v).into_bytes()) + } + GeometryDataType::EWKT => { + geo_to_ewkt(geo, Some(srid)).map(|v| v.as_bytes().to_vec()) } - GeometryDataType::EWKT => geo_to_ewkt(geo, srid).map(|v| v.as_bytes().to_vec()), GeometryDataType::GEOJSON => geo_to_json(geo).map(|v| v.as_bytes().to_vec()), - }, - ) + } + }) .unwrap_or_else(|_| v.to_vec()); match self.common_settings.settings.geometry_format { @@ -403,20 +408,19 @@ setting binary_output_format to 'UTF-8-LOSSY'." in_nested: bool, ) { let v = unsafe { column.index_unchecked(row_index) }; - let s = ewkb_to_geo(&mut Ewkb(v.0)) - .and_then( - |(geo, srid)| match self.common_settings.settings.geometry_format { - GeometryDataType::WKB => { - geo_to_wkb(geo).map(|v| hex::encode_upper(v).into_bytes()) - } - GeometryDataType::WKT => geo_to_wkt(geo).map(|v| v.as_bytes().to_vec()), - GeometryDataType::EWKB => { - geo_to_ewkb(geo, srid).map(|v| hex::encode_upper(v).into_bytes()) - } - GeometryDataType::EWKT => geo_to_ewkt(geo, srid).map(|v| v.as_bytes().to_vec()), - GeometryDataType::GEOJSON => geo_to_json(geo).map(|v| v.as_bytes().to_vec()), - }, - ) + let s = Ewkb(v.0) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(|geo| match self.common_settings.settings.geometry_format { + GeometryDataType::WKB => geo_to_wkb(geo).map(|v| hex::encode_upper(v).into_bytes()), + GeometryDataType::WKT => geo_to_wkt(geo).map(|v| v.as_bytes().to_vec()), + GeometryDataType::EWKB => geo_to_ewkb(geo, Some(GEOGRAPHY_SRID)) + .map(|v| hex::encode_upper(v).into_bytes()), + GeometryDataType::EWKT => { + geo_to_ewkt(geo, Some(GEOGRAPHY_SRID)).map(|v| v.as_bytes().to_vec()) + } + GeometryDataType::GEOJSON => geo_to_json(geo).map(|v| v.as_bytes().to_vec()), + }) .unwrap_or_else(|_| v.0.to_vec()); match self.common_settings.settings.geometry_format { diff --git a/src/query/formats/src/field_encoder/string.rs b/src/query/formats/src/field_encoder/string.rs index 2b82ed8d1a5a1..705e443b97b02 100644 --- a/src/query/formats/src/field_encoder/string.rs +++ b/src/query/formats/src/field_encoder/string.rs @@ -31,6 +31,7 @@ use databend_common_expression::types::date::date_to_string; use databend_common_expression::types::interval::interval_to_string; use databend_common_expression::types::opaque::OpaqueColumn; use databend_common_expression::types::timestamp::timestamp_to_string; +use databend_common_io::GEOGRAPHY_SRID; use databend_common_io::GeometryDataType; use databend_common_io::constants::FALSE_BYTES_NUM; use databend_common_io::constants::INF_BYTES_LONG; @@ -47,6 +48,7 @@ use databend_common_io::geo_to_wkt; use databend_common_io::prelude::BinaryDisplayFormat; use databend_common_io::prelude::HttpHandlerDataFormat; use databend_common_io::prelude::OutputFormatSettings; +use geozero::ToGeo; use geozero::wkb::Ewkb; use jsonb::RawJsonb; @@ -103,7 +105,9 @@ impl FieldEncoderToString { self.variant_text(unsafe { c.index_unchecked(row_index) }), )), Column::Geometry(c) => self.encode_geometry(unsafe { c.index_unchecked(row_index) }), - Column::Geography(c) => self.encode_geometry(unsafe { c.index_unchecked(row_index).0 }), + Column::Geography(c) => { + self.encode_geography(unsafe { c.index_unchecked(row_index).0 }) + } Column::Opaque(c) => Ok(Cow::Owned(self.encode_opaque(c, row_index))), Column::Array(box c) => { let mut out = String::new(); @@ -178,7 +182,7 @@ impl FieldEncoderToString { self.write_geometry_nested_to(unsafe { c.index_unchecked(row_index) }, out)? } Column::Geography(c) => { - self.write_geometry_nested_to(unsafe { c.index_unchecked(row_index).0 }, out)? + self.write_geography_nested_to(unsafe { c.index_unchecked(row_index).0 }, out)? } // JSON @@ -367,6 +371,11 @@ setting binary_output_format to 'UTF-8-LOSSY'." Ok(text) } + fn encode_geography(&self, value: &[u8]) -> Result> { + let (text, _) = self.geography_text(value)?; + Ok(text) + } + fn write_geometry_nested_to(&self, value: &[u8], out: &mut String) -> Result<()> { let (text, is_string) = self.geometry_text(value)?; if is_string { @@ -377,17 +386,51 @@ setting binary_output_format to 'UTF-8-LOSSY'." Ok(()) } + fn write_geography_nested_to(&self, value: &[u8], out: &mut String) -> Result<()> { + let (text, is_string) = self.geography_text(value)?; + if is_string { + self.write_quoted_string(text.as_ref(), out); + } else { + out.push_str(text.as_ref()); + } + Ok(()) + } + fn geometry_text(&self, value: &[u8]) -> Result<(Cow<'static, str>, bool)> { match ewkb_to_geo(&mut Ewkb(value)) { - Ok((geo, srid)) => match self.settings.geometry_format { + Ok((geo, srid)) => { + let srid = srid.unwrap_or(0); + match self.settings.geometry_format { + GeometryDataType::WKB => { + Ok((Cow::Owned(hex::encode_upper(geo_to_wkb(geo)?)), true)) + } + GeometryDataType::WKT => Ok((Cow::Owned(geo_to_wkt(geo)?), true)), + GeometryDataType::EWKB => Ok(( + Cow::Owned(hex::encode_upper(geo_to_ewkb(geo, Some(srid))?)), + true, + )), + GeometryDataType::EWKT => Ok((Cow::Owned(geo_to_ewkt(geo, Some(srid))?), true)), + GeometryDataType::GEOJSON => Ok((Cow::Owned(geo_to_json(geo)?), false)), + } + } + Err(_) => Ok(self.lossy_geometry(value)), + } + } + + fn geography_text(&self, value: &[u8]) -> Result<(Cow<'static, str>, bool)> { + match Ewkb(value).to_geo() { + Ok(geo) => match self.settings.geometry_format { GeometryDataType::WKB => { Ok((Cow::Owned(hex::encode_upper(geo_to_wkb(geo)?)), true)) } GeometryDataType::WKT => Ok((Cow::Owned(geo_to_wkt(geo)?), true)), - GeometryDataType::EWKB => { - Ok((Cow::Owned(hex::encode_upper(geo_to_ewkb(geo, srid)?)), true)) + GeometryDataType::EWKB => Ok(( + Cow::Owned(hex::encode_upper(geo_to_ewkb(geo, Some(GEOGRAPHY_SRID))?)), + true, + )), + GeometryDataType::EWKT => { + Ok((Cow::Owned(geo_to_ewkt(geo, Some(GEOGRAPHY_SRID))?), true)) } - GeometryDataType::EWKT => Ok((Cow::Owned(geo_to_ewkt(geo, srid)?), true)), GeometryDataType::GEOJSON => Ok((Cow::Owned(geo_to_json(geo)?), false)), }, Err(_) => Ok(self.lossy_geometry(value)), diff --git a/src/query/functions/src/aggregates/aggregate_geographic_agg.rs b/src/query/functions/src/aggregates/aggregate_geographic_agg.rs new file mode 100644 index 0000000000000..d95627890a115 --- /dev/null +++ b/src/query/functions/src/aggregates/aggregate_geographic_agg.rs @@ -0,0 +1,729 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use std::alloc::Layout; +use std::fmt; +use std::marker::PhantomData; +use std::sync::Arc; + +use databend_common_exception::ErrorCode; +use databend_common_exception::Result; +use databend_common_expression::AggrStateRegistry; +use databend_common_expression::AggrStateType; +use databend_common_expression::BlockEntry; +use databend_common_expression::Column; +use databend_common_expression::ColumnBuilder; +use databend_common_expression::ColumnView; +use databend_common_expression::ProjectedBlock; +use databend_common_expression::Scalar; +use databend_common_expression::ScalarRef; +use databend_common_expression::StateSerdeItem; +use databend_common_expression::geographic::CollectAggOp; +use databend_common_expression::geographic::EnvelopeAggOp; +use databend_common_expression::geographic::GeoAggOp; +use databend_common_expression::geographic::GeometryIntersectionAggOp; +use databend_common_expression::geographic::GeometryUnionAggOp; +use databend_common_expression::types::ArgType; +use databend_common_expression::types::ArrayType; +use databend_common_expression::types::Bitmap; +use databend_common_expression::types::DataType; +use databend_common_expression::types::GeometryType; +use databend_common_expression::types::ValueType; +use databend_common_expression::types::array::ArrayColumn; +use databend_common_expression::types::binary::BinaryColumn; +use databend_common_expression::types::binary::BinaryColumnBuilder; +use databend_common_expression::types::binary::BinaryType; +use databend_common_expression::types::geography::Geography; +use databend_common_expression::types::geography::GeographyColumn; +use databend_common_io::ewkb_to_geo; +use databend_common_io::geo_to_ewkb; +use geo::Geometry; +use geozero::ToGeo; +use geozero::wkb::Ewkb; + +use super::AggrState; +use super::AggrStateLoc; +use super::AggregateFunction; +use super::AggregateFunctionDescription; +use super::AggregateFunctionFeatures; +use super::AggregateFunctionSortDesc; +use super::StateAddr; +use super::StateSerde; +use super::aggregate_scalar_state::ScalarStateFunc; +use super::assert_params; +use super::assert_unary_arguments; +use super::batch_merge1; +use super::batch_serialize1; + +fn geometry_column_to_geos_and_srid( + column: &BinaryColumn, +) -> Result<(Vec>, Option)> { + let mut srid = None; + let mut geos = Vec::with_capacity(column.len()); + for value in column.iter() { + let (geo, geo_srid) = ewkb_to_geo(&mut Ewkb(value))?; + let geo_srid = geo_srid.unwrap_or_default(); + if let Some(srid) = srid { + if srid != geo_srid { + return Err(ErrorCode::GeometryError(format!( + "Incompatible SRID: {} and {}", + srid, geo_srid, + ))); + } + } else { + srid = Some(geo_srid); + } + geos.push(geo); + } + + Ok((geos, srid)) +} + +fn geography_column_to_geos(column: &BinaryColumn) -> Result>> { + let mut geos = Vec::with_capacity(column.len()); + for value in column.iter() { + let geo = Ewkb(value).to_geo()?; + geos.push(geo); + } + + Ok(geos) +} + +fn normalize_output_srid(srid: Option) -> Option { + match srid { + Some(0) => None, + other => other, + } +} + +#[derive(Debug)] +pub struct GeometryAggState +where O: GeoAggOp +{ + value: Option>, + srid: Option, + error: Option, + _phantom: PhantomData, +} + +impl Default for GeometryAggState +where O: GeoAggOp +{ + fn default() -> Self { + Self { + value: None, + srid: None, + error: None, + _phantom: PhantomData, + } + } +} + +impl GeometryAggState +where O: GeoAggOp +{ + fn set_error(&mut self, err: ErrorCode) { + if self.error.is_none() { + self.error = Some(err.message()); + } + } + + fn add_geo(&mut self, geo: Geometry, geo_srid: Option) -> Result<()> { + let geo_srid = geo_srid.unwrap_or_default(); + if let Some(srid) = self.srid { + if srid != geo_srid { + return Err(ErrorCode::GeometryError(format!( + "Incompatible SRID: {} and {}", + srid, geo_srid, + ))); + } + } else { + self.srid = Some(geo_srid); + } + + match &self.value { + None => { + self.value = Some(geo); + } + Some(acc) => { + let merged = O::compute(vec![acc.clone(), geo])?; + self.value = merged; + } + } + + Ok(()) + } +} + +impl ScalarStateFunc for GeometryAggState +where O: GeoAggOp +{ + fn new() -> Self { + Self::default() + } + + fn add(&mut self, other: Option<&[u8]>) { + if self.error.is_some() { + return; + } + let Some(other) = other else { + return; + }; + match ewkb_to_geo(&mut Ewkb(other)) { + Ok((geo, srid)) => { + if let Err(err) = self.add_geo(geo, srid) { + self.set_error(err); + } + } + Err(err) => { + self.set_error(err); + } + } + } + + fn add_batch( + &mut self, + column: ColumnView, + validity: Option<&Bitmap>, + ) -> Result<()> { + if self.error.is_some() { + return Ok(()); + } + if let Some(validity) = validity { + for (value, valid) in column.iter().zip(validity.iter()) { + if !valid { + continue; + } + match ewkb_to_geo(&mut Ewkb(value)) { + Ok((geo, srid)) => { + if let Err(err) = self.add_geo(geo, srid) { + self.set_error(err); + break; + } + } + Err(err) => { + self.set_error(err); + break; + } + } + } + } else { + for value in column.iter() { + match ewkb_to_geo(&mut Ewkb(value)) { + Ok((geo, srid)) => { + if let Err(err) = self.add_geo(geo, srid) { + self.set_error(err); + break; + } + } + Err(err) => { + self.set_error(err); + break; + } + } + } + } + + Ok(()) + } + + fn merge(&mut self, rhs: &Self) -> Result<()> { + if let Some(err) = &self.error { + return Err(ErrorCode::GeometryError(err.clone())); + } + if let Some(err) = &rhs.error { + return Err(ErrorCode::GeometryError(err.clone())); + } + let Some(rhs_value) = &rhs.value else { + return Ok(()); + }; + match (self.srid, rhs.srid) { + (Some(lhs_srid), Some(rhs_srid)) => { + if lhs_srid != rhs_srid { + return Err(ErrorCode::GeometryError(format!( + "Incompatible SRID: {} and {}", + lhs_srid, rhs_srid + ))); + } + } + (None, Some(rhs_srid)) => { + self.srid = Some(rhs_srid); + } + (_, _) => {} + } + + match &self.value { + None => { + self.value = Some(rhs_value.clone()); + } + Some(acc) => { + let merged = O::compute(vec![acc.clone(), rhs_value.clone()])?; + self.value = merged; + } + } + + Ok(()) + } + + fn merge_result(&mut self, builder: &mut ColumnBuilder) -> Result<()> { + if let Some(err) = self.error.take() { + return Err(ErrorCode::GeometryError(err)); + } + let Some(geo) = self.value.take() else { + builder.push(ScalarRef::Null); + return Ok(()); + }; + + let data = geo_to_ewkb(geo, normalize_output_srid(self.srid))?; + let geometry_value = Scalar::Geometry(data); + builder.push(geometry_value.as_ref()); + Ok(()) + } +} + +impl StateSerde for GeometryAggState +where O: GeoAggOp +{ + fn serialize_type(_: Option<&dyn super::SerializeInfo>) -> Vec { + vec![StateSerdeItem::Binary(None)] + } + + fn batch_serialize( + places: &[StateAddr], + loc: &[AggrStateLoc], + builders: &mut [ColumnBuilder], + ) -> Result<()> { + let binary_builder = builders[0].as_binary_mut().unwrap(); + for place in places { + let state = AggrState::new(*place, loc).get::(); + if let Some(err) = &state.error { + return Err(ErrorCode::GeometryError(err.clone())); + } + if let Some(geo) = &state.value { + let data = geo_to_ewkb(geo.clone(), state.srid)?; + binary_builder.data.extend_from_slice(&data); + } + binary_builder.commit_row(); + } + Ok(()) + } + + fn batch_merge( + places: &[StateAddr], + loc: &[AggrStateLoc], + state: &BlockEntry, + filter: Option<&Bitmap>, + ) -> Result<()> { + batch_merge1::(places, loc, state, filter, |state, value| { + if value.is_empty() { + return Ok(()); + } + let (geo, geo_srid) = ewkb_to_geo(&mut Ewkb(value))?; + state.add_geo(geo, geo_srid)?; + Ok(()) + }) + } +} + +#[derive(Debug)] +pub struct GeographicAggState +where T: ArgType +{ + builder: BinaryColumnBuilder, + _phantom: PhantomData<(T, O)>, +} + +impl Default for GeographicAggState +where T: ArgType +{ + fn default() -> Self { + Self { + builder: BinaryColumnBuilder::with_capacity(0, 0), + _phantom: PhantomData, + } + } +} + +impl ScalarStateFunc for GeographicAggState +where + T: ArgType + std::marker::Send, + O: GeoAggOp, +{ + fn new() -> Self { + Self::default() + } + + fn add(&mut self, other: Option>) { + if let Some(other) = other { + let scalar = T::upcast_scalar(T::to_owned_scalar(other)); + let bytes = scalar + .as_bytes() + .expect("geometry/geography values must be bytes"); + self.builder.put_slice(bytes); + self.builder.commit_row(); + } + } + + fn add_batch(&mut self, column: ColumnView, validity: Option<&Bitmap>) -> Result<()> { + let column_len = column.len(); + if column_len == 0 { + return Ok(()); + } + if let Some(validity) = validity { + for (val, valid) in column.iter().zip(validity.iter()) { + if valid { + let scalar = T::upcast_scalar(T::to_owned_scalar(val)); + let bytes = scalar + .as_bytes() + .expect("geometry/geography values must be bytes"); + self.builder.put_slice(bytes); + self.builder.commit_row(); + } + } + } else { + for val in column.iter() { + let scalar = T::upcast_scalar(T::to_owned_scalar(val)); + let bytes = scalar + .as_bytes() + .expect("geometry/geography values must be bytes"); + self.builder.put_slice(bytes); + self.builder.commit_row(); + } + } + + Ok(()) + } + + fn merge(&mut self, rhs: &Self) -> Result<()> { + self.builder.append_column(&rhs.builder.clone().build()); + Ok(()) + } + + fn merge_result(&mut self, builder: &mut ColumnBuilder) -> Result<()> { + let mut drained = BinaryColumnBuilder::with_capacity(0, 0); + std::mem::swap(&mut self.builder, &mut drained); + let binary_column = drained.build(); + if binary_column.is_empty() { + builder.push(ScalarRef::Null); + return Ok(()); + } + + match T::data_type() { + DataType::Geometry => { + let (geos, srid) = geometry_column_to_geos_and_srid(&binary_column)?; + let geo = O::compute(geos)?; + match geo { + Some(geo) => { + let data = geo_to_ewkb(geo, normalize_output_srid(srid))?; + let geometry_value = Scalar::Geometry(data); + builder.push(geometry_value.as_ref()); + } + None => builder.push(ScalarRef::Null), + } + } + DataType::Geography => { + let geos = geography_column_to_geos(&binary_column)?; + let geo = O::compute(geos)?; + match geo { + Some(geo) => { + let data = geo_to_ewkb(geo, None)?; + let geography_value = Scalar::Geography(Geography(data)); + builder.push(geography_value.as_ref()); + } + None => builder.push(ScalarRef::Null), + } + } + _ => unreachable!(), + } + + Ok(()) + } +} + +impl StateSerde for GeographicAggState +where + T: ArgType + std::marker::Send, + O: GeoAggOp, +{ + fn serialize_type(_: Option<&dyn super::SerializeInfo>) -> Vec { + vec![DataType::Array(Box::new(T::data_type())).into()] + } + + fn batch_serialize( + places: &[StateAddr], + loc: &[AggrStateLoc], + builders: &mut [ColumnBuilder], + ) -> Result<()> { + batch_serialize1::, Self, _>(places, loc, builders, |state, builder| { + let binary_column = state.builder.clone().build(); + let offsets = vec![0, binary_column.len() as u64]; + + let column = match T::data_type() { + DataType::Geometry => Column::Geometry(binary_column), + DataType::Geography => Column::Geography(GeographyColumn(binary_column)), + _ => unreachable!(), + }; + let column = T::try_downcast_column(&column).unwrap(); + let array_column = ArrayColumn::new(column, offsets.into()); + builder.append_column(&array_column); + + Ok(()) + }) + } + + fn batch_merge( + places: &[StateAddr], + loc: &[AggrStateLoc], + state: &BlockEntry, + filter: Option<&Bitmap>, + ) -> Result<()> { + batch_merge1::, Self, _>(places, loc, state, filter, |state, data| { + let builder = T::column_to_builder(data); + let column_builder = T::try_upcast_column_builder(builder, &T::data_type()).unwrap(); + let binary_builder = match column_builder { + ColumnBuilder::Geometry(builder) | ColumnBuilder::Geography(builder) => builder, + _ => unreachable!(), + }; + + let rhs = Self { + builder: binary_builder, + _phantom: PhantomData, + }; + + state.merge(&rhs) + }) + } +} + +#[derive(Clone)] +struct AggregateGeographicAggFunction { + display_name: String, + return_type: DataType, + _t: PhantomData, +} + +impl AggregateFunction for AggregateGeographicAggFunction +where + T: ValueType, + State: ScalarStateFunc, +{ + fn name(&self) -> &str { + "AggregateGeographicAggFunction" + } + + fn return_type(&self) -> Result { + Ok(self.return_type.clone()) + } + + fn init_state(&self, place: AggrState) { + place.write(State::new); + } + + fn register_state(&self, registry: &mut AggrStateRegistry) { + registry.register(AggrStateType::Custom(Layout::new::())); + } + + fn accumulate( + &self, + place: AggrState, + block: ProjectedBlock, + validity: Option<&Bitmap>, + _input_rows: usize, + ) -> Result<()> { + let state = place.get::(); + match &block[0] { + BlockEntry::Const(_, DataType::Null, _) | BlockEntry::Column(Column::Null { .. }) => { + Ok(()) + } + entry => { + let column = entry.downcast::().unwrap(); + state.add_batch(column, validity) + } + } + } + + fn accumulate_keys( + &self, + places: &[StateAddr], + loc: &[AggrStateLoc], + block: ProjectedBlock, + _input_rows: usize, + ) -> Result<()> { + let entry = &block[0]; + match entry.data_type() { + DataType::Null => {} + _ => entry + .downcast::() + .unwrap() + .iter() + .zip(places.iter()) + .for_each(|(v, place)| { + let state = AggrState::new(*place, loc).get::(); + state.add(Some(v)) + }), + } + + Ok(()) + } + + fn accumulate_row(&self, place: AggrState, block: ProjectedBlock, row: usize) -> Result<()> { + let state = place.get::(); + let entry = &block[0]; + match entry.data_type() { + DataType::Null => {} + _ => { + let view = entry.downcast::().unwrap(); + let v = view.index(row).unwrap(); + state.add(Some(v)); + } + } + + Ok(()) + } + + fn serialize_type(&self) -> Vec { + State::serialize_type(None) + } + + fn batch_serialize( + &self, + places: &[StateAddr], + loc: &[AggrStateLoc], + builders: &mut [ColumnBuilder], + ) -> Result<()> { + State::batch_serialize(places, loc, builders) + } + + fn batch_merge( + &self, + places: &[StateAddr], + loc: &[AggrStateLoc], + state: &BlockEntry, + filter: Option<&Bitmap>, + ) -> Result<()> { + State::batch_merge(places, loc, state, filter) + } + + fn merge_states(&self, place: AggrState, rhs: AggrState) -> Result<()> { + let state = place.get::(); + let other = rhs.get::(); + state.merge(other) + } + + fn merge_result( + &self, + place: AggrState, + _read_only: bool, + builder: &mut ColumnBuilder, + ) -> Result<()> { + let state = place.get::(); + state.merge_result(builder) + } + + fn need_manual_drop_state(&self) -> bool { + true + } + + unsafe fn drop_state(&self, place: AggrState) { + let state = place.get::(); + unsafe { std::ptr::drop_in_place(state) }; + } +} + +impl fmt::Display for AggregateGeographicAggFunction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.display_name) + } +} + +impl AggregateGeographicAggFunction +where + T: ValueType, + State: ScalarStateFunc, +{ + fn try_create(display_name: &str, return_type: DataType) -> Result> { + let func = AggregateGeographicAggFunction:: { + display_name: display_name.to_string(), + return_type, + _t: PhantomData, + }; + Ok(Arc::new(func)) + } +} + +fn try_create_aggregate_geographic_agg_function( + display_name: &str, + params: Vec, + argument_types: Vec, + _sort_descs: Vec, +) -> Result> +where + GeomState: ScalarStateFunc, +{ + assert_params(display_name, params.len(), 0)?; + assert_unary_arguments(display_name, argument_types.len())?; + let arg_type = argument_types[0].remove_nullable(); + match arg_type { + DataType::Geometry | DataType::Null => { + let return_type = DataType::Nullable(Box::new(DataType::Geometry)); + AggregateGeographicAggFunction::::try_create( + display_name, + return_type, + ) + } + _ => Err(ErrorCode::BadDataValueType(format!( + "The argument of aggregate function {display_name} must be Geometry", + ))), + } +} + +pub fn aggregate_st_union_agg_function_desc() -> AggregateFunctionDescription { + type GeomState = GeometryAggState; + AggregateFunctionDescription::creator_with_features( + Box::new(try_create_aggregate_geographic_agg_function::), + AggregateFunctionFeatures { + ..Default::default() + }, + ) +} + +pub fn aggregate_st_intersection_agg_function_desc() -> AggregateFunctionDescription { + type GeomState = GeometryAggState; + AggregateFunctionDescription::creator_with_features( + Box::new(try_create_aggregate_geographic_agg_function::), + AggregateFunctionFeatures { + ..Default::default() + }, + ) +} + +pub fn aggregate_st_envelope_agg_function_desc() -> AggregateFunctionDescription { + type GeomState = GeometryAggState; + AggregateFunctionDescription::creator_with_features( + Box::new(try_create_aggregate_geographic_agg_function::), + AggregateFunctionFeatures { + ..Default::default() + }, + ) +} + +pub fn aggregate_st_collect_function_desc() -> AggregateFunctionDescription { + type GeomState = GeographicAggState; + AggregateFunctionDescription::creator_with_features( + Box::new(try_create_aggregate_geographic_agg_function::), + AggregateFunctionFeatures { + ..Default::default() + }, + ) +} diff --git a/src/query/functions/src/aggregates/aggregate_st_collect.rs b/src/query/functions/src/aggregates/aggregate_st_collect.rs deleted file mode 100644 index 7c52066ef965b..0000000000000 --- a/src/query/functions/src/aggregates/aggregate_st_collect.rs +++ /dev/null @@ -1,441 +0,0 @@ -// Copyright 2021 Datafuse Labs -// -// Licensed 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. - -use std::alloc::Layout; -use std::fmt; -use std::marker::PhantomData; -use std::mem; -use std::sync::Arc; - -use databend_common_exception::ErrorCode; -use databend_common_exception::Result; -use databend_common_expression::AggrStateRegistry; -use databend_common_expression::AggrStateType; -use databend_common_expression::BlockEntry; -use databend_common_expression::Column; -use databend_common_expression::ColumnBuilder; -use databend_common_expression::ColumnView; -use databend_common_expression::ProjectedBlock; -use databend_common_expression::Scalar; -use databend_common_expression::ScalarRef; -use databend_common_expression::StateSerdeItem; -use databend_common_expression::types::ArgType; -use databend_common_expression::types::ArrayType; -use databend_common_expression::types::Bitmap; -use databend_common_expression::types::DataType; -use databend_common_expression::types::GeometryType; -use databend_common_expression::types::NullableType; -use databend_common_expression::types::ValueType; -use databend_common_io::ewkb_to_geo; -use databend_common_io::geo_to_ewkb; -use geo::Geometry; -use geo::GeometryCollection; -use geo::LineString; -use geo::MultiLineString; -use geo::MultiPoint; -use geo::MultiPolygon; -use geo::Point; -use geo::Polygon; -use geozero::wkb::Ewkb; - -use super::AggrState; -use super::AggrStateLoc; -use super::AggregateFunction; -use super::AggregateFunctionDescription; -use super::AggregateFunctionFeatures; -use super::AggregateFunctionSortDesc; -use super::StateAddr; -use super::StateSerde; -use super::aggregate_scalar_state::ScalarStateFunc; -use super::assert_params; -use super::assert_unary_arguments; -use super::batch_merge1; -use super::batch_serialize1; - -#[derive(Debug)] -pub struct StCollectState { - values: Vec, -} - -impl Default for StCollectState { - fn default() -> Self { - Self { values: Vec::new() } - } -} - -impl ScalarStateFunc for StCollectState -where T: ArgType -{ - fn new() -> Self { - Self::default() - } - - fn add(&mut self, other: Option>) { - if let Some(other) = other { - self.values.push(T::to_owned_scalar(other)); - } - } - - fn add_batch(&mut self, column: ColumnView, validity: Option<&Bitmap>) -> Result<()> { - let column_len = column.len(); - if column_len == 0 { - return Ok(()); - } - if let Some(validity) = validity { - for (val, valid) in column.iter().zip(validity.iter()) { - if valid { - self.values.push(T::to_owned_scalar(val)); - } - } - } else { - for val in column.iter() { - self.values.push(T::to_owned_scalar(val)); - } - } - - Ok(()) - } - - fn merge(&mut self, rhs: &Self) -> Result<()> { - self.values.extend_from_slice(&rhs.values); - Ok(()) - } - - fn merge_result(&mut self, builder: &mut ColumnBuilder) -> Result<()> { - if self.values.is_empty() { - builder.push(ScalarRef::Null); - return Ok(()); - } - - let mut has_point = false; - let mut has_line_string = false; - let mut has_polygon = false; - let mut has_other = false; - - let mut srid = None; - let mut geos = Vec::with_capacity(self.values.len()); - let values = mem::take(&mut self.values); - for (i, value) in values.into_iter().enumerate() { - let val = T::upcast_scalar(value); - let v = val.as_geometry().unwrap(); - let (geo, geo_srid) = ewkb_to_geo(&mut Ewkb(v))?; - if i == 0 { - srid = geo_srid; - } else if !srid.eq(&geo_srid) { - return Err(ErrorCode::GeometryError(format!( - "Incompatible SRID: {} and {}", - srid.unwrap_or_default(), - geo_srid.unwrap_or_default() - ))); - } - match geo { - Geometry::Point(_) => { - has_point = true; - } - Geometry::LineString(_) => { - has_line_string = true; - } - Geometry::Polygon(_) => { - has_polygon = true; - } - _ => { - has_other = true; - } - } - geos.push(geo); - } - let geo = if has_point && !has_line_string && !has_polygon && !has_other { - let points: Vec = geos - .into_iter() - .map(|geo| geo.try_into().unwrap()) - .collect(); - let multi_point = MultiPoint::from_iter(points); - Geometry::MultiPoint(multi_point) - } else if !has_point && has_line_string && !has_polygon && !has_other { - let line_strings: Vec = geos - .into_iter() - .map(|geo| geo.try_into().unwrap()) - .collect(); - let multi_line_string = MultiLineString::from_iter(line_strings); - Geometry::MultiLineString(multi_line_string) - } else if !has_point && !has_line_string && has_polygon && !has_other { - let polygons: Vec = geos - .into_iter() - .map(|geo| geo.try_into().unwrap()) - .collect(); - let multi_polygon = MultiPolygon::from_iter(polygons); - Geometry::MultiPolygon(multi_polygon) - } else { - let geo_collect = GeometryCollection::from_iter(geos); - Geometry::GeometryCollection(geo_collect) - }; - - let data = geo_to_ewkb(geo, srid)?; - let geometry_value = Scalar::Geometry(data); - builder.push(geometry_value.as_ref()); - Ok(()) - } -} - -impl StateSerde for StCollectState -where T: ArgType -{ - fn serialize_type(_: Option<&dyn super::SerializeInfo>) -> Vec { - vec![DataType::Array(Box::new(T::data_type())).into()] - } - - fn batch_serialize( - places: &[StateAddr], - loc: &[AggrStateLoc], - builders: &mut [ColumnBuilder], - ) -> Result<()> { - batch_serialize1::, Self, _>(places, loc, builders, |state, builder| { - for v in &state.values { - builder.put_item(T::to_scalar_ref(v)); - } - builder.commit_row(); - Ok(()) - }) - } - - fn batch_merge( - places: &[StateAddr], - loc: &[AggrStateLoc], - state: &BlockEntry, - filter: Option<&Bitmap>, - ) -> Result<()> { - batch_merge1::, Self, _>(places, loc, state, filter, |state, values| { - state - .values - .extend(T::iter_column(&values).map(T::to_owned_scalar)); - Ok(()) - }) - } -} - -#[derive(Clone)] -struct AggregateStCollectFunction { - display_name: String, - return_type: DataType, - _t: PhantomData, -} - -impl AggregateFunction for AggregateStCollectFunction -where - T: ValueType, - State: ScalarStateFunc, -{ - fn name(&self) -> &str { - "AggregateStCollectFunction" - } - - fn return_type(&self) -> Result { - Ok(self.return_type.clone()) - } - - fn init_state(&self, place: AggrState) { - place.write(State::new); - } - - fn register_state(&self, registry: &mut AggrStateRegistry) { - registry.register(AggrStateType::Custom(Layout::new::())); - } - - fn accumulate( - &self, - place: AggrState, - block: ProjectedBlock, - _validity: Option<&Bitmap>, - _input_rows: usize, - ) -> Result<()> { - let state = place.get::(); - - match &block[0] { - BlockEntry::Const(_, DataType::Null, _) - | BlockEntry::Column(Column::Null { .. }) - | BlockEntry::Const(Scalar::Null, DataType::Nullable(_), _) => Ok(()), - - entry @ BlockEntry::Const(_, DataType::Nullable(_), _) => { - let column = entry.clone().remove_nullable().downcast().unwrap(); - state.add_batch(column, None) - } - - BlockEntry::Column(Column::Nullable(box nullable_column)) => { - let column = T::try_downcast_column(&nullable_column.column).unwrap(); - state.add_batch(ColumnView::Column(column), Some(&nullable_column.validity)) - } - entry => { - let column = entry.downcast::().unwrap(); - state.add_batch(column, None) - } - } - } - - fn accumulate_keys( - &self, - places: &[StateAddr], - loc: &[AggrStateLoc], - block: ProjectedBlock, - _input_rows: usize, - ) -> Result<()> { - let entry = &block[0]; - match entry.data_type() { - DataType::Null => {} - DataType::Nullable(_) => { - entry - .downcast::>() - .unwrap() - .iter() - .zip(places.iter()) - .for_each(|(v, place)| { - let state = AggrState::new(*place, loc).get::(); - state.add(v) - }); - } - _ => { - entry - .downcast::() - .unwrap() - .iter() - .zip(places.iter()) - .for_each(|(v, place)| { - let state = AggrState::new(*place, loc).get::(); - state.add(Some(v)) - }); - } - } - - Ok(()) - } - - fn accumulate_row(&self, place: AggrState, block: ProjectedBlock, row: usize) -> Result<()> { - let state = place.get::(); - let entry = &block[0]; - match entry.data_type() { - DataType::Null => {} - DataType::Nullable(_) => { - let view = entry.downcast::>().unwrap(); - let v = view.index(row).unwrap(); - state.add(v); - } - _ => { - let view = entry.downcast::().unwrap(); - let v = view.index(row).unwrap(); - state.add(Some(v)); - } - } - - Ok(()) - } - - fn serialize_type(&self) -> Vec { - State::serialize_type(None) - } - - fn batch_serialize( - &self, - places: &[StateAddr], - loc: &[AggrStateLoc], - builders: &mut [ColumnBuilder], - ) -> Result<()> { - State::batch_serialize(places, loc, builders) - } - - fn batch_merge( - &self, - places: &[StateAddr], - loc: &[AggrStateLoc], - state: &BlockEntry, - filter: Option<&Bitmap>, - ) -> Result<()> { - State::batch_merge(places, loc, state, filter) - } - - fn merge_states(&self, place: AggrState, rhs: AggrState) -> Result<()> { - let state = place.get::(); - let other = rhs.get::(); - state.merge(other) - } - - fn merge_result( - &self, - place: AggrState, - _read_only: bool, - builder: &mut ColumnBuilder, - ) -> Result<()> { - let state = place.get::(); - state.merge_result(builder) - } - - fn need_manual_drop_state(&self) -> bool { - true - } - - unsafe fn drop_state(&self, place: AggrState) { - let state = place.get::(); - unsafe { std::ptr::drop_in_place(state) }; - } -} - -impl fmt::Display for AggregateStCollectFunction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.display_name) - } -} - -impl AggregateStCollectFunction -where - T: ValueType, - State: ScalarStateFunc, -{ - fn try_create(display_name: &str, return_type: DataType) -> Result> { - let func = AggregateStCollectFunction:: { - display_name: display_name.to_string(), - return_type, - _t: PhantomData, - }; - Ok(Arc::new(func)) - } -} - -fn try_create_aggregate_st_collect_function( - display_name: &str, - params: Vec, - argument_types: Vec, - _sort_descs: Vec, -) -> Result> { - assert_params(display_name, params.len(), 0)?; - assert_unary_arguments(display_name, argument_types.len())?; - if argument_types[0].remove_nullable() != DataType::Geometry - && argument_types[0] != DataType::Null - { - return Err(ErrorCode::BadDataValueType(format!( - "The argument of aggregate function {display_name} must be Geometry", - ))); - } - let return_type = DataType::Nullable(Box::new(DataType::Geometry)); - - type State = StCollectState; - AggregateStCollectFunction::::try_create(display_name, return_type) -} - -pub fn aggregate_st_collect_function_desc() -> AggregateFunctionDescription { - AggregateFunctionDescription::creator_with_features( - Box::new(try_create_aggregate_st_collect_function), - AggregateFunctionFeatures { - keep_nullable: true, - ..Default::default() - }, - ) -} diff --git a/src/query/functions/src/aggregates/aggregator.rs b/src/query/functions/src/aggregates/aggregator.rs index 6b8806583bf2b..07ce38ba9ca19 100644 --- a/src/query/functions/src/aggregates/aggregator.rs +++ b/src/query/functions/src/aggregates/aggregator.rs @@ -36,6 +36,10 @@ use super::aggregate_bitmap::aggregate_bitmap_xor_function_desc; use super::aggregate_boolean::aggregate_boolean_function_desc; use super::aggregate_covariance::aggregate_covariance_population_desc; use super::aggregate_covariance::aggregate_covariance_sample_desc; +use super::aggregate_geographic_agg::aggregate_st_collect_function_desc; +use super::aggregate_geographic_agg::aggregate_st_envelope_agg_function_desc; +use super::aggregate_geographic_agg::aggregate_st_intersection_agg_function_desc; +use super::aggregate_geographic_agg::aggregate_st_union_agg_function_desc; use super::aggregate_histogram::aggregate_histogram_function_desc; use super::aggregate_json_array_agg::aggregate_json_array_agg_function_desc; use super::aggregate_json_object_agg::aggregate_json_object_agg_function_desc; @@ -55,7 +59,6 @@ use super::aggregate_quantile_tdigest_weighted::aggregate_quantile_tdigest_weigh use super::aggregate_range_bound::aggregate_range_bound_function_desc; use super::aggregate_retention::aggregate_retention_function_desc; use super::aggregate_skewness::aggregate_skewness_function_desc; -use super::aggregate_st_collect::aggregate_st_collect_function_desc; use super::aggregate_stddev::aggregate_stddev_pop_function_desc; use super::aggregate_stddev::aggregate_stddev_samp_function_desc; use super::aggregate_string_agg::aggregate_string_agg_function_desc; @@ -182,6 +185,12 @@ impl Aggregators { factory.register("mode", aggregate_mode_function_desc()); factory.register("st_collect", aggregate_st_collect_function_desc()); + factory.register("st_union_agg", aggregate_st_union_agg_function_desc()); + factory.register( + "st_intersection_agg", + aggregate_st_intersection_agg_function_desc(), + ); + factory.register("st_envelope_agg", aggregate_st_envelope_agg_function_desc()); factory.register("markov_train", aggregate_markov_train_function_desc()); } diff --git a/src/query/functions/src/aggregates/mod.rs b/src/query/functions/src/aggregates/mod.rs index 8eb2175ae0706..da9466f769064 100644 --- a/src/query/functions/src/aggregates/mod.rs +++ b/src/query/functions/src/aggregates/mod.rs @@ -70,6 +70,7 @@ mod aggregate_count; mod aggregate_covariance; mod aggregate_distinct_state; mod aggregate_function_factory; +mod aggregate_geographic_agg; mod aggregate_histogram; mod aggregate_json_array_agg; mod aggregate_json_object_agg; @@ -87,7 +88,6 @@ mod aggregate_range_bound; mod aggregate_retention; mod aggregate_scalar_state; mod aggregate_skewness; -mod aggregate_st_collect; mod aggregate_stddev; mod aggregate_string_agg; mod aggregate_sum; diff --git a/src/query/functions/src/scalars/geographic/src/geography.rs b/src/query/functions/src/scalars/geographic/src/geography.rs index 26482cad87a51..180945a6eae70 100644 --- a/src/query/functions/src/scalars/geographic/src/geography.rs +++ b/src/query/functions/src/scalars/geographic/src/geography.rs @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +use databend_common_exception::ErrorCode; use databend_common_expression::FunctionDomain; use databend_common_expression::FunctionRegistry; use databend_common_expression::hilbert_index_from_bounds; use databend_common_expression::hilbert_index_from_bounds_slice; -use databend_common_expression::types::geography::GeographyRef; +use databend_common_expression::types::geography::Geography; use databend_common_expression::types::*; use databend_common_expression::vectorize_with_builder_1_arg; use databend_common_expression::vectorize_with_builder_2_arg; @@ -36,11 +37,11 @@ use databend_common_io::geography::geography_from_ewkb; use databend_common_io::geography::geography_from_ewkt; use databend_common_io::geography::geography_from_geojson; use databend_common_io::geography::haversine_distance_between_geometries; +use databend_common_io::geography_format; use databend_common_io::geometry::count_points; use databend_common_io::geometry::geometry_bbox_center; use databend_common_io::geometry::point_to_geohash; use databend_common_io::geometry::st_extreme; -use databend_common_io::geometry_format; use databend_common_io::geometry_type_name; use databend_common_io::wkb::make_point; use geo::Coord; @@ -60,6 +61,13 @@ use jsonb::RawJsonb; use jsonb::parse_owned_jsonb_with_buf; use num_traits::AsPrimitive; +use crate::register::geo_convert_fn; +use crate::register::geo_convert_with_arg_fn; +use crate::register::geo_try_convert_fn; +use crate::register::geography_binary_fn; +use crate::register::geography_unary_combine_fn; +use crate::register::geography_unary_fn; + pub fn register(registry: &mut FunctionRegistry) { // aliases registry.register_aliases("st_makepoint", &["st_point"]); @@ -80,367 +88,155 @@ pub fn register(registry: &mut FunctionRegistry) { "st_makepoint", |_, _, _| FunctionDomain::MayThrow, vectorize_with_builder_2_arg::, NumberType, GeographyType> (|lon,lat,builder,ctx|{ + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.commit_row(); return; } } - if let Err(e) = GeographyType::check_point(*lon, *lat) { - ctx.set_error(builder.len(), e.to_string()); - builder.commit_row() - } else { - builder.put_slice(&make_point(*lon, *lat)); - builder.commit_row() - } - }) - ); - - registry.register_passthrough_nullable_1_arg::( - "st_asgeojson", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; + match GeographyType::check_point(*lon, *lat) { + Ok(_) => { + builder.put_slice(&make_point(*lon, *lat)); } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo_to_json(geo) { - Ok(json) => { - if let Err(e) = - parse_owned_jsonb_with_buf(json.as_bytes(), &mut builder.data) - { - ctx.set_error(builder.len(), e.to_string()); - } - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }, Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + ctx.set_error(row, e.to_string()); } } builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_asewkb", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo_to_ewkb(geo, Some(GEOGRAPHY_SRID)) { - Ok(ewkb) => { - builder.put_slice(ewkb.as_slice()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_aswkb", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo_to_wkb(geo) { - Ok(wkb) => { - builder.put_slice(wkb.as_slice()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_asewkt", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo_to_ewkt(geo, Some(GEOGRAPHY_SRID)) { - Ok(ewkt) => { - builder.put_str(&ewkt); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), + }) ); - registry.register_passthrough_nullable_1_arg::( - "st_aswkt", + registry.register_passthrough_nullable_1_arg::( + "st_asgeojson", |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { + vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.commit_row(); return; } } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo_to_wkt(geo) { - Ok(wkt) => { - builder.put_str(&wkt); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } + let result = Ewkb(ewkb) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(geo_to_json) + .and_then(|json| { + parse_owned_jsonb_with_buf(json.as_bytes(), &mut builder.data) + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + }); + if let Err(e) = result { + ctx.set_error(row, e.to_string()); } builder.commit_row(); }), ); - registry - .register_passthrough_nullable_2_arg::, _, _>( - "st_distance", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::>( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } - - match (Ewkb(l_ewkb).to_geo(), Ewkb(r_ewkb).to_geo()) { - (Ok(l_geo), Ok(r_geo)) => { - let distance = haversine_distance_between_geometries(&l_geo, &r_geo); - match distance { - Ok(distance) => { - let distance = - (distance * 1_000_000_000_f64).round() / 1_000_000_000_f64; - builder.push(distance.into()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::, _>( - "st_area", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => { - let area = geo.geodesic_area_unsigned(); - let area = (area * 1_000_000_000_f64).round() / 1_000_000_000_f64; - builder.push(area.into()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } + geography_unary_fn::("st_asewkb", registry, |geo| { + let ewkb = geo_to_ewkb(geo, Some(GEOGRAPHY_SRID))?; + Ok(ewkb) + }); + + geography_unary_fn::("st_aswkb", registry, |geo| { + let wkb = geo_to_wkb(geo)?; + Ok(wkb) + }); + + geography_unary_fn::("st_asewkt", registry, |geo| { + let ewkt = geo_to_ewkt(geo, Some(GEOGRAPHY_SRID))?; + Ok(ewkt) + }); + + geography_unary_fn::("st_aswkt", registry, |geo| { + let wkt = geo_to_wkt(geo)?; + Ok(wkt) + }); + + geography_binary_fn::>("st_distance", registry, |l_geo, r_geo| { + let distance = haversine_distance_between_geometries(&l_geo, &r_geo)?; + let distance = (distance * 1_000_000_000_f64).round() / 1_000_000_000_f64; + Ok(distance.into()) + }); + + geography_unary_fn::>("st_area", registry, |geo| { + let area = geo.geodesic_area_unsigned(); + let area = (area * 1_000_000_000_f64).round() / 1_000_000_000_f64; + Ok(area.into()) + }); + + geography_unary_combine_fn::("st_endpoint", registry, |geo| match geo { + Geometry::LineString(line_string) => { + let mut points = line_string.points(); + if let Some(point) = points.next_back() { + let ewkb = geo_to_ewkb(Geometry::from(point), None)?; + Ok(Some(Geography(ewkb))) + } else { + Ok(None) + } + } + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_ENDPOINT", + geometry_type_name(&geo) + ))), + }); + + geography_unary_combine_fn::("st_startpoint", registry, |geo| match geo { + Geometry::LineString(line_string) => { + let mut points = line_string.points(); + if let Some(point) = points.next() { + let ewkb = geo_to_ewkb(Geometry::from(point), None)?; + Ok(Some(Geography(ewkb))) + } else { + Ok(None) } - }), - ); - - registry.register_combine_nullable_1_arg::( - "st_endpoint", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo { - Geometry::LineString(line_string) => { - let mut points = line_string.points(); - if let Some(point) = points.next_back() { - match geo_to_ewkb(Geometry::from(point), None) { - Ok(binary) => builder.push(GeographyRef(binary.as_slice())), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - } else { - builder.push_null(); - } - } - _ => { - ctx.set_error( - builder.len(), - format!( - "Type {} is not supported as argument to ST_ENDPOINT", - geometry_type_name(&geo) - ), - ); - builder.push_null(); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::( - "st_startpoint", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo { - Geometry::LineString(line_string) => { - let mut points = line_string.points(); - if let Some(point) = points.next() { - match geo_to_ewkb(Geometry::from(point), None) { - Ok(binary) => builder.push(GeographyRef(binary.as_slice())), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - } else { - builder.push_null(); - } - } - _ => { - ctx.set_error( - builder.len(), - format!( - "Type {} is not supported as argument to ST_STARTPOINT", - geometry_type_name(&geo) - ), - ); - builder.push_null(); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); + } + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_STARTPOINT", + geometry_type_name(&geo) + ))), + }); registry.register_combine_nullable_2_arg::( "st_pointn", |_, _, _| FunctionDomain::MayThrow, vectorize_with_builder_2_arg::>( |ewkb, index, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.push_null(); return; } } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo { + let result = Ewkb(ewkb) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(|geo| match geo { Geometry::LineString(line_string) => { let mut points = line_string.points(); let len = points.len() as i32; let idx = if index < 0 { len + index } else { index - 1 }; if let Some(point) = points.nth(idx as usize) { - match geo_to_ewkb(Geometry::from(point), None) { - Ok(binary) => builder.push(GeographyRef(binary.as_slice())), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } + geo_to_ewkb(Geometry::from(point), None) + .map(|binary| Some(Geography(binary))) } else { - builder.push_null(); + Ok(None) } } - _ => { - ctx.set_error( - builder.len(), - format!( - "Type {} is not supported as argument to ST_POINTN", - geometry_type_name(&geo) - ), - ); - builder.push_null(); - } - }, + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_POINTN", + geometry_type_name(&geo) + ))), + }); + + match result { + Ok(Some(binary)) => builder.push(binary.as_ref()), + Ok(None) => builder.push_null(), Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + ctx.set_error(row, e.to_string()); builder.push_null(); } } @@ -448,467 +244,177 @@ pub fn register(registry: &mut FunctionRegistry) { ), ); - registry.register_combine_nullable_1_arg::( - "st_dimension", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>( - |ewkb, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => { - let dimension: Option = match geo.dimensions() { - Dimensions::Empty => None, - Dimensions::ZeroDimensional => Some(0), - Dimensions::OneDimensional => Some(1), - Dimensions::TwoDimensional => Some(2), - }; - match dimension { - Some(dimension) => output.push(dimension), - None => output.push_null(), - } - } - Err(e) => { - ctx.set_error(output.len(), e.to_string()); - output.push_null(); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geogfromgeohash", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|geohash, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - if geohash.len() > 12 { - ctx.set_error( - builder.len(), - "Currently the precision only implement within 12 digits!".to_string(), - ); - builder.commit_row(); - return; + geography_unary_combine_fn::>("st_dimension", registry, |geo| { + let dimension: Option = match geo.dimensions() { + Dimensions::Empty => None, + Dimensions::ZeroDimensional => Some(0), + Dimensions::OneDimensional => Some(1), + Dimensions::TwoDimensional => Some(2), + }; + Ok(dimension) + }); + + geo_convert_fn::("st_geogfromgeohash", registry, |geohash| { + if geohash.len() > 12 { + return Err(ErrorCode::GeometryError( + "Currently the precision only implement within 12 digits!", + )); + } + let rect = decode_bbox(geohash).map_err(|e| ErrorCode::GeometryError(e.to_string()))?; + let geo: Geometry = rect.into(); + let ewkb = geo_to_ewkb(geo, None)?; + Ok(Geography(ewkb)) + }); + + geo_convert_fn::("st_geogpointfromgeohash", registry, |geohash| { + if geohash.len() > 12 { + return Err(ErrorCode::GeometryError( + "Currently the precision only implement within 12 digits!", + )); + } + let rect = decode_bbox(geohash).map_err(|e| ErrorCode::GeometryError(e.to_string()))?; + let geo: Geometry = Point::from(rect.center()).into(); + let ewkb = geo_to_ewkb(geo, None)?; + Ok(Geography(ewkb)) + }); + + geography_unary_fn::("st_makepolygon", registry, |geo| match geo { + Geometry::LineString(line_string) => { + let points = line_string.into_points(); + if points.len() < 4 { + Err(ErrorCode::GeometryError(format!( + "Input lines must have at least 4 points, but got {}", + points.len() + ))) + } else if points.last() != points.first() { + Err(ErrorCode::GeometryError( + "The first point and last point are not equal".to_string(), + )) + } else { + let polygon = Polygon::new(LineString::from(points), vec![]); + let geo: Geometry = polygon.into(); + let ewkb = geo_to_ewkb(geo, None)?; + Ok(Geography(ewkb)) } + } + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_MAKEPOLYGON", + geometry_type_name(&geo) + ))), + }); - match decode_bbox(geohash) { - Ok(rect) => { - let geo: Geometry = rect.into(); - match geo_to_ewkb(geo, None) { - Ok(binary) => builder.put_slice(binary.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } + geography_binary_fn::("st_makeline", registry, |l_geo, r_geo| { + let geos = [l_geo, r_geo]; + let mut coords: Vec = vec![]; + for geo in geos.iter() { + match geo { + Geometry::Point(point) => { + coords.push(point.0); } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + Geometry::LineString(line) => { + coords.extend(line.clone().into_inner()); } - }; - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geogpointfromgeohash", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|geohash, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - if geohash.len() > 12 { - ctx.set_error( - builder.len(), - "Currently the precision only implement within 12 digits!".to_string(), - ); - builder.commit_row(); - return; - } - - match decode_bbox(geohash) { - Ok(rect) => { - let geo: Geometry = Point::from(rect.center()).into(); - match geo_to_ewkb(geo, None) { - Ok(binary) => builder.put_slice(binary.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), + Geometry::MultiPoint(multi_point) => { + for point in multi_point.into_iter() { + coords.push(point.0); } } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }; - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_makepolygon", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo { - Geometry::LineString(line_string) => { - let points = line_string.into_points(); - if points.len() < 4 { - ctx.set_error( - builder.len(), - format!( - "Input lines must have at least 4 points, but got {}", - points.len() - ), - ); - } else if points.last() != points.first() { - ctx.set_error( - builder.len(), - "The first point and last point are not equal".to_string(), - ); - } else { - let polygon = Polygon::new(LineString::from(points), vec![]); - let geo: Geometry = polygon.into(); - match geo_to_ewkb(geo, None) { - Ok(binary) => builder.put_slice(binary.as_slice()), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - } - } - _ => { - ctx.set_error( - builder.len(), - format!( - "Type {} is not supported as argument to ST_MAKEPOLYGON", - geometry_type_name(&geo) - ), - ); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + _ => { + return Err(ErrorCode::GeometryError(format!( + "Geometry expression must be a Point, MultiPoint, or LineString, but got {}", + geometry_type_name(geo) + ))); } } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_makeline", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match (Ewkb(l_ewkb).to_geo(), Ewkb(r_ewkb).to_geo()) { - (Ok(l_geo), Ok(r_geo)) => { - let geos = [l_geo, r_geo]; - let mut coords: Vec = vec![]; - for geo in geos.iter() { - match geo { - Geometry::Point(point) => { - coords.push(point.0); - } - Geometry::LineString(line) => { - coords.append(&mut line.clone().into_inner()); - } - Geometry::MultiPoint(multi_point) => { - for point in multi_point.into_iter() { - coords.push(point.0); - } - } - _ => { - ctx.set_error( - builder.len(), - format!( - "Geometry expression must be a Point, MultiPoint, or LineString, but got {}", - geometry_type_name(geo) - ), - ); - builder.commit_row(); - return; - } - } - } - - let geo = Geometry::from(LineString::new(coords)); - match geo_to_ewkb(geo, None) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }, - ), - ); + } - registry.register_passthrough_nullable_1_arg::( - "st_geohash", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } + let geo = Geometry::from(LineString::new(coords)); + let data = geo_to_ewkb(geo, None)?; + Ok(Geography(data)) + }); - match point_to_geohash(ewkb.as_ref(), None) { - Ok(hash) => builder.put_str(&hash), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }; - builder.commit_row(); - }), - ); + geo_convert_fn::("st_geohash", registry, |input| { + point_to_geohash(input.as_ref(), None) + }); - registry.register_passthrough_nullable_2_arg::( + geo_convert_with_arg_fn::( "st_geohash", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |geography, precision, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - if precision > 12 { - ctx.set_error( - builder.len(), - "Currently the precision only implement within 12 digits!", - ); - builder.commit_row(); - return; - } - - match point_to_geohash(geography.as_ref(), Some(precision)) { - Ok(hash) => builder.put_str(&hash), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }; - builder.commit_row(); - }, - ), + registry, + |input, precision| { + if precision > 12 { + return Err(ErrorCode::GeometryError( + "Currently the precision only implement within 12 digits!", + )); + } + point_to_geohash(input.as_ref(), Some(precision)) + }, ); - registry.register_passthrough_nullable_1_arg::( - "st_geographyfromwkb", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|s, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } + geo_convert_fn::("st_geographyfromwkb", registry, |s| { + let binary = hex::decode(s).map_err(|e| ErrorCode::GeometryError(e.to_string()))?; + let data = geography_from_ewkb(&binary)?; + Ok(Geography(data)) + }); - let binary = match hex::decode(s) { - Ok(binary) => binary, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.commit_row(); - return; - } - }; + geo_convert_fn::("st_geographyfromwkb", registry, |binary| { + let data = geography_from_ewkb(binary)?; + Ok(Geography(data)) + }); - match geography_from_ewkb(&binary) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }), - ); + geo_convert_fn::("st_geographyfromwkt", registry, |wkt| { + let data = geography_from_ewkt(wkt)?; + Ok(Geography(data)) + }); - registry.register_passthrough_nullable_1_arg::( - "st_geographyfromwkb", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|binary, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match geography_from_ewkb(binary) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geographyfromwkt", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|wkt, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } + geography_unary_fn::>("st_length", registry, |geo| { + let mut distance = 0f64; + match geo { + Geometry::Line(line) => { + distance += Geodesic.length(&line); } - - match geography_from_ewkt(wkt) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), + Geometry::LineString(lines) => { + distance += Geodesic.length(&lines); } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::, _>( - "st_length", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } + Geometry::MultiLineString(multi_lines) => { + distance += Geodesic.length(&multi_lines); } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => { - let mut distance = 0f64; - match geo { + Geometry::GeometryCollection(geom_c) => { + for geometry in geom_c.0 { + match geometry { Geometry::Line(line) => { distance += Geodesic.length(&line); } - Geometry::LineString(lines) => { - distance += Geodesic.length(&lines); + Geometry::LineString(line_string) => { + distance += Geodesic.length(&line_string); } Geometry::MultiLineString(multi_lines) => { distance += Geodesic.length(&multi_lines); } - Geometry::GeometryCollection(geom_c) => { - for geometry in geom_c.0 { - match geometry { - Geometry::Line(line) => { - distance += Geodesic.length(&line); - } - Geometry::LineString(line_string) => { - distance += Geodesic.length(&line_string); - } - Geometry::MultiLineString(multi_lines) => { - distance += Geodesic.length(&multi_lines); - } - _ => {} - } - } - } _ => {} } - let distance = (distance * 1_000_000_000_f64).round() / 1_000_000_000_f64; - builder.push(distance.into()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - }), - ); - - registry.register_passthrough_nullable_1_arg::, _>( - "st_x", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo { - Geometry::Point(point) => { - builder.push(F64::from(AsPrimitive::::as_(point.x()))); - } - _ => { - ctx.set_error( - builder.len(), - format!( - "Type {} is not supported as argument to ST_X", - geometry_type_name(&geo) - ), - ); - builder.push(F64::from(0_f64)); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); } } - }), - ); + _ => {} + } + let distance = (distance * 1_000_000_000_f64).round() / 1_000_000_000_f64; + Ok(distance.into()) + }); - registry.register_passthrough_nullable_1_arg::, _>( - "st_y", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } + geography_unary_fn::>("st_x", registry, |geo| match geo { + Geometry::Point(point) => Ok(AsPrimitive::::as_(point.x()).into()), + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_X", + geometry_type_name(&geo) + ))), + }); - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geo { - Geometry::Point(point) => { - builder.push(F64::from(AsPrimitive::::as_(point.y()))); - } - _ => { - ctx.set_error( - builder.len(), - format!( - "Type {} is not supported as argument to ST_Y", - geometry_type_name(&geo) - ), - ); - builder.push(F64::from(0_f64)); - } - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - }), - ); + geography_unary_fn::>("st_y", registry, |geo| match geo { + Geometry::Point(point) => Ok(AsPrimitive::::as_(point.y()).into()), + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_Y", + geometry_type_name(&geo) + ))), + }); registry.register_1_arg::( "st_srid", @@ -924,312 +430,103 @@ pub fn register(registry: &mut FunctionRegistry) { }, ); - registry.register_combine_nullable_1_arg::, _, _>( - "st_xmax", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match st_extreme(&geo, Axis::X, Extremum::Max) { - Some(x_max) => builder.push(F64::from(AsPrimitive::::as_(x_max))), - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); + geography_unary_combine_fn::>("st_xmax", registry, |geo| { + Ok(st_extreme(&geo, Axis::X, Extremum::Max) + .map(|x_max| F64::from(AsPrimitive::::as_(x_max)))) + }); - registry.register_combine_nullable_1_arg::, _, _>( - "st_xmin", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match st_extreme(&geo, Axis::X, Extremum::Min) { - Some(x_min) => builder.push(F64::from(AsPrimitive::::as_(x_min))), - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); + geography_unary_combine_fn::>("st_xmin", registry, |geo| { + Ok(st_extreme(&geo, Axis::X, Extremum::Min) + .map(|x_min| F64::from(AsPrimitive::::as_(x_min)))) + }); - registry.register_combine_nullable_1_arg::, _, _>( - "st_ymax", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match st_extreme(&geo, Axis::Y, Extremum::Max) { - Some(y_max) => builder.push(F64::from(AsPrimitive::::as_(y_max))), - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); + geography_unary_combine_fn::>("st_ymax", registry, |geo| { + Ok(st_extreme(&geo, Axis::Y, Extremum::Max) + .map(|y_max| F64::from(AsPrimitive::::as_(y_max)))) + }); - registry.register_combine_nullable_1_arg::, _, _>( - "st_ymin", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match st_extreme(&geo, Axis::Y, Extremum::Min) { - Some(y_min) => builder.push(F64::from(AsPrimitive::::as_(y_min))), - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); + geography_unary_combine_fn::>("st_ymin", registry, |geo| { + Ok(st_extreme(&geo, Axis::Y, Extremum::Min) + .map(|y_min| F64::from(AsPrimitive::::as_(y_min)))) + }); - registry.register_passthrough_nullable_1_arg::( - "st_npoints", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(0); - return; - } - } - match Ewkb(ewkb).to_geo() { - Ok(geo) => { - let npoints = count_points(&geo); - builder.push(npoints as u32); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(0); - } - } - }), - ); + geography_unary_fn::("st_npoints", registry, |geo| { + let npoints = count_points(&geo); + Ok(npoints as u32) + }); registry.register_passthrough_nullable_1_arg::( "to_string", |_, _| FunctionDomain::MayThrow, vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.commit_row(); return; } } - match geometry_format(Ewkb(ewkb), ctx.func_ctx.geometry_output_format) { + match geography_format(ewkb.0, ctx.func_ctx.geometry_output_format) { Ok(data) => builder.put_str(&data), Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + ctx.set_error(row, e.to_string()); } } builder.commit_row(); }), ); - registry.register_passthrough_nullable_1_arg::( - "to_geography", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|val, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - let raw_jsonb = RawJsonb::new(val); - let json_str = raw_jsonb.to_string(); - match geography_from_geojson(&json_str) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_geography", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|s, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - match geography_from_ewkt(s) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_geography", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|binary, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match geography_from_ewkb(binary) { - Ok(ewkb) => builder.put_slice(ewkb.as_slice()), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_combine_nullable_1_arg::( - "try_to_geography", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::>( - |val, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; - } - } - let raw_jsonb = RawJsonb::new(val); - let json_str = raw_jsonb.to_string(); - match geography_from_geojson(&json_str) { - Ok(data) => { - output.push(GeographyRef(data.as_slice())); - } - Err(_) => output.push_null(), - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::( - "try_to_geography", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::>( - |s, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; - } - } - match geography_from_ewkt(s) { - Ok(data) => { - output.push(GeographyRef(data.as_slice())); - } - Err(_) => output.push_null(), - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::( - "try_to_geography", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::>( - |binary, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; - } - } - match geography_from_ewkb(binary) { - Ok(data) => { - output.push(GeographyRef(data.as_slice())); - } - Err(_) => output.push_null(), - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::( - "st_hilbert", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match Ewkb(ewkb.as_ref()).to_geo() { - Ok(geo) => match geometry_bbox_center(&geo) { - Some((x, y)) => match hilbert_index_from_bounds( - x, - y, - LONGITUDE_MIN, - LATITUDE_MIN, - LONGITUDE_MAX, - LATITUDE_MAX, - ) { - Ok(index) => builder.push(index), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - }, - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); + geo_convert_fn::("to_geography", registry, |val| { + let raw_jsonb = RawJsonb::new(val); + let json_str = raw_jsonb.to_string(); + let data = geography_from_geojson(&json_str)?; + Ok(Geography(data)) + }); + + geo_convert_fn::("to_geography", registry, |s| { + let data = geography_from_ewkt(s)?; + Ok(Geography(data)) + }); + + geo_convert_fn::("to_geography", registry, |binary| { + let data = geography_from_ewkb(binary)?; + Ok(Geography(data)) + }); + + geo_try_convert_fn::("try_to_geography", registry, |val| { + let raw_jsonb = RawJsonb::new(val); + let json_str = raw_jsonb.to_string(); + let data = geography_from_geojson(&json_str)?; + Ok(Geography(data)) + }); + + geo_try_convert_fn::("try_to_geography", registry, |s| { + let data = geography_from_ewkt(s)?; + Ok(Geography(data)) + }); + + geo_try_convert_fn::("try_to_geography", registry, |binary| { + let data = geography_from_ewkb(binary)?; + Ok(Geography(data)) + }); + + geography_unary_combine_fn::("st_hilbert", registry, |geo| { + match geometry_bbox_center(&geo) { + Some((x, y)) => { + let index = hilbert_index_from_bounds( + x, + y, + LONGITUDE_MIN, + LATITUDE_MIN, + LONGITUDE_MAX, + LATITUDE_MAX, + )?; + Ok(Some(index)) + } + None => Ok(None), + } + }); registry.register_combine_nullable_2_arg::< GeographyType, @@ -1245,26 +542,26 @@ pub fn register(registry: &mut FunctionRegistry) { ArrayType>, NullableType, >(|ewkb, bounds, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.push_null(); return; } } - match Ewkb(ewkb.as_ref()).to_geo() { - Ok(geo) => match geometry_bbox_center(&geo) { - Some((x, y)) => match hilbert_index_from_bounds_slice(x, y, &bounds) { - Ok(index) => builder.push(index), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - }, - None => builder.push_null(), + let result = Ewkb(ewkb.as_ref()).to_geo().map(|geo| geometry_bbox_center(&geo)); + match result { + Ok(Some((x, y))) => match hilbert_index_from_bounds_slice(x, y, &bounds) { + Ok(index) => builder.push(index), + Err(e) => { + ctx.set_error(row, e.to_string()); + builder.push_null(); + } }, + Ok(None) => builder.push_null(), Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + ctx.set_error(row, e.to_string()); builder.push_null(); } } diff --git a/src/query/functions/src/scalars/geographic/src/geometry.rs b/src/query/functions/src/scalars/geographic/src/geometry.rs index d9d43de49a62a..d57a72ed9a035 100644 --- a/src/query/functions/src/scalars/geographic/src/geometry.rs +++ b/src/query/functions/src/scalars/geographic/src/geometry.rs @@ -13,9 +13,13 @@ // limitations under the License. use databend_common_exception::ErrorCode; -use databend_common_expression::EvalContext; use databend_common_expression::FunctionDomain; use databend_common_expression::FunctionRegistry; +use databend_common_expression::geographic::GeoAggOp; +use databend_common_expression::geographic::GeometryDifferenceAggOp; +use databend_common_expression::geographic::GeometryIntersectionAggOp; +use databend_common_expression::geographic::GeometrySymDifferenceAggOp; +use databend_common_expression::geographic::GeometryUnionAggOp; use databend_common_expression::hilbert_index_from_bounds_slice; use databend_common_expression::hilbert_index_from_point; use databend_common_expression::types::ArrayType; @@ -26,7 +30,6 @@ use databend_common_expression::types::Int32Type; use databend_common_expression::types::NullableType; use databend_common_expression::types::NumberType; use databend_common_expression::types::StringType; -use databend_common_expression::types::UInt32Type; use databend_common_expression::types::UInt64Type; use databend_common_expression::types::VariantType; use databend_common_expression::types::geometry::GeometryType; @@ -47,12 +50,15 @@ use databend_common_io::geometry::count_points; use databend_common_io::geometry::geometry_bbox_center; use databend_common_io::geometry::geometry_from_str; use databend_common_io::geometry::point_to_geohash; +use databend_common_io::geometry::rect_to_polygon; use databend_common_io::geometry::st_extreme; use databend_common_io::geometry_format; use databend_common_io::geometry_from_ewkt; use databend_common_io::geometry_type_name; use databend_common_io::read_srid; use geo::Area; +use geo::BoundingRect; +use geo::Centroid; use geo::Contains; use geo::ConvexHull; use geo::Coord; @@ -89,6 +95,15 @@ use num_traits::AsPrimitive; use proj4rs::Proj; use proj4rs::transform::transform; +use crate::register::geo_convert_fn; +use crate::register::geo_convert_with_arg_fn; +use crate::register::geo_try_convert_fn; +use crate::register::geo_try_convert_with_arg_fn; +use crate::register::geometry_binary_combine_fn; +use crate::register::geometry_binary_fn; +use crate::register::geometry_unary_combine_fn; +use crate::register::geometry_unary_fn; + pub fn register(registry: &mut FunctionRegistry) { // aliases registry.register_aliases("st_aswkb", &["st_asbinary"]); @@ -115,8 +130,9 @@ pub fn register(registry: &mut FunctionRegistry) { "haversine", |_, _, _, _, _| FunctionDomain::Full, vectorize_with_builder_4_arg::, NumberType, NumberType, NumberType, NumberType,>(|lat1, lon1, lat2, lon2, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.push(0_f64.into()); return; } @@ -129,1486 +145,572 @@ pub fn register(registry: &mut FunctionRegistry) { }), ); - registry.register_passthrough_nullable_1_arg::( - "st_asgeojson", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, _)| geo_to_json(geo)) { - Ok(json) => { - if let Err(e) = parse_owned_jsonb_with_buf(json.as_bytes(), &mut builder.data) { - ctx.set_error(builder.len(), e.to_string()); - } - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_asewkb", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| geo_to_ewkb(geo, srid)) { - Ok(ewkb) => { - builder.put_slice(ewkb.as_slice()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_aswkb", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, _)| geo_to_wkb(geo)) { - Ok(wkb) => { - builder.put_slice(wkb.as_slice()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_asewkt", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| geo_to_ewkt(geo, srid)) { - Ok(ewkt) => { - builder.put_str(&ewkt); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_aswkt", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, _)| geo_to_wkt(geo)) { - Ok(wkt) => { - builder.put_str(&wkt); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_contains", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(false); - return; - } - } - - match ( - ewkb_to_geo(&mut Ewkb(l_ewkb)), - ewkb_to_geo(&mut Ewkb(r_ewkb)), - ) { - (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { - if !check_incompatible_srid(l_srid, r_srid, builder.len(), ctx) { - builder.push(false); - return; - } - let is_contains = l_geo.contains(&r_geo); - builder.push(is_contains); - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(false); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_intersects", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(false); - return; - } - } - - match ( - ewkb_to_geo(&mut Ewkb(l_ewkb)), - ewkb_to_geo(&mut Ewkb(r_ewkb)), - ) { - (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { - if !check_incompatible_srid(l_srid, r_srid, builder.len(), ctx) { - builder.push(false); - return; - } - let is_intersects = l_geo.intersects(&r_geo); - builder.push(is_intersects); - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(false); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_disjoint", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(false); - return; - } - } - - match ( - ewkb_to_geo(&mut Ewkb(l_ewkb)), - ewkb_to_geo(&mut Ewkb(r_ewkb)), - ) { - (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { - if !check_incompatible_srid(l_srid, r_srid, builder.len(), ctx) { - builder.push(false); - return; - } - let is_disjoint = !l_geo.intersects(&r_geo); - builder.push(is_disjoint); - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(false); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_within", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(false); - return; - } - } - - match ( - ewkb_to_geo(&mut Ewkb(l_ewkb)), - ewkb_to_geo(&mut Ewkb(r_ewkb)), - ) { - (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { - if !check_incompatible_srid(l_srid, r_srid, builder.len(), ctx) { - builder.push(false); - return; - } - let is_within = l_geo.is_within(&r_geo); - builder.push(is_within); - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(false); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_equals", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(false); - return; - } - } - - match ( - ewkb_to_geo(&mut Ewkb(l_ewkb)), - ewkb_to_geo(&mut Ewkb(r_ewkb)), - ) { - (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { - if !check_incompatible_srid(l_srid, r_srid, builder.len(), ctx) { - builder.push(false); - return; - } - let is_equal = l_geo.is_within(&r_geo) && r_geo.is_within(&l_geo); - builder.push(is_equal); - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(false); - } - } - }, - ), - ); - - registry - .register_passthrough_nullable_2_arg::, _, _>( - "st_distance", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::>( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } - - match ( - ewkb_to_geo(&mut Ewkb(l_ewkb)), - ewkb_to_geo(&mut Ewkb(r_ewkb)), - ) { - (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { - if !check_incompatible_srid(l_srid, r_srid, builder.len(), ctx) { - builder.push(F64::from(0_f64)); - return; - } - let distance = Euclidean.distance(&l_geo, &r_geo); - let distance = - (distance * 1_000_000_000_f64).round() / 1_000_000_000_f64; - builder.push(distance.into()); - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::, _>( - "st_area", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)) { - Ok((geo, _)) => { - let area = geo.unsigned_area(); - let area = (area * 1_000_000_000_f64).round() / 1_000_000_000_f64; - builder.push(area.into()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_convexhull", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| { - let polygon = geo.convex_hull(); - geo_to_ewkb(Geometry::from(polygon), srid) - }) { - Ok(ewkb) => { - builder.put_slice(ewkb.as_slice()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_combine_nullable_1_arg::( - "st_endpoint", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| match geo { - Geometry::LineString(line_string) => { - let mut points = line_string.points(); - if let Some(point) = points.next_back() { - geo_to_ewkb(Geometry::from(point), srid).map(Some) - } else { - Ok(None) - } - } - _ => Err(ErrorCode::GeometryError(format!( - "Type {} is not supported as argument to ST_ENDPOINT", - geometry_type_name(&geo) - ))), - }) { - Ok(Some(binary)) => { - builder.push(binary.as_slice()); - } - Ok(None) => { - builder.push_null(); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::( - "st_startpoint", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| match geo { - Geometry::LineString(line_string) => { - let mut points = line_string.points(); - if let Some(point) = points.next() { - geo_to_ewkb(Geometry::from(point), srid).map(Some) - } else { - Ok(None) - } - } - _ => Err(ErrorCode::GeometryError(format!( - "Type {} is not supported as argument to ST_STARTPOINT", - geometry_type_name(&geo) - ))), - }) { - Ok(Some(binary)) => { - builder.push(binary.as_slice()); - } - Ok(None) => { - builder.push_null(); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); - - registry.register_combine_nullable_2_arg::( - "st_pointn", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::>( - |ewkb, index, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| match geo { - Geometry::LineString(line_string) => { - let mut points = line_string.points(); - let len = points.len() as i32; - let idx = if index < 0 { len + index } else { index - 1 }; - if let Some(point) = points.nth(idx as usize) { - geo_to_ewkb(Geometry::from(point), srid).map(Some) - } else { - Ok(None) - } - } - _ => Err(ErrorCode::GeometryError(format!( - "Type {} is not supported as argument to ST_POINTN", - geometry_type_name(&geo) - ))), - }) { - Ok(Some(binary)) => { - builder.push(binary.as_slice()); - } - Ok(None) => { - builder.push_null(); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::( - "st_dimension", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>( - |ewkb, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => { - let dimension: Option = match geo.dimensions() { - Dimensions::Empty => None, - Dimensions::ZeroDimensional => Some(0), - Dimensions::OneDimensional => Some(1), - Dimensions::TwoDimensional => Some(2), - }; - match dimension { - Some(dimension) => output.push(dimension), - None => output.push_null(), - } - } - Err(e) => { - ctx.set_error(output.len(), e.to_string()); - output.push_null(); - } - } - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geomfromgeohash", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|geohash, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - if geohash.len() > 12 { - ctx.set_error( - builder.len(), - "Currently the precision only implement within 12 digits!".to_string(), - ); - builder.commit_row(); - return; - } - - match decode_bbox(geohash) { - Ok(rect) => { - let geo: Geometry = rect.into(); - match geo_to_ewkb(geo, None) { - Ok(binary) => builder.put_slice(binary.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }; - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geompointfromgeohash", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|geohash, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - if geohash.len() > 12 { - ctx.set_error( - builder.len(), - "Currently the precision only implement within 12 digits!".to_string(), - ); - builder.commit_row(); - return; - } - - match decode_bbox(geohash) { - Ok(rect) => { - let geo: Geometry = Point::from(rect.center()).into(); - match geo_to_ewkb(geo, None) { - Ok(binary) => builder.put_slice(binary.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }; - builder.commit_row(); - }), - ); + geo_convert_fn::("st_geomfromgeohash", registry, |input| { + if input.len() > 12 { + return Err(ErrorCode::GeometryError( + "Currently the precision only implement within 12 digits!", + )); + } + let rect = decode_bbox(input).map_err(|_| ErrorCode::GeometryError("invalid geohash"))?; + let geo = Geometry::from(rect); + geo_to_ewkb(geo, None) + }); + + geo_convert_fn::("st_geompointfromgeohash", registry, |input| { + if input.len() > 12 { + return Err(ErrorCode::GeometryError( + "Currently the precision only implement within 12 digits!", + )); + } + let rect = decode_bbox(input).map_err(|_| ErrorCode::GeometryError("invalid geohash"))?; + let geo = Geometry::from(Point::from(rect.center())); + geo_to_ewkb(geo, None) + }); registry .register_passthrough_nullable_2_arg::, NumberType, GeometryType, _, _>( - "st_makegeompoint", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::, NumberType, GeometryType>( - |longitude, latitude, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - let geo = Geometry::from(Point::new(longitude.0, latitude.0)); - match geo_to_ewkb(geo, None) { - Ok(binary) => builder.put_slice(binary.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_makepolygon", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| match geo { - Geometry::LineString(line_string) => { - let points = line_string.into_points(); - if points.len() < 4 { - Err(ErrorCode::GeometryError(format!( - "Input lines must have at least 4 points, but got {}", - points.len() - ))) - } else if points.last() != points.first() { - Err(ErrorCode::GeometryError( - "The first point and last point are not equal".to_string(), - )) - } else { - let polygon = Polygon::new(LineString::from(points), vec![]); - let geo: Geometry = polygon.into(); - geo_to_ewkb(geo, srid) - } - } - _ => Err(ErrorCode::GeometryError(format!( - "Type {} is not supported as argument to ST_MAKEPOLYGON", - geometry_type_name(&geo) - ))), - }) { - Ok(binary) => { - builder.put_slice(binary.as_slice()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_makeline", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |l_ewkb, r_ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match (ewkb_to_geo(&mut Ewkb(l_ewkb)), ewkb_to_geo(&mut Ewkb(r_ewkb))) { - (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { - if !check_incompatible_srid(l_srid, r_srid, builder.len(), ctx) { - builder.commit_row(); - return; - } - let geos = [l_geo, r_geo]; - let mut coords: Vec = vec![]; - for geo in geos.iter() { - match geo { - Geometry::Point(point) => { - coords.push(point.0); - }, - Geometry::LineString(line)=> { - coords.append(&mut line.clone().into_inner()); - }, - Geometry::MultiPoint(multi_point)=> { - for point in multi_point.into_iter() { - coords.push(point.0); - } - }, - _ => { - ctx.set_error( - builder.len(), - format!("Geometry expression must be a Point, MultiPoint, or LineString, but got {}", - geometry_type_name(geo) - ) - ); - builder.commit_row(); - return; - } - } - } - - let geo = Geometry::from(LineString::new(coords)); - match geo_to_ewkb(geo, l_srid) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - } - (Err(e), _) | (_, Err(e)) => { - ctx.set_error( - builder.len(), - e.to_string(), - ); - } - } - builder.commit_row(); - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geohash", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match point_to_geohash(ewkb, None) { - Ok(hash) => builder.put_str(&hash), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }; - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_geohash", + "st_makegeompoint", |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |geometry, precision, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - if precision > 12 { - ctx.set_error( - builder.len(), - "Currently the precision only implement within 12 digits!", - ); - builder.commit_row(); - return; - } - - match point_to_geohash(geometry, Some(precision)) { - Ok(hash) => builder.put_str(&hash), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - }; - builder.commit_row(); - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geometryfromwkb", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|s, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - let binary = match hex::decode(s) { - Ok(binary) => binary, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.commit_row(); - return; - } - }; - - match ewkb_to_geo(&mut Ewkb(binary)).and_then(|(geo, srid)| geo_to_ewkb(geo, srid)) { - Ok(ewkb) => builder.put_slice(ewkb.as_slice()), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geometryfromwkb", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|binary, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(binary)).and_then(|(geo, srid)| geo_to_ewkb(geo, srid)) { - Ok(ewkb) => builder.put_slice(ewkb.as_slice()), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_geometryfromwkb", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |s, srid, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - let binary = match hex::decode(s) { - Ok(binary) => binary, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.commit_row(); - return; - } - }; - - match ewkb_to_geo(&mut Ewkb(binary)) - .and_then(|(geo, _)| geo_to_ewkb(geo, Some(srid))) - { - Ok(ewkb) => builder.put_slice(ewkb.as_slice()), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }, - ), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_geometryfromwkb", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |binary, srid, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(binary)) - .and_then(|(geo, _)| geo_to_ewkb(geo, Some(srid))) - { - Ok(ewkb) => builder.put_slice(ewkb.as_slice()), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_geometryfromwkt", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|wkt, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match geometry_from_ewkt(wkt, None) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_geometryfromwkt", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |wkt, srid, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - match geometry_from_ewkt(wkt, Some(srid)) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::, _>( - "st_length", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => { - let mut distance = 0f64; - match geo { - Geometry::LineString(lines) => { - for line in lines.lines() { - distance += Euclidean.length(&line); - } - } - Geometry::MultiLineString(multi_lines) => { - for line_string in multi_lines.0 { - for line in line_string.lines() { - distance += Euclidean.length(&line); - } - } - } - Geometry::GeometryCollection(geom_c) => { - for geometry in geom_c.0 { - if let Geometry::LineString(line_string) = geometry { - for line in line_string.lines() { - distance += Euclidean.length(&line); - } - } - } - } - _ => {} - } - let distance = (distance * 1_000_000_000_f64).round() / 1_000_000_000_f64; - builder.push(distance.into()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - }), - ); - - registry.register_passthrough_nullable_1_arg::, _>( - "st_x", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, _)| match geo { - Geometry::Point(point) => Ok(F64::from(AsPrimitive::::as_(point.x()))), - _ => Err(ErrorCode::GeometryError(format!( - "Type {} is not supported as argument to ST_X", - geometry_type_name(&geo) - ))), - }) { - Ok(x) => { - builder.push(x); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - }), - ); - - registry.register_passthrough_nullable_1_arg::, _>( - "st_y", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(F64::from(0_f64)); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, _)| match geo { - Geometry::Point(point) => Ok(F64::from(AsPrimitive::::as_(point.y()))), - _ => Err(ErrorCode::GeometryError(format!( - "Type {} is not supported as argument to ST_Y", - geometry_type_name(&geo) - ))), - }) { - Ok(x) => { - builder.push(x); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(F64::from(0_f64)); - } - } - }), - ); - - registry.register_passthrough_nullable_2_arg::( - "st_setsrid", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |ewkb, srid, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - - match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, _)| geo_to_ewkb(geo, Some(srid))) - { - Ok(ewkb) => { - builder.put_slice(ewkb.as_slice()); - } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }, - ), - ); - - registry.register_passthrough_nullable_1_arg::( - "st_srid", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|ewkb, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push(0); - return; - } - } - - let srid = read_srid(&mut Ewkb(ewkb)).unwrap_or_default(); - output.push(srid); - }), - ); - - registry.register_combine_nullable_1_arg::, _, _>( - "st_xmax", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match st_extreme(&geo, Axis::X, Extremum::Max) { - Some(x_max) => builder.push(F64::from(AsPrimitive::::as_(x_max))), - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::, _, _>( - "st_xmin", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match st_extreme(&geo, Axis::X, Extremum::Min) { - Some(x_max) => builder.push(F64::from(AsPrimitive::::as_(x_max))), - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::, _, _>( - "st_ymax", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match st_extreme(&geo, Axis::Y, Extremum::Max) { - Some(x_max) => builder.push(F64::from(AsPrimitive::::as_(x_max))), - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); - - registry.register_combine_nullable_1_arg::, _, _>( - "st_ymin", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match st_extreme(&geo, Axis::Y, Extremum::Min) { - Some(x_max) => builder.push(F64::from(AsPrimitive::::as_(x_max))), - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); + vectorize_with_builder_2_arg::, NumberType, GeometryType>( + |longitude, latitude, builder, ctx| { + let row = builder.len(); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + builder.commit_row(); + return; } } + let geo = Geometry::from(Point::new(longitude.0, latitude.0)); + match geo_to_ewkb(geo, None) { + Ok(binary) => builder.put_slice(binary.as_slice()), + Err(e) => ctx.set_error(row, e.to_string()), + } + builder.commit_row(); }, ), ); - registry.register_passthrough_nullable_1_arg::( - "st_npoints", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push(0); - return; - } + geometry_unary_fn::("st_makepolygon", registry, |geo, srid| match geo { + Geometry::LineString(line_string) => { + let points = line_string.into_points(); + if points.len() < 4 { + Err(ErrorCode::GeometryError(format!( + "Input lines must have at least 4 points, but got {}", + points.len() + ))) + } else if points.last() != points.first() { + Err(ErrorCode::GeometryError( + "The first point and last point are not equal".to_string(), + )) + } else { + let polygon = Polygon::new(LineString::from(points), vec![]); + let geo: Geometry = polygon.into(); + geo_to_ewkb(geo, srid) } + } + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_MAKEPOLYGON", + geometry_type_name(&geo) + ))), + }); - match Ewkb(ewkb).to_geo() { - Ok(geo) => { - let npoints = count_points(&geo); - builder.push(npoints as u32); + geometry_binary_fn::("st_makeline", registry, |l, r, srid| { + let geos = [l, r]; + let mut coords: Vec = vec![]; + for geo in geos.iter() { + match geo { + Geometry::Point(point) => { + coords.push(point.0); } - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push(0); + Geometry::LineString(line) => { + coords.append(&mut line.clone().into_inner()); + } + Geometry::MultiPoint(multi_point) => { + for point in multi_point.into_iter() { + coords.push(point.0); + } + } + _ => { + return Err(ErrorCode::GeometryError(format!( + "Geometry expression must be a Point, MultiPoint, or LineString, but got {}", + geometry_type_name(geo) + ))); } } - }), + } + let geo = Geometry::from(LineString::new(coords)); + geo_to_ewkb(geo, srid) + }); + + geo_convert_fn::("st_geometryfromwkb", registry, |input| { + let binary = + hex::decode(input).map_err(|_| ErrorCode::GeometryError("invalid binary input"))?; + ewkb_to_geo(&mut Ewkb(binary)).and_then(|(geo, srid)| geo_to_ewkb(geo, srid)) + }); + + geo_convert_fn::("st_geometryfromwkb", registry, |input| { + ewkb_to_geo(&mut Ewkb(input)).and_then(|(geo, srid)| geo_to_ewkb(geo, srid)) + }); + + geo_convert_with_arg_fn::( + "st_geometryfromwkb", + registry, + |input, srid| { + let binary = + hex::decode(input).map_err(|_| ErrorCode::GeometryError("invalid binary input"))?; + ewkb_to_geo(&mut Ewkb(binary)).and_then(|(geo, _)| geo_to_ewkb(geo, Some(srid))) + }, ); - registry.register_passthrough_nullable_1_arg::( - "to_string", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } + geo_convert_with_arg_fn::( + "st_geometryfromwkb", + registry, + |input, srid| { + ewkb_to_geo(&mut Ewkb(input)).and_then(|(geo, _)| geo_to_ewkb(geo, Some(srid))) + }, + ); - match geometry_format(Ewkb(ewkb), ctx.func_ctx.geometry_output_format) { - Ok(data) => builder.put_str(&data), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }), + geo_convert_fn::("st_geometryfromwkt", registry, |input| { + geometry_from_ewkt(input, None) + }); + + geo_convert_with_arg_fn::( + "st_geometryfromwkt", + registry, + |input, srid| geometry_from_ewkt(input, Some(srid)), + ); + + geo_convert_fn::("to_geometry", registry, |input| { + geometry_from_str(input, None) + }); + + geo_convert_with_arg_fn::("to_geometry", registry, |input, srid| { + geometry_from_str(input, Some(srid)) + }); + + geo_convert_fn::("to_geometry", registry, |input| { + ewkb_to_geo(&mut Ewkb(input)).and_then(|(geo, srid)| geo_to_ewkb(geo, srid)) + }); + + geo_convert_with_arg_fn::("to_geometry", registry, |input, srid| { + ewkb_to_geo(&mut Ewkb(input)).and_then(|(geo, _)| geo_to_ewkb(geo, Some(srid))) + }); + + geo_convert_fn::("to_geometry", registry, |input| { + json_to_geometry_impl(input, None).map_err(|e| ErrorCode::GeometryError(e.to_string())) + }); + + geo_convert_with_arg_fn::("to_geometry", registry, |input, srid| { + json_to_geometry_impl(input, Some(srid)) + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + }); + + geo_try_convert_fn::("try_to_geometry", registry, |input| { + json_to_geometry_impl(input, None).map_err(|e| ErrorCode::GeometryError(e.to_string())) + }); + + geo_try_convert_with_arg_fn::( + "try_to_geometry", + registry, + |input, srid| { + json_to_geometry_impl(input, Some(srid)) + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + }, + ); + + geo_try_convert_fn::("try_to_geometry", registry, |input| { + geometry_from_str(input, None) + }); + + geo_try_convert_with_arg_fn::( + "try_to_geometry", + registry, + |input, srid| geometry_from_str(input, Some(srid)), + ); + + geo_try_convert_fn::("try_to_geometry", registry, |input| { + ewkb_to_geo(&mut Ewkb(input)).and_then(|(geo, srid)| geo_to_ewkb(geo, srid)) + }); + + geo_try_convert_with_arg_fn::( + "try_to_geometry", + registry, + |input, srid| { + ewkb_to_geo(&mut Ewkb(input)).and_then(|(geo, _)| geo_to_ewkb(geo, Some(srid))) + }, ); - registry.register_passthrough_nullable_1_arg::( - "to_geometry", + registry.register_passthrough_nullable_1_arg::( + "st_asgeojson", |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|s, builder, ctx| { + vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.commit_row(); return; } } - match geometry_from_str(s, None) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), + + let result = ewkb_to_geo(&mut Ewkb(ewkb)) + .and_then(|(geo, _)| geo_to_json(geo)) + .and_then(|json| { + parse_owned_jsonb_with_buf(json.as_bytes(), &mut builder.data) + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + }); + if let Err(e) = result { + ctx.set_error(row, e.to_string()); } builder.commit_row(); }), ); - registry.register_passthrough_nullable_2_arg::( - "to_geometry", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |s, srid, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } - match geometry_from_str(s, Some(srid)) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }, - ), - ); + geometry_unary_fn::("st_asewkb", registry, |geo, srid| { + let wkb = geo_to_ewkb(geo, srid)?; + Ok(wkb) + }); + + geometry_unary_fn::("st_aswkb", registry, |geo, _| { + let wkb = geo_to_wkb(geo)?; + Ok(wkb) + }); + + geometry_unary_fn::("st_asewkt", registry, |geo, srid| { + let wkb = geo_to_ewkt(geo, srid)?; + Ok(wkb) + }); + + geometry_unary_fn::("st_aswkt", registry, |geo, _| { + let wkb = geo_to_wkt(geo)?; + Ok(wkb) + }); - registry.register_passthrough_nullable_1_arg::( - "to_geometry", + registry.register_passthrough_nullable_1_arg::( + "to_string", |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|binary, builder, ctx| { + vectorize_with_builder_1_arg::(|ewkb, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.commit_row(); return; } } - match ewkb_to_geo(&mut Ewkb(binary)).and_then(|(geo, srid)| geo_to_ewkb(geo, srid)) { - Ok(ewkb) => builder.put_slice(ewkb.as_slice()), + match geometry_format(ewkb, ctx.func_ctx.geometry_output_format) { + Ok(data) => builder.put_str(&data), Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + ctx.set_error(row, e.to_string()); } } builder.commit_row(); }), ); - registry.register_passthrough_nullable_2_arg::( - "to_geometry", - |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |binary, srid, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } - } + geo_convert_fn::("st_geohash", registry, |input| { + point_to_geohash(input, None) + }); - match ewkb_to_geo(&mut Ewkb(binary)) - .and_then(|(geo, _)| geo_to_ewkb(geo, Some(srid))) - { - Ok(ewkb) => builder.put_slice(ewkb.as_slice()), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - } - } - builder.commit_row(); - }, - ), - ); + geo_convert_with_arg_fn::( + "st_geohash", + registry, + |input, precision| { + if precision > 12 { + return Err(ErrorCode::GeometryError( + "Currently the precision only implement within 12 digits!", + )); + } + point_to_geohash(input, Some(precision)) + }, + ); + + geometry_unary_fn::>("st_area", registry, |geo, _| { + let area = geo.unsigned_area(); + let area = (area * 1_000_000_000_f64).round() / 1_000_000_000_f64; + Ok(area.into()) + }); + + geometry_unary_combine_fn::("st_envelope", registry, |geo, srid| { + match geo.bounding_rect() { + Some(rect) => { + let value = Geometry::from(rect_to_polygon(rect)); + let ewkb = geo_to_ewkb(value, srid)?; + Ok(Some(ewkb)) + } + None => Ok(None), + } + }); - registry.register_passthrough_nullable_1_arg::( - "to_geometry", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::(|json, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); - return; - } + geometry_unary_combine_fn::("st_centroid", registry, |geo, srid| { + match geo.centroid() { + Some(point) => { + let value = Geometry::from(point); + let ewkb = geo_to_ewkb(value, srid)?; + Ok(Some(ewkb)) + } + None => Ok(None), + } + }); + + geometry_unary_combine_fn::("st_startpoint", registry, |geo, srid| match geo { + Geometry::LineString(line_string) => { + let mut points = line_string.points(); + if let Some(point) = points.next() { + let value = Geometry::from(point); + let ewkb = geo_to_ewkb(value, srid)?; + Ok(Some(ewkb)) + } else { + Ok(None) } - match json_to_geometry_impl(json, None) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), + } + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_ENDPOINT", + geometry_type_name(&geo) + ))), + }); + + geometry_unary_combine_fn::("st_endpoint", registry, |geo, srid| match geo { + Geometry::LineString(line_string) => { + let mut points = line_string.points(); + if let Some(point) = points.next_back() { + let value = Geometry::from(point); + let ewkb = geo_to_ewkb(value, srid)?; + Ok(Some(ewkb)) + } else { + Ok(None) } - builder.commit_row(); - }), - ); + } + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_ENDPOINT", + geometry_type_name(&geo) + ))), + }); - registry.register_passthrough_nullable_2_arg::( - "to_geometry", + registry.register_combine_nullable_2_arg::( + "st_pointn", |_, _, _| FunctionDomain::MayThrow, - vectorize_with_builder_2_arg::( - |json, srid, builder, ctx| { + vectorize_with_builder_2_arg::>( + |ewkb, index, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.commit_row(); + if !validity.get_bit(row) { + builder.push_null(); return; } } - match json_to_geometry_impl(json, Some(srid)) { - Ok(data) => builder.put_slice(data.as_slice()), - Err(e) => ctx.set_error(builder.len(), e.to_string()), - } - builder.commit_row(); - }, - ), - ); - registry.register_combine_nullable_1_arg::( - "try_to_geometry", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::>( - |json, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; + match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| match geo { + Geometry::LineString(line_string) => { + let mut points = line_string.points(); + let len = points.len() as i32; + let idx = if index < 0 { len + index } else { index - 1 }; + if let Some(point) = points.nth(idx as usize) { + geo_to_ewkb(Geometry::from(point), srid).map(Some) + } else { + Ok(None) + } } - } - match json_to_geometry_impl(json, None) { - Ok(data) => { - output.push(data.as_slice()); + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_POINTN", + geometry_type_name(&geo) + ))), + }) { + Ok(Some(binary)) => { + builder.push(binary.as_slice()); } - Err(_) => output.push_null(), - } - }, - ), - ); - - registry.register_combine_nullable_2_arg::( - "try_to_geometry", - |_, _, _| FunctionDomain::Full, - vectorize_with_builder_2_arg::>( - |json, srid, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; + Ok(None) => { + builder.push_null(); } - } - match json_to_geometry_impl(json, Some(srid)) { - Ok(data) => { - output.push(data.as_slice()); + Err(e) => { + ctx.set_error(row, e.to_string()); + builder.push_null(); } - Err(_) => output.push_null(), } }, ), ); - registry.register_combine_nullable_1_arg::( - "try_to_geometry", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::>(|s, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; + geometry_unary_fn::>("st_length", registry, |geo, _| { + let mut distance = 0f64; + match geo { + Geometry::LineString(lines) => { + for line in lines.lines() { + distance += Euclidean.length(&line); } } - match geometry_from_str(s, None) { - Ok(data) => { - output.push(data.as_slice()); - } - Err(_) => output.push_null(), - } - }), - ); - - registry.register_combine_nullable_2_arg::( - "try_to_geometry", - |_, _, _| FunctionDomain::Full, - vectorize_with_builder_2_arg::>( - |s, srid, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; + Geometry::MultiLineString(multi_lines) => { + for line_string in multi_lines.0 { + for line in line_string.lines() { + distance += Euclidean.length(&line); } } - match geometry_from_str(s, Some(srid)) { - Ok(data) => { - output.push(data.as_slice()); + } + Geometry::GeometryCollection(geom_c) => { + for geometry in geom_c.0 { + if let Geometry::LineString(line_string) = geometry { + for line in line_string.lines() { + distance += Euclidean.length(&line); + } } - Err(_) => output.push_null(), } - }, - ), - ); + } + _ => {} + } + let distance = (distance * 1_000_000_000_f64).round() / 1_000_000_000_f64; + Ok(distance.into()) + }); + + geometry_unary_fn::>("st_x", registry, |geo, _| match geo { + Geometry::Point(point) => Ok(AsPrimitive::::as_(point.x()).into()), + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_X", + geometry_type_name(&geo) + ))), + }); + + geometry_unary_fn::>("st_y", registry, |geo, _| match geo { + Geometry::Point(point) => Ok(AsPrimitive::::as_(point.y()).into()), + _ => Err(ErrorCode::GeometryError(format!( + "Type {} is not supported as argument to ST_Y", + geometry_type_name(&geo) + ))), + }); + + geometry_unary_combine_fn::>("st_xmax", registry, |geo, _| { + match st_extreme(&geo, Axis::X, Extremum::Max) { + Some(x_max) => Ok(Some(AsPrimitive::::as_(x_max).into())), + None => Ok(None), + } + }); - registry.register_combine_nullable_1_arg::( - "try_to_geometry", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::>( - |binary, output, ctx| { + geometry_unary_combine_fn::>("st_xmin", registry, |geo, _| { + match st_extreme(&geo, Axis::X, Extremum::Min) { + Some(x_min) => Ok(Some(AsPrimitive::::as_(x_min).into())), + None => Ok(None), + } + }); + + geometry_unary_combine_fn::>("st_ymax", registry, |geo, _| { + match st_extreme(&geo, Axis::Y, Extremum::Max) { + Some(y_max) => Ok(Some(AsPrimitive::::as_(y_max).into())), + None => Ok(None), + } + }); + + geometry_unary_combine_fn::>("st_ymin", registry, |geo, _| { + match st_extreme(&geo, Axis::Y, Extremum::Min) { + Some(y_min) => Ok(Some(AsPrimitive::::as_(y_min).into())), + None => Ok(None), + } + }); + + geometry_unary_fn::>("st_npoints", registry, |geo, _| { + let npoints = count_points(&geo); + Ok(npoints as u32) + }); + + geometry_unary_fn::("st_convexhull", registry, |geo, srid| { + let polygon = geo.convex_hull(); + let value = Geometry::from(polygon); + let ewkb = geo_to_ewkb(value, srid)?; + Ok(ewkb) + }); + + geometry_binary_fn::("st_contains", registry, |l, r, _| Ok(l.contains(&r))); + + geometry_binary_fn::("st_intersects", registry, |l, r, _| Ok(l.intersects(&r))); + + geometry_binary_fn::("st_disjoint", registry, |l, r, _| Ok(!l.intersects(&r))); + + geometry_binary_fn::("st_within", registry, |l, r, _| Ok(l.is_within(&r))); + + geometry_binary_fn::("st_equals", registry, |l, r, _| { + Ok(l.is_within(&r) && r.is_within(&l)) + }); + + geometry_binary_fn::>("st_distance", registry, |l, r, _| { + let distance = Euclidean.distance(&l, &r); + let distance = (distance * 1_000_000_000_f64).round() / 1_000_000_000_f64; + Ok(distance.into()) + }); + + geometry_binary_combine_fn::("st_union", registry, |l, r, srid| { + GeometryUnionAggOp::binary_compute(l, r)? + .map(|value| geo_to_ewkb(value, srid)) + .transpose() + }); + + geometry_binary_combine_fn::("st_intersection", registry, |l, r, srid| { + GeometryIntersectionAggOp::binary_compute(l, r)? + .map(|value| geo_to_ewkb(value, srid)) + .transpose() + }); + + geometry_binary_combine_fn::("st_difference", registry, |l, r, srid| { + GeometryDifferenceAggOp::binary_compute(l, r)? + .map(|value| geo_to_ewkb(value, srid)) + .transpose() + }); + + geometry_binary_combine_fn::("st_symdifference", registry, |l, r, srid| { + GeometrySymDifferenceAggOp::binary_compute(l, r)? + .map(|value| geo_to_ewkb(value, srid)) + .transpose() + }); + + geometry_unary_combine_fn::>("st_dimension", registry, |geo, _| { + let dimension: Option = match geo.dimensions() { + Dimensions::Empty => None, + Dimensions::ZeroDimensional => Some(0), + Dimensions::OneDimensional => Some(1), + Dimensions::TwoDimensional => Some(2), + }; + Ok(dimension) + }); + + registry.register_passthrough_nullable_2_arg::( + "st_setsrid", + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + |ewkb, srid, builder, ctx| { if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); + if !validity.get_bit(builder.len()) { + builder.commit_row(); return; } } - match ewkb_to_geo(&mut Ewkb(binary)) { - Ok((geo, srid)) => match geo.to_ewkb(CoordDimensions::xy(), srid) { - Ok(ewkb) => { - output.push(ewkb.as_slice()); - } - Err(_) => output.push_null(), - }, - Err(_) => output.push_null(), + match Ewkb(ewkb) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(|geo| geo_to_ewkb(geo, Some(srid))) + { + Ok(ewkb) => { + builder.put_slice(ewkb.as_slice()); + } + Err(e) => { + ctx.set_error(builder.len(), e.to_string()); + } } + builder.commit_row(); }, ), ); - registry.register_combine_nullable_2_arg::( - "try_to_geometry", - |_, _, _| FunctionDomain::Full, - vectorize_with_builder_2_arg::>( - |binary, srid, output, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(output.len()) { - output.push_null(); - return; - } + registry.register_passthrough_nullable_1_arg::( + "st_srid", + |_, _| FunctionDomain::Full, + vectorize_with_builder_1_arg::(|ewkb, output, ctx| { + if let Some(validity) = &ctx.validity { + if !validity.get_bit(output.len()) { + output.push(0); + return; } + } - match Ewkb(binary).to_geo() { - Ok(geo) => match geo.to_ewkb(CoordDimensions::xy(), Some(srid)) { - Ok(ewkb) => { - output.push(ewkb.as_slice()); - } - Err(_) => output.push_null(), - }, - Err(_) => output.push_null(), - } - }, - ), + let srid = read_srid(&mut Ewkb(ewkb)).unwrap_or_default(); + output.push(srid); + }), ); registry.register_passthrough_nullable_2_arg::( @@ -1616,8 +718,9 @@ pub fn register(registry: &mut FunctionRegistry) { |_, _, _| FunctionDomain::MayThrow, vectorize_with_builder_2_arg::( |original, to_srid, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.commit_row(); return; } @@ -1625,22 +728,17 @@ pub fn register(registry: &mut FunctionRegistry) { // All representations of the geo types supported by crates under the GeoRust organization, have not implemented srid(). // Currently, the srid() of all types returns the default value `None`, so we need to parse it manually here. - let from_srid = read_srid(&mut Ewkb(original)); - if from_srid.is_none() { - ctx.set_error( - builder.len(), - "input geometry must has the correct SRID".to_string(), - ); + let Some(from_srid) = read_srid(&mut Ewkb(original)) else { + ctx.set_error(row, "input geometry must has the correct SRID".to_string()); builder.commit_row(); return; - } - let from_srid = from_srid.unwrap(); + }; match st_transform_impl(original, from_srid, to_srid) { Ok(data) => { builder.put_slice(data.as_slice()); } Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + ctx.set_error(row, e.to_string()); } } builder.commit_row(); @@ -1654,8 +752,9 @@ pub fn register(registry: &mut FunctionRegistry) { |_, _, _, _| FunctionDomain::MayThrow, vectorize_with_builder_3_arg::( |original, from_srid, to_srid, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.commit_row(); return; } @@ -1666,7 +765,7 @@ pub fn register(registry: &mut FunctionRegistry) { builder.put_slice(data.as_slice()); } Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + ctx.set_error(row, e.to_string()); } } builder.commit_row(); @@ -1674,37 +773,15 @@ pub fn register(registry: &mut FunctionRegistry) { ), ); - registry.register_combine_nullable_1_arg::( - "st_hilbert", - |_, _| FunctionDomain::MayThrow, - vectorize_with_builder_1_arg::>( - |ewkb, builder, ctx| { - if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { - builder.push_null(); - return; - } - } - - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geometry_bbox_center(&geo) { - Some((x, y)) => match hilbert_index_from_point(x, y) { - Ok(index) => builder.push(index), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - }, - None => builder.push_null(), - }, - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - } - }, - ), - ); + geometry_unary_combine_fn::>("st_hilbert", registry, |geo, _| { + match geometry_bbox_center(&geo) { + Some((x, y)) => { + let index = hilbert_index_from_point(x, y)?; + Ok(Some(index)) + } + None => Ok(None), + } + }); registry.register_combine_nullable_2_arg::< GeometryType, @@ -1720,26 +797,26 @@ pub fn register(registry: &mut FunctionRegistry) { ArrayType>, NullableType, >(|ewkb, bounds, builder, ctx| { + let row = builder.len(); if let Some(validity) = &ctx.validity { - if !validity.get_bit(builder.len()) { + if !validity.get_bit(row) { builder.push_null(); return; } } - match Ewkb(ewkb).to_geo() { - Ok(geo) => match geometry_bbox_center(&geo) { - Some((x, y)) => match hilbert_index_from_bounds_slice(x, y, &bounds) { - Ok(index) => builder.push(index), - Err(e) => { - ctx.set_error(builder.len(), e.to_string()); - builder.push_null(); - } - }, - None => builder.push_null(), + let result = Ewkb(ewkb).to_geo().map(|geo| geometry_bbox_center(&geo)); + match result { + Ok(Some((x, y))) => match hilbert_index_from_bounds_slice(x, y, &bounds) { + Ok(index) => builder.push(index), + Err(e) => { + ctx.set_error(row, e.to_string()); + builder.push_null(); + } }, + Ok(None) => builder.push_null(), Err(e) => { - ctx.set_error(builder.len(), e.to_string()); + ctx.set_error(row, e.to_string()); builder.push_null(); } } @@ -1927,19 +1004,3 @@ fn round_geometry_coordinates(geom: Geometry) -> Geometry { )), } } - -fn check_incompatible_srid( - l_srid: Option, - r_srid: Option, - len: usize, - ctx: &mut EvalContext, -) -> bool { - let l_srid = l_srid.unwrap_or_default(); - let r_srid = r_srid.unwrap_or_default(); - if !l_srid.eq(&r_srid) { - ctx.set_error(len, format!("Incompatible SRID: {} and {}", l_srid, r_srid)); - false - } else { - true - } -} diff --git a/src/query/functions/src/scalars/geographic/src/lib.rs b/src/query/functions/src/scalars/geographic/src/lib.rs index 9b985fd9d1698..ca62b105fdac2 100644 --- a/src/query/functions/src/scalars/geographic/src/lib.rs +++ b/src/query/functions/src/scalars/geographic/src/lib.rs @@ -30,3 +30,4 @@ pub mod geo; pub mod geo_h3; pub mod geography; pub mod geometry; +mod register; diff --git a/src/query/functions/src/scalars/geographic/src/register.rs b/src/query/functions/src/scalars/geographic/src/register.rs new file mode 100644 index 0000000000000..3850bd4e5e470 --- /dev/null +++ b/src/query/functions/src/scalars/geographic/src/register.rs @@ -0,0 +1,429 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use databend_common_exception::ErrorCode; +use databend_common_exception::Result; +use databend_common_expression::EvalContext; +use databend_common_expression::FunctionDomain; +use databend_common_expression::FunctionRegistry; +use databend_common_expression::types::ArgType; +use databend_common_expression::types::GeographyType; +use databend_common_expression::types::GeometryType; +use databend_common_expression::types::Int32Type; +use databend_common_expression::types::NullableType; +use databend_common_expression::vectorize_with_builder_1_arg; +use databend_common_expression::vectorize_with_builder_2_arg; +use databend_common_io::ewkb_to_geo; +use geo::Geometry; +use geozero::ToGeo; +use geozero::wkb::Ewkb; + +pub(crate) fn geometry_unary_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(Geometry, Option) -> Result, +) { + registry.register_passthrough_nullable_1_arg::( + name, + |_, _| FunctionDomain::MayThrow, + vectorize_with_builder_1_arg::(move |ewkb, builder, ctx| { + let row = O::builder_len(builder); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + O::push_default(builder); + return; + } + } + + match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| op(geo, srid)) { + Ok(value) => { + O::push_item(builder, O::to_scalar_ref(&value)); + } + Err(e) => { + ctx.set_error(row, e.to_string()); + O::push_default(builder); + } + } + }), + ); +} + +pub(crate) fn geometry_unary_combine_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(Geometry, Option) -> Result>, +) { + registry.register_combine_nullable_1_arg::( + name, + |_, _| FunctionDomain::MayThrow, + vectorize_with_builder_1_arg::>(move |ewkb, builder, ctx| { + if let Some(validity) = &ctx.validity { + if !validity.get_bit(builder.len()) { + builder.push_null(); + return; + } + } + + match ewkb_to_geo(&mut Ewkb(ewkb)).and_then(|(geo, srid)| op(geo, srid)) { + Ok(Some(value)) => builder.push(O::to_scalar_ref(&value)), + Ok(None) => builder.push_null(), + Err(e) => { + ctx.set_error(builder.len(), e.to_string()); + builder.push_null(); + } + } + }), + ); +} + +pub(crate) fn geometry_binary_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(Geometry, Geometry, Option) -> Result, +) { + registry.register_passthrough_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + move |l_ewkb, r_ewkb, builder, ctx| { + let row = O::builder_len(builder); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + O::push_default(builder); + return; + } + } + + let result = match ( + ewkb_to_geo(&mut Ewkb(l_ewkb)), + ewkb_to_geo(&mut Ewkb(r_ewkb)), + ) { + (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { + if !check_incompatible_srid(l_srid, r_srid, row, ctx) { + O::push_default(builder); + return; + } + op(l_geo, r_geo, l_srid) + } + (Err(e), _) | (_, Err(e)) => Err(e), + }; + + match result { + Ok(result) => { + O::push_item(builder, O::to_scalar_ref(&result)); + } + Err(e) => { + ctx.set_error(row, e.to_string()); + O::push_default(builder); + } + } + }, + ), + ); +} + +pub(crate) fn geometry_binary_combine_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(Geometry, Geometry, Option) -> Result>, +) { + registry.register_combine_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::>( + move |l_ewkb, r_ewkb, builder, ctx| { + let row = builder.len(); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + builder.push_null(); + return; + } + } + + let result = match ( + ewkb_to_geo(&mut Ewkb(l_ewkb)), + ewkb_to_geo(&mut Ewkb(r_ewkb)), + ) { + (Ok((l_geo, l_srid)), Ok((r_geo, r_srid))) => { + if !check_incompatible_srid(l_srid, r_srid, row, ctx) { + builder.push_null(); + return; + } + op(l_geo, r_geo, l_srid) + } + (Err(e), _) | (_, Err(e)) => Err(e), + }; + + match result { + Ok(Some(value)) => { + builder.push(O::to_scalar_ref(&value)); + } + Ok(None) => { + builder.push_null(); + } + Err(e) => { + ctx.set_error(row, e.to_string()); + builder.push_null(); + } + } + }, + ), + ); +} + +pub(crate) fn geography_unary_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(Geometry) -> Result, +) { + registry.register_passthrough_nullable_1_arg::( + name, + |_, _| FunctionDomain::MayThrow, + vectorize_with_builder_1_arg::(move |ewkb, builder, ctx| { + let row = O::builder_len(builder); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + O::push_default(builder); + return; + } + } + + let result = Ewkb(ewkb) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(op); + match result { + Ok(value) => { + O::push_item(builder, O::to_scalar_ref(&value)); + } + Err(e) => { + ctx.set_error(row, e.to_string()); + O::push_default(builder); + } + } + }), + ); +} + +pub(crate) fn geography_unary_combine_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(Geometry) -> Result>, +) { + registry.register_combine_nullable_1_arg::( + name, + |_, _| FunctionDomain::MayThrow, + vectorize_with_builder_1_arg::>( + move |ewkb, builder, ctx| { + if let Some(validity) = &ctx.validity { + if !validity.get_bit(builder.len()) { + builder.push_null(); + return; + } + } + + let result = Ewkb(ewkb) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(op); + match result { + Ok(Some(value)) => builder.push(O::to_scalar_ref(&value)), + Ok(None) => builder.push_null(), + Err(e) => { + ctx.set_error(builder.len(), e.to_string()); + builder.push_null(); + } + } + }, + ), + ); +} + +pub(crate) fn geography_binary_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(Geometry, Geometry) -> Result, +) { + registry.register_passthrough_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::( + move |l_ewkb, r_ewkb, builder, ctx| { + let row = O::builder_len(builder); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + O::push_default(builder); + return; + } + } + + let result = Ewkb(l_ewkb) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(|l_geo| { + Ewkb(r_ewkb) + .to_geo() + .map_err(|e| ErrorCode::GeometryError(e.to_string())) + .and_then(|r_geo| op(l_geo, r_geo)) + }); + + match result { + Ok(result) => { + O::push_item(builder, O::to_scalar_ref(&result)); + } + Err(e) => { + ctx.set_error(row, e.to_string()); + O::push_default(builder); + } + } + }, + ), + ); +} + +pub(crate) fn geo_convert_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(I::ScalarRef<'_>) -> Result, +) { + registry.register_passthrough_nullable_1_arg::( + name, + |_, _| FunctionDomain::MayThrow, + vectorize_with_builder_1_arg::(move |input, builder, ctx| { + let row = O::builder_len(builder); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + O::push_default(builder); + return; + } + } + + match op(input) { + Ok(value) => { + O::push_item(builder, O::to_scalar_ref(&value)); + } + Err(e) => { + ctx.set_error(row, e.to_string()); + O::push_default(builder); + } + } + }), + ); +} + +pub(crate) fn geo_convert_with_arg_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(I::ScalarRef<'_>, i32) -> Result, +) { + registry.register_passthrough_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::MayThrow, + vectorize_with_builder_2_arg::(move |input, srid, builder, ctx| { + let row = O::builder_len(builder); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + O::push_default(builder); + return; + } + } + + match op(input, srid) { + Ok(value) => { + O::push_item(builder, O::to_scalar_ref(&value)); + } + Err(e) => { + ctx.set_error(row, e.to_string()); + O::push_default(builder); + } + } + }), + ); +} + +pub(crate) fn geo_try_convert_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(I::ScalarRef<'_>) -> Result, +) { + registry.register_combine_nullable_1_arg::( + name, + |_, _| FunctionDomain::Full, + vectorize_with_builder_1_arg::>(move |input, builder, ctx| { + let row = builder.len(); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + builder.push_null(); + return; + } + } + + match op(input) { + Ok(value) => { + builder.push(O::to_scalar_ref(&value)); + } + Err(_) => { + builder.push_null(); + } + } + }), + ); +} + +pub(crate) fn geo_try_convert_with_arg_fn( + name: &str, + registry: &mut FunctionRegistry, + op: fn(I::ScalarRef<'_>, i32) -> Result, +) { + registry.register_combine_nullable_2_arg::( + name, + |_, _, _| FunctionDomain::Full, + vectorize_with_builder_2_arg::>( + move |input, srid, builder, ctx| { + let row = builder.len(); + if let Some(validity) = &ctx.validity { + if !validity.get_bit(row) { + builder.push_null(); + return; + } + } + + match op(input, srid) { + Ok(value) => { + builder.push(O::to_scalar_ref(&value)); + } + Err(_) => { + builder.push_null(); + } + } + }, + ), + ); +} + +fn check_incompatible_srid( + l_srid: Option, + r_srid: Option, + len: usize, + ctx: &mut EvalContext, +) -> bool { + let l_srid = l_srid.unwrap_or_default(); + let r_srid = r_srid.unwrap_or_default(); + if !l_srid.eq(&r_srid) { + ctx.set_error(len, format!("Incompatible SRID: {} and {}", l_srid, r_srid)); + false + } else { + true + } +} diff --git a/src/query/functions/tests/it/aggregates/agg.rs b/src/query/functions/tests/it/aggregates/agg.rs index 8f9f744afaaa9..b7205ece5ba5a 100644 --- a/src/query/functions/tests/it/aggregates/agg.rs +++ b/src/query/functions/tests/it/aggregates/agg.rs @@ -127,6 +127,9 @@ fn test_aggr_functions() { test_agg_json_array_agg(file, eval_aggr); test_agg_json_object_agg(file, eval_aggr); test_agg_mode(file, eval_aggr); + test_agg_st_union_agg(file, eval_aggr); + test_agg_st_intersection_agg(file, eval_aggr); + test_agg_st_envelope_agg(file, eval_aggr); test_agg_st_collect(file, eval_aggr); } @@ -171,6 +174,9 @@ fn test_aggr_functions_group_by() { test_agg_json_array_agg(file, eval_aggr); test_agg_json_object_agg(file, eval_aggr); test_agg_mode(file, simulate_two_groups_group_by); + test_agg_st_union_agg(file, simulate_two_groups_group_by); + test_agg_st_intersection_agg(file, simulate_two_groups_group_by); + test_agg_st_envelope_agg(file, simulate_two_groups_group_by); test_agg_st_collect(file, eval_aggr); } @@ -401,6 +407,35 @@ fn get_geometry_example() -> Vec<(&'static str, BlockEntry)> { .collect() } +fn get_geometry_overlap_example() -> Vec<(&'static str, BlockEntry)> { + [ + ( + "polygon_overlap", + StringType::from_data(vec![ + "POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))", + "POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))", + "POLYGON((1.5 1.5, 2.5 1.5, 2.5 2.5, 1.5 2.5, 1.5 1.5))", + "POLYGON((1.8 1.8, 2.2 1.8, 2.2 2.2, 1.8 2.2, 1.8 1.8))", + ]), + ), + ( + "polygon_overlap_null", + StringType::from_data_with_validity( + vec![ + "POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))", + "", + "POLYGON((1.5 1.5, 2.5 1.5, 2.5 2.5, 1.5 2.5, 1.5 1.5))", + "POLYGON((1.8 1.8, 2.2 1.8, 2.2 2.2, 1.8 2.2, 1.8 1.8))", + ], + vec![true, false, true, true], + ), + ), + ] + .into_iter() + .map(|(name, column)| (name, column.into())) + .collect() +} + fn test_count(file: &mut impl Write, simulator: impl AggregationSimulator) { run_agg_ast( file, @@ -1662,6 +1697,71 @@ fn test_agg_mode(file: &mut impl Write, simulator: impl AggregationSimulator) { ); } +fn test_agg_st_union_agg(file: &mut impl Write, simulator: impl AggregationSimulator) { + run_agg_ast( + file, + "st_union_agg(to_geometry(point))", + get_geometry_example().as_slice(), + simulator, + vec![], + ); + run_agg_ast( + file, + "st_union_agg(to_geometry(polygon))", + get_geometry_example().as_slice(), + simulator, + vec![], + ); + run_agg_ast( + file, + "st_union_agg(to_geometry(point_null))", + get_geometry_example().as_slice(), + simulator, + vec![], + ); +} + +fn test_agg_st_intersection_agg(file: &mut impl Write, simulator: impl AggregationSimulator) { + run_agg_ast( + file, + "st_intersection_agg(to_geometry(polygon_overlap))", + get_geometry_overlap_example().as_slice(), + simulator, + vec![], + ); + run_agg_ast( + file, + "st_intersection_agg(to_geometry(polygon_overlap_null))", + get_geometry_overlap_example().as_slice(), + simulator, + vec![], + ); +} + +fn test_agg_st_envelope_agg(file: &mut impl Write, simulator: impl AggregationSimulator) { + run_agg_ast( + file, + "st_envelope_agg(to_geometry(point))", + get_geometry_example().as_slice(), + simulator, + vec![], + ); + run_agg_ast( + file, + "st_envelope_agg(to_geometry(line_string))", + get_geometry_example().as_slice(), + simulator, + vec![], + ); + run_agg_ast( + file, + "st_envelope_agg(to_geometry(point_null))", + get_geometry_example().as_slice(), + simulator, + vec![], + ); +} + fn test_agg_st_collect(file: &mut impl Write, simulator: impl AggregationSimulator) { run_agg_ast( file, diff --git a/src/query/functions/tests/it/aggregates/testdata/agg.txt b/src/query/functions/tests/it/aggregates/testdata/agg.txt index 730ee4011e759..01ff5f83813b5 100644 --- a/src/query/functions/tests/it/aggregates/testdata/agg.txt +++ b/src/query/functions/tests/it/aggregates/testdata/agg.txt @@ -1859,6 +1859,86 @@ evaluation (internal): +----------+---------------------------------------------------------------------------------+ +ast: st_union_agg(to_geometry(point)) +evaluation (internal): ++--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| point | Column(StringColumn[POINT(1 1), POINT(2 2), POINT(3 3), POINT(4 4)]) | +| Output | NullableColumn { column: Geometry([0x0104000000040000000101000000000000000000f03f000000000000f03f010100000000000000000000400000000000000040010100000000000000000008400000000000000840010100000000000000000010400000000000001040]), validity: [0b_______1] } | ++--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_union_agg(to_geometry(polygon)) +evaluation (internal): ++---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| polygon | Column(StringColumn[POLYGON((0 0, 1 0, 1 1, 0 1, 0 0)), POLYGON((1 1, 2 1, 2 2, 1 2, 1 1)), POLYGON((2 2, 3 2, 3 3, 2 3, 2 2)), POLYGON((3 3, 4 3, 4 4, 3 4, 3 3))]) | +| Output | NullableColumn { column: Geometry([0x010600000004000000010300000001000000050000000000000000000000000000000000f03f00000000000000000000000000000000000000000000f03f0000000000000000000000000000f03f000000000000f03f0000000000000000000000000000f03f01030000000100000005000000000000000000f03f0000000000000040000000000000f03f000000000000f03f0000000000000040000000000000f03f00000000000000400000000000000040000000000000f03f0000000000000040010300000001000000050000000000000000000040000000000000084000000000000000400000000000000040000000000000084000000000000000400000000000000840000000000000084000000000000000400000000000000840010300000001000000050000000000000000000840000000000000104000000000000008400000000000000840000000000000104000000000000008400000000000001040000000000000104000000000000008400000000000001040]), validity: [0b_______1] } | ++---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_union_agg(to_geometry(point_null)) +evaluation (internal): ++------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| point_null | Column(NullableColumn { column: StringColumn[POINT(1 1), , POINT(3 3), POINT(4 4)], validity: [0b____1101] }) | +| Output | NullableColumn { column: Geometry([0x0104000000030000000101000000000000000000f03f000000000000f03f010100000000000000000008400000000000000840010100000000000000000010400000000000001040]), validity: [0b_______1] } | ++------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_intersection_agg(to_geometry(polygon_overlap)) +evaluation (internal): ++-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| polygon_overlap | Column(StringColumn[POLYGON((0 0, 4 0, 4 4, 0 4, 0 0)), POLYGON((1 1, 3 1, 3 3, 1 3, 1 1)), POLYGON((1.5 1.5, 2.5 1.5, 2.5 2.5, 1.5 2.5, 1.5 1.5)), POLYGON((1.8 1.8, 2.2 1.8, 2.2 2.2, 1.8 2.2, 1.8 1.8))]) | +| Output | NullableColumn { column: Geometry([0x010300000001000000050000000000c0ccccccfc3f0000a099999901400000c0ccccccfc3f0000c0ccccccfc3f0000a099999901400000c0ccccccfc3f0000a099999901400000a099999901400000c0ccccccfc3f0000a09999990140]), validity: [0b_______1] } | ++-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_intersection_agg(to_geometry(polygon_overlap_null)) +evaluation (internal): ++----------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++----------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| polygon_overlap_null | Column(NullableColumn { column: StringColumn[POLYGON((0 0, 4 0, 4 4, 0 4, 0 0)), , POLYGON((1.5 1.5, 2.5 1.5, 2.5 2.5, 1.5 2.5, 1.5 1.5)), POLYGON((1.8 1.8, 2.2 1.8, 2.2 2.2, 1.8 2.2, 1.8 1.8))], validity: [0b____1101] }) | +| Output | NullableColumn { column: Geometry([0x010300000001000000050000000000c0ccccccfc3f0000a099999901400000c0ccccccfc3f0000c0ccccccfc3f0000a099999901400000c0ccccccfc3f0000a099999901400000a099999901400000c0ccccccfc3f0000a09999990140]), validity: [0b_______1] } | ++----------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_envelope_agg(to_geometry(point)) +evaluation (internal): ++--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| point | Column(StringColumn[POINT(1 1), POINT(2 2), POINT(3 3), POINT(4 4)]) | +| Output | NullableColumn { column: Geometry([0x01030000000100000005000000000000000000f03f000000000000f03f0000000000001040000000000000f03f00000000000010400000000000001040000000000000f03f0000000000001040000000000000f03f000000000000f03f]), validity: [0b_______1] } | ++--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_envelope_agg(to_geometry(line_string)) +evaluation (internal): ++-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| line_string | Column(StringColumn[LINESTRING(0 0, 1 1), LINESTRING(1 1, 2 2), LINESTRING(2 2, 3 3), LINESTRING(3 3, 4 4)]) | +| Output | NullableColumn { column: Geometry([0x010300000001000000050000000000000000000000000000000000000000000000000010400000000000000000000000000000104000000000000010400000000000000000000000000000104000000000000000000000000000000000]), validity: [0b_______1] } | ++-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_envelope_agg(to_geometry(point_null)) +evaluation (internal): ++------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| point_null | Column(NullableColumn { column: StringColumn[POINT(1 1), , POINT(3 3), POINT(4 4)], validity: [0b____1101] }) | +| Output | NullableColumn { column: Geometry([0x01030000000100000005000000000000000000f03f000000000000f03f0000000000001040000000000000f03f00000000000010400000000000001040000000000000f03f0000000000001040000000000000f03f000000000000f03f]), validity: [0b_______1] } | ++------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + ast: st_collect(to_geometry('point(10 20)')) evaluation (internal): +--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -1885,7 +1965,7 @@ evaluation (internal): | Column | Data | +--------+----------------------------------------------------------------------+ | point | Column(StringColumn[POINT(1 1), POINT(2 2), POINT(3 3), POINT(4 4)]) | -| Output | NullableColumn { column: Geometry([0x]), validity: [0b_______0] } | +| Output | Null { len: 1 } | +--------+----------------------------------------------------------------------+ diff --git a/src/query/functions/tests/it/aggregates/testdata/agg_group_by.txt b/src/query/functions/tests/it/aggregates/testdata/agg_group_by.txt index a35b94cc889de..8b15182a60a49 100644 --- a/src/query/functions/tests/it/aggregates/testdata/agg_group_by.txt +++ b/src/query/functions/tests/it/aggregates/testdata/agg_group_by.txt @@ -1797,6 +1797,86 @@ evaluation (internal): +----------+---------------------------------------------------------------------------------+ +ast: st_union_agg(to_geometry(point)) +evaluation (internal): ++--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| point | Column(StringColumn[POINT(1 1), POINT(2 2), POINT(3 3), POINT(4 4)]) | +| Output | NullableColumn { column: Geometry([0x0104000000020000000101000000000000000000f03f000000000000f03f010100000000000000000008400000000000000840, 0x010400000002000000010100000000000000000000400000000000000040010100000000000000000010400000000000001040]), validity: [0b______11] } | ++--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_union_agg(to_geometry(polygon)) +evaluation (internal): ++---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| polygon | Column(StringColumn[POLYGON((0 0, 1 0, 1 1, 0 1, 0 0)), POLYGON((1 1, 2 1, 2 2, 1 2, 1 1)), POLYGON((2 2, 3 2, 3 3, 2 3, 2 2)), POLYGON((3 3, 4 3, 4 4, 3 4, 3 3))]) | +| Output | NullableColumn { column: Geometry([0x010600000002000000010300000001000000050000000000000000000000000000000000f03f00000000000000000000000000000000000000000000f03f0000000000000000000000000000f03f000000000000f03f0000000000000000000000000000f03f010300000001000000050000000000000000000040000000000000084000000000000000400000000000000040000000000000084000000000000000400000000000000840000000000000084000000000000000400000000000000840, 0x01060000000200000001030000000100000005000000000000000000f03f0000000000000040000000000000f03f000000000000f03f0000000000000040000000000000f03f00000000000000400000000000000040000000000000f03f0000000000000040010300000001000000050000000000000000000840000000000000104000000000000008400000000000000840000000000000104000000000000008400000000000001040000000000000104000000000000008400000000000001040]), validity: [0b______11] } | ++---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_union_agg(to_geometry(point_null)) +evaluation (internal): ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| point_null | Column(NullableColumn { column: StringColumn[POINT(1 1), , POINT(3 3), POINT(4 4)], validity: [0b____1101] }) | +| Output | NullableColumn { column: Geometry([0x0104000000020000000101000000000000000000f03f000000000000f03f010100000000000000000008400000000000000840, 0x010100000000000000000010400000000000001040]), validity: [0b______11] } | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_intersection_agg(to_geometry(polygon_overlap)) +evaluation (internal): ++-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| polygon_overlap | Column(StringColumn[POLYGON((0 0, 4 0, 4 4, 0 4, 0 0)), POLYGON((1 1, 3 1, 3 3, 1 3, 1 1)), POLYGON((1.5 1.5, 2.5 1.5, 2.5 2.5, 1.5 2.5, 1.5 1.5)), POLYGON((1.8 1.8, 2.2 1.8, 2.2 2.2, 1.8 2.2, 1.8 1.8))]) | +| Output | NullableColumn { column: Geometry([0x01030000000100000005000000000000000000f83f0000000000000440000000000000f83f000000000000f83f0000000000000440000000000000f83f00000000000004400000000000000440000000000000f83f0000000000000440, 0x01030000000100000005000000000080ccccccfc3f0000c09999990140000080ccccccfc3f000080ccccccfc3f0000c09999990140000080ccccccfc3f0000c099999901400000c09999990140000080ccccccfc3f0000c09999990140]), validity: [0b______11] } | ++-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_intersection_agg(to_geometry(polygon_overlap_null)) +evaluation (internal): ++----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| polygon_overlap_null | Column(NullableColumn { column: StringColumn[POLYGON((0 0, 4 0, 4 4, 0 4, 0 0)), , POLYGON((1.5 1.5, 2.5 1.5, 2.5 2.5, 1.5 2.5, 1.5 1.5)), POLYGON((1.8 1.8, 2.2 1.8, 2.2 2.2, 1.8 2.2, 1.8 1.8))], validity: [0b____1101] }) | +| Output | NullableColumn { column: Geometry([0x01030000000100000005000000000000000000f83f0000000000000440000000000000f83f000000000000f83f0000000000000440000000000000f83f00000000000004400000000000000440000000000000f83f0000000000000440, 0x01030000000100000005000000cdccccccccccfc3fcdccccccccccfc3f9a99999999990140cdccccccccccfc3f9a999999999901409a99999999990140cdccccccccccfc3f9a99999999990140cdccccccccccfc3fcdccccccccccfc3f]), validity: [0b______11] } | ++----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_envelope_agg(to_geometry(point)) +evaluation (internal): ++--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| point | Column(StringColumn[POINT(1 1), POINT(2 2), POINT(3 3), POINT(4 4)]) | +| Output | NullableColumn { column: Geometry([0x01030000000100000005000000000000000000f03f000000000000f03f0000000000000840000000000000f03f00000000000008400000000000000840000000000000f03f0000000000000840000000000000f03f000000000000f03f, 0x010300000001000000050000000000000000000040000000000000004000000000000010400000000000000040000000000000104000000000000010400000000000000040000000000000104000000000000000400000000000000040]), validity: [0b______11] } | ++--------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_envelope_agg(to_geometry(line_string)) +evaluation (internal): ++-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| line_string | Column(StringColumn[LINESTRING(0 0, 1 1), LINESTRING(1 1, 2 2), LINESTRING(2 2, 3 3), LINESTRING(3 3, 4 4)]) | +| Output | NullableColumn { column: Geometry([0x010300000001000000050000000000000000000000000000000000000000000000000008400000000000000000000000000000084000000000000008400000000000000000000000000000084000000000000000000000000000000000, 0x01030000000100000005000000000000000000f03f000000000000f03f0000000000001040000000000000f03f00000000000010400000000000001040000000000000f03f0000000000001040000000000000f03f000000000000f03f]), validity: [0b______11] } | ++-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +ast: st_envelope_agg(to_geometry(point_null)) +evaluation (internal): ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| point_null | Column(NullableColumn { column: StringColumn[POINT(1 1), , POINT(3 3), POINT(4 4)], validity: [0b____1101] }) | +| Output | NullableColumn { column: Geometry([0x01030000000100000005000000000000000000f03f000000000000f03f0000000000000840000000000000f03f00000000000008400000000000000840000000000000f03f0000000000000840000000000000f03f000000000000f03f, 0x010100000000000000000010400000000000001040]), validity: [0b______11] } | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + ast: st_collect(to_geometry('point(10 20)')) evaluation (internal): +--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -1823,7 +1903,7 @@ evaluation (internal): | Column | Data | +--------+----------------------------------------------------------------------+ | point | Column(StringColumn[POINT(1 1), POINT(2 2), POINT(3 3), POINT(4 4)]) | -| Output | NullableColumn { column: Geometry([0x]), validity: [0b_______0] } | +| Output | Null { len: 1 } | +--------+----------------------------------------------------------------------+ diff --git a/src/query/functions/tests/it/scalars/geometry.rs b/src/query/functions/tests/it/scalars/geometry.rs index 843209084c716..059aebac24b1e 100644 --- a/src/query/functions/tests/it/scalars/geometry.rs +++ b/src/query/functions/tests/it/scalars/geometry.rs @@ -64,6 +64,12 @@ fn test_geometry() { test_st_disjoint(file); test_st_within(file); test_st_equals(file); + test_st_centroid(file); + test_st_envelope(file); + test_st_union(file); + test_st_intersection(file); + test_st_difference(file); + test_st_symdifference(file); test_st_area(file); test_st_convexhull(file); test_st_hilbert(file); @@ -764,6 +770,136 @@ fn test_st_equals(file: &mut impl Write) { ); } +fn test_st_centroid(file: &mut impl Write) { + run_ast( + file, + "ST_CENTROID(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'))", + &[], + ); + run_ast( + file, + "ST_CENTROID(TO_GEOMETRY('LINESTRING(0 0, 2 0, 2 2)'))", + &[], + ); + run_ast(file, "ST_CENTROID(TO_GEOMETRY('POINT(5 5)'))", &[]); + run_ast( + file, + "ST_CENTROID(TO_GEOMETRY('MULTIPOINT((0 0), (2 0), (0 2), (2 2))'))", + &[], + ); +} + +fn test_st_envelope(file: &mut impl Write) { + run_ast( + file, + "ST_ENVELOPE(TO_GEOMETRY('LINESTRING(0 0, 2 1, -1 3)'))", + &[], + ); + run_ast(file, "ST_ENVELOPE(TO_GEOMETRY('POINT(1 2)'))", &[]); + run_ast( + file, + "ST_ENVELOPE(TO_GEOMETRY('POLYGON((0 0, 3 0, 2 2, 0 3, 0 0))'))", + &[], + ); + run_ast( + file, + "ST_ENVELOPE(TO_GEOMETRY('MULTIPOINT((0 0), (2 1), (-1 3))'))", + &[], + ); +} + +fn test_st_union(file: &mut impl Write) { + run_ast( + file, + "ST_UNION(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)'))", + &[], + ); + run_ast( + file, + "ST_UNION(TO_GEOMETRY('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'), TO_GEOMETRY('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'))", + &[], + ); + run_ast( + file, + "ST_UNION(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(2 2, 3 3)'))", + &[], + ); + run_ast( + file, + "ST_UNION(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))'))", + &[], + ); +} + +fn test_st_intersection(file: &mut impl Write) { + run_ast( + file, + "ST_INTERSECTION(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))'))", + &[], + ); + run_ast( + file, + "ST_INTERSECTION(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(0 0)'))", + &[], + ); + run_ast( + file, + "ST_INTERSECTION(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(0 2, 2 0)'))", + &[], + ); + run_ast( + file, + "ST_INTERSECTION(TO_GEOMETRY('LINESTRING(0 0, 3 3)'), TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'))", + &[], + ); +} + +fn test_st_difference(file: &mut impl Write) { + run_ast( + file, + "ST_DIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 3 0, 3 3, 0 3, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))'))", + &[], + ); + run_ast( + file, + "ST_DIFFERENCE(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)'))", + &[], + ); + run_ast( + file, + "ST_DIFFERENCE(TO_GEOMETRY('LINESTRING(0 0, 3 3)'), TO_GEOMETRY('LINESTRING(1 1, 2 2)'))", + &[], + ); + run_ast( + file, + "ST_DIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((3 3, 4 3, 4 4, 3 4, 3 3))'))", + &[], + ); +} + +fn test_st_symdifference(file: &mut impl Write) { + run_ast( + file, + "ST_SYMDIFFERENCE(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)'))", + &[], + ); + run_ast( + file, + "ST_SYMDIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))'))", + &[], + ); + run_ast( + file, + "ST_SYMDIFFERENCE(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(2 2, 4 4)'))", + &[], + ); + run_ast( + file, + "ST_SYMDIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'))", + &[], + ); +} + fn test_st_area(file: &mut impl Write) { run_ast( file, diff --git a/src/query/functions/tests/it/scalars/testdata/function_list.txt b/src/query/functions/tests/it/scalars/testdata/function_list.txt index 39c603ded466d..687f1e0d9cf88 100644 --- a/src/query/functions/tests/it/scalars/testdata/function_list.txt +++ b/src/query/functions/tests/it/scalars/testdata/function_list.txt @@ -3879,10 +3879,14 @@ Functions overloads: 1 st_aswkt(Geometry NULL) :: String NULL 2 st_aswkt(Geography) :: String 3 st_aswkt(Geography NULL) :: String NULL +0 st_centroid(Geometry) :: Geometry NULL +1 st_centroid(Geometry NULL) :: Geometry NULL 0 st_contains(Geometry, Geometry) :: Boolean 1 st_contains(Geometry NULL, Geometry NULL) :: Boolean NULL 0 st_convexhull(Geometry) :: Geometry 1 st_convexhull(Geometry NULL) :: Geometry NULL +0 st_difference(Geometry, Geometry) :: Geometry NULL +1 st_difference(Geometry NULL, Geometry NULL) :: Geometry NULL 0 st_dimension(Geometry) :: Int32 NULL 1 st_dimension(Geometry NULL) :: Int32 NULL 2 st_dimension(Geography) :: Int32 NULL @@ -3897,6 +3901,8 @@ Functions overloads: 1 st_endpoint(Geometry NULL) :: Geometry NULL 2 st_endpoint(Geography) :: Geography NULL 3 st_endpoint(Geography NULL) :: Geography NULL +0 st_envelope(Geometry) :: Geometry NULL +1 st_envelope(Geometry NULL) :: Geometry NULL 0 st_equals(Geometry, Geometry) :: Boolean 1 st_equals(Geometry NULL, Geometry NULL) :: Boolean NULL 0 st_geogfromgeohash(String) :: Geography @@ -3941,6 +3947,8 @@ Functions overloads: 5 st_hilbert(Geography NULL) :: UInt64 NULL 6 st_hilbert(Geography, Array(Float64)) :: UInt64 NULL 7 st_hilbert(Geography NULL, Array(Float64) NULL) :: UInt64 NULL +0 st_intersection(Geometry, Geometry) :: Geometry NULL +1 st_intersection(Geometry NULL, Geometry NULL) :: Geometry NULL 0 st_intersects(Geometry, Geometry) :: Boolean 1 st_intersects(Geometry NULL, Geometry NULL) :: Boolean NULL 0 st_length(Geometry) :: Float64 @@ -3977,10 +3985,14 @@ Functions overloads: 1 st_startpoint(Geometry NULL) :: Geometry NULL 2 st_startpoint(Geography) :: Geography NULL 3 st_startpoint(Geography NULL) :: Geography NULL +0 st_symdifference(Geometry, Geometry) :: Geometry NULL +1 st_symdifference(Geometry NULL, Geometry NULL) :: Geometry NULL 0 st_transform(Geometry, Int32) :: Geometry 1 st_transform(Geometry NULL, Int32 NULL) :: Geometry NULL 2 st_transform(Geometry, Int32, Int32) :: Geometry 3 st_transform(Geometry NULL, Int32 NULL, Int32 NULL) :: Geometry NULL +0 st_union(Geometry, Geometry) :: Geometry NULL +1 st_union(Geometry NULL, Geometry NULL) :: Geometry NULL 0 st_within(Geometry, Geometry) :: Boolean 1 st_within(Geometry NULL, Geometry NULL) :: Boolean NULL 0 st_x(Geometry) :: Float64 diff --git a/src/query/functions/tests/it/scalars/testdata/geography.txt b/src/query/functions/tests/it/scalars/testdata/geography.txt index e98337172410a..e829fbe3e996b 100644 --- a/src/query/functions/tests/it/scalars/testdata/geography.txt +++ b/src/query/functions/tests/it/scalars/testdata/geography.txt @@ -1,25 +1,25 @@ ast : st_makepoint(40.7127, -74.0059) raw expr : st_makepoint(40.7127, -74.0059) checked expr : st_makepoint(CAST(40.7127_d64(6,4) AS Float64), CAST(-74.0059_d64(6,4) AS Float64)) -optimized expr : "POINT(40.7127 -74.0059)" +optimized expr : "SRID=4326;POINT(40.7127 -74.0059)" output type : Geography output domain : Undefined -output : 'POINT(40.7127 -74.0059)' +output : 'SRID=4326;POINT(40.7127 -74.0059)' ast : st_makepoint(lon, lat) raw expr : st_makepoint(lon::Float64, lat::Float64) checked expr : st_makepoint(lon, lat) evaluation: -+--------+-----------------+------------+--------------------+ -| | lon | lat | Output | -+--------+-----------------+------------+--------------------+ -| Type | Float64 | Float64 | Geography | -| Domain | {-48.5..=78.74} | {-45..=90} | Unknown | -| Row 0 | 12.57 | 0 | 'POINT(12.57 0)' | -| Row 1 | 78.74 | 90 | 'POINT(78.74 90)' | -| Row 2 | -48.5 | -45 | 'POINT(-48.5 -45)' | -+--------+-----------------+------------+--------------------+ ++--------+-----------------+------------+------------------------------+ +| | lon | lat | Output | ++--------+-----------------+------------+------------------------------+ +| Type | Float64 | Float64 | Geography | +| Domain | {-48.5..=78.74} | {-45..=90} | Unknown | +| Row 0 | 12.57 | 0 | 'SRID=4326;POINT(12.57 0)' | +| Row 1 | 78.74 | 90 | 'SRID=4326;POINT(78.74 90)' | +| Row 2 | -48.5 | -45 | 'SRID=4326;POINT(-48.5 -45)' | ++--------+-----------------+------------+------------------------------+ evaluation (internal): +--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Column | Data | @@ -33,37 +33,37 @@ evaluation (internal): ast : st_geographyfromewkt('POINT EMPTY') raw expr : st_geographyfromewkt('POINT EMPTY') checked expr : st_geographyfromwkt("POINT EMPTY") -optimized expr : "MULTIPOINT EMPTY" +optimized expr : "SRID=4326;MULTIPOINT EMPTY" output type : Geography output domain : Undefined -output : 'MULTIPOINT EMPTY' +output : 'SRID=4326;MULTIPOINT EMPTY' ast : st_geographyfromewkt('POINT(1 2)') raw expr : st_geographyfromewkt('POINT(1 2)') checked expr : st_geographyfromwkt("POINT(1 2)") -optimized expr : "POINT(1 2)" +optimized expr : "SRID=4326;POINT(1 2)" output type : Geography output domain : Undefined -output : 'POINT(1 2)' +output : 'SRID=4326;POINT(1 2)' ast : st_geographyfromewkt('SRID=4326;POINT(-122.35 37.55)') raw expr : st_geographyfromewkt('SRID=4326;POINT(-122.35 37.55)') checked expr : st_geographyfromwkt("SRID=4326;POINT(-122.35 37.55)") -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : st_geographyfromewkt('LINESTRING(-124.2 42,-120.01 41.99)') raw expr : st_geographyfromewkt('LINESTRING(-124.2 42,-120.01 41.99)') checked expr : st_geographyfromwkt("LINESTRING(-124.2 42,-120.01 41.99)") -optimized expr : "LINESTRING(-124.2 42,-120.01 41.99)" +optimized expr : "SRID=4326;LINESTRING(-124.2 42,-120.01 41.99)" output type : Geography output domain : Undefined -output : 'LINESTRING(-124.2 42,-120.01 41.99)' +output : 'SRID=4326;LINESTRING(-124.2 42,-120.01 41.99)' ast : st_asgeojson(st_geographyfromewkt('POINT(-122.35 37.55)')) @@ -159,28 +159,28 @@ output : 510053312945727 ast : st_endpoint(st_geographyfromewkt('LINESTRING(0 0,1 1,2 2)')) raw expr : st_endpoint(st_geographyfromewkt('LINESTRING(0 0,1 1,2 2)')) checked expr : st_endpoint(st_geographyfromwkt("LINESTRING(0 0,1 1,2 2)")) -optimized expr : "POINT(2 2)" +optimized expr : "SRID=4326;POINT(2 2)" output type : Geography NULL output domain : Undefined -output : 'POINT(2 2)' +output : 'SRID=4326;POINT(2 2)' ast : st_startpoint(st_geographyfromewkt('LINESTRING(0 0,1 1,2 2)')) raw expr : st_startpoint(st_geographyfromewkt('LINESTRING(0 0,1 1,2 2)')) checked expr : st_startpoint(st_geographyfromwkt("LINESTRING(0 0,1 1,2 2)")) -optimized expr : "POINT(0 0)" +optimized expr : "SRID=4326;POINT(0 0)" output type : Geography NULL output domain : Undefined -output : 'POINT(0 0)' +output : 'SRID=4326;POINT(0 0)' ast : st_pointn(st_geographyfromewkt('LINESTRING(0 0,1 1,2 2)'), 2) raw expr : st_pointn(st_geographyfromewkt('LINESTRING(0 0,1 1,2 2)'), 2) checked expr : st_pointn(st_geographyfromwkt("LINESTRING(0 0,1 1,2 2)"), CAST(2_u8 AS Int32)) -optimized expr : "POINT(1 1)" +optimized expr : "SRID=4326;POINT(1 1)" output type : Geography NULL output domain : Undefined -output : 'POINT(1 1)' +output : 'SRID=4326;POINT(1 1)' ast : st_dimension(st_geographyfromewkt('POINT(-122.35 37.55)')) @@ -213,37 +213,37 @@ output : 2 ast : st_geogfromgeohash(st_geohash(st_makepoint(-122.3061, 37.554162))) raw expr : st_geogfromgeohash(st_geohash(st_makepoint(-122.3061, 37.554162))) checked expr : st_geogfromgeohash(st_geohash(st_makepoint(CAST(-122.3061_d64(7,4) AS Float64), CAST(37.554162_d64(8,6) AS Float64)))) -optimized expr : "POLYGON((-122.30609983205795 37.554161958396435,-122.30609983205795 37.5541621260345,-122.30610016733408 37.5541621260345,-122.30610016733408 37.554161958396435,-122.30609983205795 37.554161958396435))" +optimized expr : "SRID=4326;POLYGON((-122.30609983205795 37.554161958396435,-122.30609983205795 37.5541621260345,-122.30610016733408 37.5541621260345,-122.30610016733408 37.554161958396435,-122.30609983205795 37.554161958396435))" output type : Geography output domain : Undefined -output : 'POLYGON((-122.30609983205795 37.554161958396435,-122.30609983205795 37.5541621260345,-122.30610016733408 37.5541621260345,-122.30610016733408 37.554161958396435,-122.30609983205795 37.554161958396435))' +output : 'SRID=4326;POLYGON((-122.30609983205795 37.554161958396435,-122.30609983205795 37.5541621260345,-122.30610016733408 37.5541621260345,-122.30610016733408 37.554161958396435,-122.30609983205795 37.554161958396435))' ast : st_geogpointfromgeohash(st_geohash(st_makepoint(-122.3061, 37.554162))) raw expr : st_geogpointfromgeohash(st_geohash(st_makepoint(-122.3061, 37.554162))) checked expr : st_geogpointfromgeohash(st_geohash(st_makepoint(CAST(-122.3061_d64(7,4) AS Float64), CAST(37.554162_d64(8,6) AS Float64)))) -optimized expr : "POINT(-122.30609999969602 37.55416204221547)" +optimized expr : "SRID=4326;POINT(-122.30609999969602 37.55416204221547)" output type : Geography output domain : Undefined -output : 'POINT(-122.30609999969602 37.55416204221547)' +output : 'SRID=4326;POINT(-122.30609999969602 37.55416204221547)' ast : st_makepolygon(st_geographyfromewkt('LINESTRING(0 0,0 1,1 1,1 0,0 0)')) raw expr : st_makepolygon(st_geographyfromewkt('LINESTRING(0 0,0 1,1 1,1 0,0 0)')) checked expr : st_makepolygon(st_geographyfromwkt("LINESTRING(0 0,0 1,1 1,1 0,0 0)")) -optimized expr : "POLYGON((0 0,0 1,1 1,1 0,0 0))" +optimized expr : "SRID=4326;POLYGON((0 0,0 1,1 1,1 0,0 0))" output type : Geography output domain : Undefined -output : 'POLYGON((0 0,0 1,1 1,1 0,0 0))' +output : 'SRID=4326;POLYGON((0 0,0 1,1 1,1 0,0 0))' ast : st_makeline(st_makepoint(0, 0), st_makepoint(1, 1)) raw expr : st_makeline(st_makepoint(0, 0), st_makepoint(1, 1)) checked expr : st_makeline(st_makepoint(CAST(0_u8 AS Float64), CAST(0_u8 AS Float64)), st_makepoint(CAST(1_u8 AS Float64), CAST(1_u8 AS Float64))) -optimized expr : "LINESTRING(0 0,1 1)" +optimized expr : "SRID=4326;LINESTRING(0 0,1 1)" output type : Geography output domain : Undefined -output : 'LINESTRING(0 0,1 1)' +output : 'SRID=4326;LINESTRING(0 0,1 1)' ast : st_geohash(st_makepoint(-122.3061, 37.554162)) @@ -267,55 +267,55 @@ output : '9q9j8ue2' ast : st_geographyfromwkb(to_hex(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)')))) raw expr : st_geographyfromwkb(to_hex(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)')))) checked expr : st_geographyfromwkb(to_hex(st_aswkb(st_geographyfromwkt("POINT(-122.35 37.55)")))) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : st_geographyfromwkb(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)'))) raw expr : st_geographyfromwkb(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)'))) checked expr : st_geographyfromwkb(st_aswkb(st_geographyfromwkt("POINT(-122.35 37.55)"))) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : st_geographyfromwkb(to_hex(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)')))) raw expr : st_geographyfromwkb(to_hex(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)')))) checked expr : st_geographyfromwkb(to_hex(st_aswkb(st_geographyfromwkt("POINT(-122.35 37.55)")))) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : st_geographyfromwkb(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)'))) raw expr : st_geographyfromwkb(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)'))) checked expr : st_geographyfromwkb(st_aswkb(st_geographyfromwkt("POINT(-122.35 37.55)"))) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : st_geographyfromwkt('POINT(-122.35 37.55)') raw expr : st_geographyfromwkt('POINT(-122.35 37.55)') checked expr : st_geographyfromwkt("POINT(-122.35 37.55)") -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : st_geographyfromwkt('POINT(-122.35 37.55)') raw expr : st_geographyfromwkt('POINT(-122.35 37.55)') checked expr : st_geographyfromwkt("POINT(-122.35 37.55)") -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : st_x(st_geographyfromewkt('POINT(-122.35 37.55)')) @@ -411,46 +411,46 @@ output : 5 ast : to_string(st_geographyfromewkt('POINT(-122.35 37.55)')) raw expr : to_string(st_geographyfromewkt('POINT(-122.35 37.55)')) checked expr : CAST(st_geographyfromwkt("POINT(-122.35 37.55)") AS String) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : String -output domain : {"POINT(-122.35 37.55)"..="POINT(-122.35 37.55)"} -output : 'POINT(-122.35 37.55)' +output domain : {"SRID=4326;POINT(-122.35 37.55)"..="SRID=4326;POINT(-122.35 37.55)"} +output : 'SRID=4326;POINT(-122.35 37.55)' ast : to_geography('POINT(-122.35 37.55)') raw expr : to_geography('POINT(-122.35 37.55)') checked expr : CAST("POINT(-122.35 37.55)" AS Geography) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : to_geography(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)'))) raw expr : to_geography(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)'))) checked expr : CAST(st_aswkb(st_geographyfromwkt("POINT(-122.35 37.55)")) AS Geography) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : to_geography(parse_json('{"type":"Point","coordinates":[1,2]}')) raw expr : to_geography(parse_json('{"type":"Point","coordinates":[1,2]}')) checked expr : CAST(CAST("{\"type\":\"Point\",\"coordinates\":[1,2]}" AS Variant) AS Geography) -optimized expr : "POINT(1 2)" +optimized expr : "SRID=4326;POINT(1 2)" output type : Geography output domain : Undefined -output : 'POINT(1 2)' +output : 'SRID=4326;POINT(1 2)' ast : try_to_geography('POINT(-122.35 37.55)') raw expr : try_to_geography('POINT(-122.35 37.55)') checked expr : TRY_CAST("POINT(-122.35 37.55)" AS Geography NULL) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography NULL output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : try_to_geography('INVALID') @@ -465,19 +465,19 @@ output : NULL ast : try_to_geography(parse_json('{"type":"Point","coordinates":[1,2]}')) raw expr : try_to_geography(parse_json('{"type":"Point","coordinates":[1,2]}')) checked expr : TRY_CAST(CAST("{\"type\":\"Point\",\"coordinates\":[1,2]}" AS Variant) AS Geography NULL) -optimized expr : "POINT(1 2)" +optimized expr : "SRID=4326;POINT(1 2)" output type : Geography NULL output domain : Undefined -output : 'POINT(1 2)' +output : 'SRID=4326;POINT(1 2)' ast : try_to_geography(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)'))) raw expr : try_to_geography(st_aswkb(st_geographyfromewkt('POINT(-122.35 37.55)'))) checked expr : TRY_CAST(st_aswkb(st_geographyfromwkt("POINT(-122.35 37.55)")) AS Geography NULL) -optimized expr : "POINT(-122.35 37.55)" +optimized expr : "SRID=4326;POINT(-122.35 37.55)" output type : Geography NULL output domain : Undefined -output : 'POINT(-122.35 37.55)' +output : 'SRID=4326;POINT(-122.35 37.55)' ast : ST_HILBERT(TO_GEOGRAPHY('POINT(113.15 23.06)')) diff --git a/src/query/functions/tests/it/scalars/testdata/geometry.txt b/src/query/functions/tests/it/scalars/testdata/geometry.txt index be7ba8567736b..5d80c03c3821c 100644 --- a/src/query/functions/tests/it/scalars/testdata/geometry.txt +++ b/src/query/functions/tests/it/scalars/testdata/geometry.txt @@ -1224,23 +1224,23 @@ evaluation (internal): ast : st_transform(st_geomfromwkt('POINT(4.500212 52.161170)'), 4326, 28992) raw expr : st_transform(st_geomfromwkt('POINT(4.500212 52.161170)'), 4326, 28992) checked expr : st_transform(st_geometryfromwkt("POINT(4.500212 52.161170)"), CAST(4326_u16 AS Int32), CAST(28992_u16 AS Int32)) -optimized expr : "SRID=28992;POINT(94308.670475 464038.168827)" +optimized expr : "SRID=28992;POINT(94286.471882 463928.603089)" output type : Geometry output domain : Undefined -output : 'SRID=28992;POINT(94308.670475 464038.168827)' +output : 'SRID=28992;POINT(94286.471882 463928.603089)' ast : st_transform(st_geomfromwkt(a), b, c) raw expr : st_transform(st_geomfromwkt(a::String), b::Int32, c::Int32) checked expr : st_transform(st_geometryfromwkt(a), b, c) -optimized expr : "SRID=28992;POINT(94308.670475 464038.168827)" +optimized expr : "SRID=28992;POINT(94286.471882 463928.603089)" evaluation: +--------+-------------------------------------------------------------+---------------+-----------------+------------------------------------------------+ | | a | b | c | Output | +--------+-------------------------------------------------------------+---------------+-----------------+------------------------------------------------+ | Type | String | Int32 | Int32 | Geometry | | Domain | {"POINT(4.500212 52.161170)"..="POINT(4.500212 52.161170)"} | {4326..=4326} | {28992..=28992} | Undefined | -| Row 0 | 'POINT(4.500212 52.161170)' | 4326 | 28992 | 'SRID=28992;POINT(94308.670475 464038.168827)' | +| Row 0 | 'POINT(4.500212 52.161170)' | 4326 | 28992 | 'SRID=28992;POINT(94286.471882 463928.603089)' | +--------+-------------------------------------------------------------+---------------+-----------------+------------------------------------------------+ evaluation (internal): +--------+------------------------------------------------------------------+ @@ -1249,7 +1249,7 @@ evaluation (internal): | a | Column(StringColumn[POINT(4.500212 52.161170)]) | | b | Column(Int32([4326])) | | c | Column(Int32([28992])) | -| Output | Geometry([0x0101000020407100005dfe43ba4a06f7402ffce0ac98521c41]) | +| Output | Geometry([0x010100002040710000d923d48ce704f740ae299069e2501c41]) | +--------+------------------------------------------------------------------+ @@ -1370,6 +1370,222 @@ output domain : {TRUE} output : true +ast : ST_CENTROID(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))')) +raw expr : ST_CENTROID(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))')) +checked expr : st_centroid(CAST("POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))" AS Geometry)) +optimized expr : "POINT(1 1)" +output type : Geometry NULL +output domain : Undefined +output : 'POINT(1 1)' + + +ast : ST_CENTROID(TO_GEOMETRY('LINESTRING(0 0, 2 0, 2 2)')) +raw expr : ST_CENTROID(TO_GEOMETRY('LINESTRING(0 0, 2 0, 2 2)')) +checked expr : st_centroid(CAST("LINESTRING(0 0, 2 0, 2 2)" AS Geometry)) +optimized expr : "POINT(1.5 0.5)" +output type : Geometry NULL +output domain : Undefined +output : 'POINT(1.5 0.5)' + + +ast : ST_CENTROID(TO_GEOMETRY('POINT(5 5)')) +raw expr : ST_CENTROID(TO_GEOMETRY('POINT(5 5)')) +checked expr : st_centroid(CAST("POINT(5 5)" AS Geometry)) +optimized expr : "POINT(5 5)" +output type : Geometry NULL +output domain : Undefined +output : 'POINT(5 5)' + + +ast : ST_CENTROID(TO_GEOMETRY('MULTIPOINT((0 0), (2 0), (0 2), (2 2))')) +raw expr : ST_CENTROID(TO_GEOMETRY('MULTIPOINT((0 0), (2 0), (0 2), (2 2))')) +checked expr : st_centroid(CAST("MULTIPOINT((0 0), (2 0), (0 2), (2 2))" AS Geometry)) +optimized expr : "POINT(1 1)" +output type : Geometry NULL +output domain : Undefined +output : 'POINT(1 1)' + + +ast : ST_ENVELOPE(TO_GEOMETRY('LINESTRING(0 0, 2 1, -1 3)')) +raw expr : ST_ENVELOPE(TO_GEOMETRY('LINESTRING(0 0, 2 1, -1 3)')) +checked expr : st_envelope(CAST("LINESTRING(0 0, 2 1, -1 3)" AS Geometry)) +optimized expr : "POLYGON((-1 0,2 0,2 3,-1 3,-1 0))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((-1 0,2 0,2 3,-1 3,-1 0))' + + +ast : ST_ENVELOPE(TO_GEOMETRY('POINT(1 2)')) +raw expr : ST_ENVELOPE(TO_GEOMETRY('POINT(1 2)')) +checked expr : st_envelope(CAST("POINT(1 2)" AS Geometry)) +optimized expr : "POLYGON((1 2,1 2,1 2,1 2,1 2))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((1 2,1 2,1 2,1 2,1 2))' + + +ast : ST_ENVELOPE(TO_GEOMETRY('POLYGON((0 0, 3 0, 2 2, 0 3, 0 0))')) +raw expr : ST_ENVELOPE(TO_GEOMETRY('POLYGON((0 0, 3 0, 2 2, 0 3, 0 0))')) +checked expr : st_envelope(CAST("POLYGON((0 0, 3 0, 2 2, 0 3, 0 0))" AS Geometry)) +optimized expr : "POLYGON((0 0,3 0,3 3,0 3,0 0))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((0 0,3 0,3 3,0 3,0 0))' + + +ast : ST_ENVELOPE(TO_GEOMETRY('MULTIPOINT((0 0), (2 1), (-1 3))')) +raw expr : ST_ENVELOPE(TO_GEOMETRY('MULTIPOINT((0 0), (2 1), (-1 3))')) +checked expr : st_envelope(CAST("MULTIPOINT((0 0), (2 1), (-1 3))" AS Geometry)) +optimized expr : "POLYGON((-1 0,2 0,2 3,-1 3,-1 0))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((-1 0,2 0,2 3,-1 3,-1 0))' + + +ast : ST_UNION(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)')) +raw expr : ST_UNION(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)')) +checked expr : st_union(CAST("POINT(0 0)" AS Geometry), CAST("POINT(1 1)" AS Geometry)) +optimized expr : "MULTIPOINT(0 0,1 1)" +output type : Geometry NULL +output domain : Undefined +output : 'MULTIPOINT(0 0,1 1)' + + +ast : ST_UNION(TO_GEOMETRY('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'), TO_GEOMETRY('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')) +raw expr : ST_UNION(TO_GEOMETRY('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'), TO_GEOMETRY('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')) +checked expr : st_union(CAST("POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))" AS Geometry), CAST("POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))" AS Geometry)) +optimized expr : "POLYGON((0 1,0 0,1 0,1 1,0 1))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((0 1,0 0,1 0,1 1,0 1))' + + +ast : ST_UNION(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(2 2, 3 3)')) +raw expr : ST_UNION(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(2 2, 3 3)')) +checked expr : st_union(CAST("LINESTRING(0 0, 2 2)" AS Geometry), CAST("LINESTRING(2 2, 3 3)" AS Geometry)) +optimized expr : "LINESTRING(0 0,3 3)" +output type : Geometry NULL +output domain : Undefined +output : 'LINESTRING(0 0,3 3)' + + +ast : ST_UNION(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))')) +raw expr : ST_UNION(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))')) +checked expr : st_union(CAST("POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))" AS Geometry), CAST("POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))" AS Geometry)) +optimized expr : "POLYGON((0 2,0 0,2 0,2 1,3 1,3 3,1 3,1 2,0 2))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((0 2,0 0,2 0,2 1,3 1,3 3,1 3,1 2,0 2))' + + +ast : ST_INTERSECTION(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))')) +raw expr : ST_INTERSECTION(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))')) +checked expr : st_intersection(CAST("POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))" AS Geometry), CAST("POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))" AS Geometry)) +optimized expr : "POLYGON((1 2,1 1,2 1,2 2,1 2))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((1 2,1 1,2 1,2 2,1 2))' + + +ast : ST_INTERSECTION(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(0 0)')) +raw expr : ST_INTERSECTION(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(0 0)')) +checked expr : st_intersection(CAST("POINT(0 0)" AS Geometry), CAST("POINT(0 0)" AS Geometry)) +optimized expr : "POINT(0 0)" +output type : Geometry NULL +output domain : Undefined +output : 'POINT(0 0)' + + +ast : ST_INTERSECTION(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(0 2, 2 0)')) +raw expr : ST_INTERSECTION(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(0 2, 2 0)')) +checked expr : st_intersection(CAST("LINESTRING(0 0, 2 2)" AS Geometry), CAST("LINESTRING(0 2, 2 0)" AS Geometry)) +optimized expr : "POINT(1 1)" +output type : Geometry NULL +output domain : Undefined +output : 'POINT(1 1)' + + +ast : ST_INTERSECTION(TO_GEOMETRY('LINESTRING(0 0, 3 3)'), TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))')) +raw expr : ST_INTERSECTION(TO_GEOMETRY('LINESTRING(0 0, 3 3)'), TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))')) +checked expr : st_intersection(CAST("LINESTRING(0 0, 3 3)" AS Geometry), CAST("POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))" AS Geometry)) +optimized expr : "LINESTRING(0 0,2 2)" +output type : Geometry NULL +output domain : Undefined +output : 'LINESTRING(0 0,2 2)' + + +ast : ST_DIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 3 0, 3 3, 0 3, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))')) +raw expr : ST_DIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 3 0, 3 3, 0 3, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))')) +checked expr : st_difference(CAST("POLYGON((0 0, 3 0, 3 3, 0 3, 0 0))" AS Geometry), CAST("POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))" AS Geometry)) +optimized expr : "POLYGON((0 3,0 0,3 0,3 3,0 3),(1 1,1 2,2 2,2 1,1 1))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((0 3,0 0,3 0,3 3,0 3),(1 1,1 2,2 2,2 1,1 1))' + + +ast : ST_DIFFERENCE(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)')) +raw expr : ST_DIFFERENCE(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)')) +checked expr : st_difference(CAST("POINT(0 0)" AS Geometry), CAST("POINT(1 1)" AS Geometry)) +optimized expr : "POINT(0 0)" +output type : Geometry NULL +output domain : Undefined +output : 'POINT(0 0)' + + +ast : ST_DIFFERENCE(TO_GEOMETRY('LINESTRING(0 0, 3 3)'), TO_GEOMETRY('LINESTRING(1 1, 2 2)')) +raw expr : ST_DIFFERENCE(TO_GEOMETRY('LINESTRING(0 0, 3 3)'), TO_GEOMETRY('LINESTRING(1 1, 2 2)')) +checked expr : st_difference(CAST("LINESTRING(0 0, 3 3)" AS Geometry), CAST("LINESTRING(1 1, 2 2)" AS Geometry)) +optimized expr : "MULTILINESTRING((0 0,1 1),(2 2,3 3))" +output type : Geometry NULL +output domain : Undefined +output : 'MULTILINESTRING((0 0,1 1),(2 2,3 3))' + + +ast : ST_DIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((3 3, 4 3, 4 4, 3 4, 3 3))')) +raw expr : ST_DIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((3 3, 4 3, 4 4, 3 4, 3 3))')) +checked expr : st_difference(CAST("POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))" AS Geometry), CAST("POLYGON((3 3, 4 3, 4 4, 3 4, 3 3))" AS Geometry)) +optimized expr : "POLYGON((0 2,0 0,2 0,2 2,0 2))" +output type : Geometry NULL +output domain : Undefined +output : 'POLYGON((0 2,0 0,2 0,2 2,0 2))' + + +ast : ST_SYMDIFFERENCE(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)')) +raw expr : ST_SYMDIFFERENCE(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)')) +checked expr : st_symdifference(CAST("POINT(0 0)" AS Geometry), CAST("POINT(1 1)" AS Geometry)) +optimized expr : "MULTIPOINT(0 0,1 1)" +output type : Geometry NULL +output domain : Undefined +output : 'MULTIPOINT(0 0,1 1)' + + +ast : ST_SYMDIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))')) +raw expr : ST_SYMDIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))')) +checked expr : st_symdifference(CAST("POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))" AS Geometry), CAST("POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))" AS Geometry)) +optimized expr : "MULTIPOLYGON(((0 2,0 0,2 0,2 1,1 1,1 2,0 2)),((1 3,1 2,2 2,2 1,3 1,3 3,1 3)))" +output type : Geometry NULL +output domain : Undefined +output : 'MULTIPOLYGON(((0 2,0 0,2 0,2 1,1 1,1 2,0 2)),((1 3,1 2,2 2,2 1,3 1,3 3,1 3)))' + + +ast : ST_SYMDIFFERENCE(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(2 2, 4 4)')) +raw expr : ST_SYMDIFFERENCE(TO_GEOMETRY('LINESTRING(0 0, 2 2)'), TO_GEOMETRY('LINESTRING(2 2, 4 4)')) +checked expr : st_symdifference(CAST("LINESTRING(0 0, 2 2)" AS Geometry), CAST("LINESTRING(2 2, 4 4)" AS Geometry)) +optimized expr : "LINESTRING(0 0,4 4)" +output type : Geometry NULL +output domain : Undefined +output : 'LINESTRING(0 0,4 4)' + + +ast : ST_SYMDIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))')) +raw expr : ST_SYMDIFFERENCE(TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), TO_GEOMETRY('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))')) +checked expr : st_symdifference(CAST("POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))" AS Geometry), CAST("POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))" AS Geometry)) +optimized expr : NULL +output type : Geometry NULL +output domain : {NULL} +output : NULL + + ast : ST_AREA(TO_GEOMETRY('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')) raw expr : ST_AREA(TO_GEOMETRY('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')) checked expr : st_area(CAST("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))" AS Geometry)) diff --git a/tests/nox/noxfile.py b/tests/nox/noxfile.py index f4f3b565dfd16..43d1b9eaed7b9 100644 --- a/tests/nox/noxfile.py +++ b/tests/nox/noxfile.py @@ -3,7 +3,7 @@ import nox -PYTHON_DRIVER = ["0.33.1", "0.33.4"] +PYTHON_DRIVER = ["0.33.7"] @nox.session diff --git a/tests/sqllogictests/suites/query/functions/02_0060_function_geometry.test b/tests/sqllogictests/suites/query/functions/02_0060_function_geometry.test index de9fba77c515f..ae6c56d936e74 100644 --- a/tests/sqllogictests/suites/query/functions/02_0060_function_geometry.test +++ b/tests/sqllogictests/suites/query/functions/02_0060_function_geometry.test @@ -744,6 +744,84 @@ SELECT ST_HILBERT(g) FROM collect_test; 2684354560 2684354560 + +query T +SELECT ST_ASWKT(ST_CENTROID(TO_GEOMETRY('POINT(1 2)'))); +---- +POINT(1 2) + +query T +SELECT ST_ASWKT(ST_CENTROID(TO_GEOMETRY('LINESTRING(0 0, 2 0)'))); +---- +POINT(1 0) + +query T +SELECT ST_ASWKT(ST_ENVELOPE(TO_GEOMETRY('LINESTRING(0 0, 2 3)'))); +---- +POLYGON((0 0,2 0,2 3,0 3,0 0)) + +query T +SELECT ST_ASWKT(ST_UNION(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)'))); +---- +MULTIPOINT(0 0,1 1) + +query T +SELECT ST_ASWKT(ST_INTERSECTION(TO_GEOMETRY('LINESTRING(0 0, 1 1)'), TO_GEOMETRY('LINESTRING(0 0, 1 1)'))); +---- +LINESTRING(0 0,1 1) + +query T +SELECT ST_ASWKT(ST_DIFFERENCE(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)'))); +---- +POINT(0 0) + +query T +SELECT ST_ASWKT(ST_SYMDIFFERENCE(TO_GEOMETRY('POINT(0 0)'), TO_GEOMETRY('POINT(1 1)'))); +---- +MULTIPOINT(0 0,1 1) + +statement ok +CREATE OR REPLACE TABLE geom_union_agg (g GEOMETRY); + +statement ok +INSERT INTO geom_union_agg VALUES ('POINT(0 0)'), ('POINT(1 1)'), ('POINT(2 2)'); + +query T +SELECT ST_ASWKT(ST_UNION_AGG(g)) FROM geom_union_agg; +---- +MULTIPOINT(0 0,1 1,2 2) + +statement ok +DROP TABLE geom_union_agg; + +statement ok +CREATE OR REPLACE TABLE geom_intersection_agg (g GEOMETRY); + +statement ok +INSERT INTO geom_intersection_agg VALUES ('POINT(1 1)'), ('POINT(1 1)'); + +query T +SELECT ST_ASWKT(ST_INTERSECTION_AGG(g)) FROM geom_intersection_agg; +---- +POINT(1 1) + +statement ok +DROP TABLE geom_intersection_agg; + +statement ok +CREATE OR REPLACE TABLE geom_envelope_agg (g GEOMETRY); + +statement ok +INSERT INTO geom_envelope_agg VALUES ('POINT(0 0)'), ('POINT(2 3)'); + +query T +SELECT ST_ASWKT(ST_ENVELOPE_AGG(g)) FROM geom_envelope_agg; +---- +POLYGON((0 0,2 0,2 3,0 3,0 0)) + +statement ok +DROP TABLE geom_envelope_agg; + statement ok DROP TABLE collect_test; diff --git a/tests/sqllogictests/suites/query/functions/02_0076_function_geography.test b/tests/sqllogictests/suites/query/functions/02_0076_function_geography.test index 94350f2a78f25..9e3f9c5ce8890 100644 --- a/tests/sqllogictests/suites/query/functions/02_0076_function_geography.test +++ b/tests/sqllogictests/suites/query/functions/02_0076_function_geography.test @@ -19,7 +19,7 @@ SET geometry_output_format='EWKT' query ? SELECT st_point(60,37); ---- -POINT(60 37) +SRID=4326;POINT(60 37) statement ok DROP TABLE IF EXISTS t1 @@ -33,8 +33,8 @@ INSERT INTO t1 VALUES(1, ST_GeogFromWkt('POINT(38.986635 58.1900303)')), (2, ST_ query I? SELECT a, g FROM t1 ---- -1 POINT(38.986635 58.1900303) -2 POINT(4.500212 -52.16117) +1 SRID=4326;POINT(38.986635 58.1900303) +2 SRID=4326;POINT(4.500212 -52.16117) statement ok SET geometry_output_format='EWKT' @@ -42,7 +42,7 @@ SET geometry_output_format='EWKT' query T SELECT to_string(st_geographyfromwkb('0101000020E6100000000000000000F03F0000000000000040')); ---- -POINT(1 2) +SRID=4326;POINT(1 2) query T SELECT st_asgeojson(st_geogfromwkt('POINT(1 2)'));