blob: 780b2ff9695a535c3413ed996edb8aa6fc4edf5f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::iter::zip;
use arrow_array::ArrayRef;
use arrow_schema::DataType;
use datafusion_common::{
cast::{as_binary_array, as_binary_view_array},
ScalarValue,
};
use datafusion_expr::ColumnarValue;
use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY};
use crate::create::create_scalar;
/// Assert two [`ColumnarValue`]s are equal
///
/// Panics if the values' Scalar/Array status is different or if the content
/// is not equal. This can be used in place of `assert_eq!()` to generate reasonable
/// failure messages for geometry values where the default failure message would
/// otherwise be uninformative.
///
/// This is intended to be used with `create_array_value()` functions
/// for geometry types. It can be used with `create_array!()` and Arrow types as well;
/// however, `assert_eq!()` is usually sufficient for those cases.
pub fn assert_value_equal(actual: &ColumnarValue, expected: &ColumnarValue) {
match (actual, expected) {
(ColumnarValue::Array(actual_array), ColumnarValue::Array(expected_array)) => {
assert_array_equal(actual_array, expected_array);
}
(ColumnarValue::Scalar(actual_scalar), ColumnarValue::Scalar(expected_scalar)) => {
assert_scalar_equal(actual_scalar, expected_scalar);
}
(ColumnarValue::Array(_), ColumnarValue::Scalar(_)) => {
panic!("ColumnarValues not equal: actual is Array, expected Scalar");
}
(ColumnarValue::Scalar(_), ColumnarValue::Array(_)) => {
panic!("ColumnarValues not equal: actual is Scalar, expected Array");
}
}
}
/// Assert two [`ArrayRef`]s are equal
///
/// Panics if the values' length or types are different or if the content is otherwise not
/// equal. This can be used in place of `assert_eq!()` to generate reasonable
/// failure messages for geometry arrays where the default failure message would
/// otherwise be uninformative.
pub fn assert_array_equal(actual: &ArrayRef, expected: &ArrayRef) {
let (actual_sedona, expected_sedona) = assert_type_equal(
actual.data_type(),
expected.data_type(),
"actual Array",
"expected Array",
);
if actual.len() != expected.len() {
panic!(
"Lengths not equal: actual Array has length {}, expected Array has length {}",
actual.len(),
expected.len()
)
}
match (&actual_sedona, &expected_sedona) {
(SedonaType::Arrow(_), SedonaType::Arrow(_)) => {
assert_eq!(actual, expected)
}
(SedonaType::Wkb(_, _), SedonaType::Wkb(_, _)) => {
assert_wkb_sequences_equal(
as_binary_array(&actual).unwrap(),
as_binary_array(&expected).unwrap(),
);
}
(SedonaType::WkbView(_, _), SedonaType::WkbView(_, _)) => {
assert_wkb_sequences_equal(
as_binary_view_array(&actual).unwrap(),
as_binary_view_array(&expected).unwrap(),
);
}
(_, _) => {
unreachable!()
}
}
}
/// Assert a [`ScalarValue`] is a WKB_GEOMETRY scalar corresponding to the given WKT
///
/// Panics if the values' are not equal, generating reasonable failure messages for geometry
/// arrays where the default failure message would otherwise be uninformative.
pub fn assert_scalar_equal_wkb_geometry(actual: &ScalarValue, expected_wkt: Option<&str>) {
let expected = create_scalar(expected_wkt, &WKB_GEOMETRY);
assert_eq!(actual.data_type(), DataType::Binary);
assert_wkb_scalar_equal(actual, &expected, false);
}
/// Assert a [`ScalarValue`] is a WKB_GEOMETRY scalar corresponding to the given WKT. This function
/// compares the geometries topologically, so two geometries that are not byte-wise equal but are
/// topologically equal will be considered equal.
///
/// Panics if the values' are not topologically equal, generating reasonable failure messages for geometry
/// arrays where the default failure message would otherwise be uninformative.
#[cfg(feature = "geo")]
pub fn assert_scalar_equal_wkb_geometry_topologically(
actual: &ScalarValue,
expected_wkt: Option<&str>,
) {
let expected = create_scalar(expected_wkt, &WKB_GEOMETRY);
assert_eq!(actual.data_type(), DataType::Binary);
assert_wkb_scalar_equal(actual, &expected, true);
}
/// Assert two [`ScalarValue`]s are equal
///
/// Panics if the values' are not equal, generating reasonable failure messages for geometry
/// arrays where the default failure message would otherwise be uninformative.
pub fn assert_scalar_equal(actual: &ScalarValue, expected: &ScalarValue) {
let (actual_sedona, expected_sedona) = assert_type_equal(
&actual.data_type(),
&expected.data_type(),
"actual ScalarValue",
"expected ScalarValue",
);
match (&actual_sedona, &expected_sedona) {
(SedonaType::Arrow(_), SedonaType::Arrow(_)) => assert_arrow_scalar_equal(actual, expected),
(SedonaType::Wkb(_, _), SedonaType::Wkb(_, _))
| (SedonaType::WkbView(_, _), SedonaType::WkbView(_, _)) => {
assert_wkb_scalar_equal(actual, expected, false);
}
(_, _) => unreachable!(),
}
}
fn assert_type_equal(
actual: &DataType,
expected: &DataType,
actual_label: &str,
expected_label: &str,
) -> (SedonaType, SedonaType) {
let actual_sedona = SedonaType::Arrow(actual.clone());
let expected_sedona = SedonaType::Arrow(expected.clone());
if actual_sedona != expected_sedona {
panic!(
"{actual_label} != {expected_label}:\n{actual_label} has type {actual_sedona:?}, {expected_label} has type {expected_sedona:?}"
);
}
(actual_sedona, expected_sedona)
}
fn assert_arrow_scalar_equal(actual: &ScalarValue, expected: &ScalarValue) {
if actual != expected {
panic!("Arrow ScalarValues not equal:\nactual is {actual:?}, expected {expected:?}")
}
}
fn assert_wkb_sequences_equal<'a, 'b, TActual, TExpected>(actual: TActual, expected: TExpected)
where
TActual: IntoIterator<Item = Option<&'a [u8]>>,
TExpected: IntoIterator<Item = Option<&'b [u8]>>,
{
for (i, (actual_item, expected_item)) in zip(actual, expected).enumerate() {
let actual_label = format!("actual Array element #{i}");
let expected_label = format!("expected Array element #{i}");
assert_wkb_value_equal(
actual_item,
expected_item,
&actual_label,
&expected_label,
false,
);
}
}
fn assert_wkb_scalar_equal(
actual: &ScalarValue,
expected: &ScalarValue,
compare_topologically: bool,
) {
match (actual, expected) {
(ScalarValue::Binary(maybe_actual_wkb), ScalarValue::Binary(maybe_expected_wkb))
| (
ScalarValue::BinaryView(maybe_actual_wkb),
ScalarValue::BinaryView(maybe_expected_wkb),
) => {
assert_wkb_value_equal(
maybe_actual_wkb.as_deref(),
maybe_expected_wkb.as_deref(),
"actual WKB scalar",
"expected WKB scalar",
compare_topologically,
);
}
(_, _) => {
unreachable!()
}
}
}
fn assert_wkb_value_equal(
actual: Option<&[u8]>,
expected: Option<&[u8]>,
actual_label: &str,
expected_label: &str,
compare_topologically: bool,
) {
match (actual, expected) {
(None, None) => {}
(None, Some(expected_wkb)) => {
panic!(
"{actual_label} != {expected_label}:\n{actual_label} is null, {expected_label} is {}",
format_wkb(expected_wkb)
)
}
(Some(actual_wkb), None) => {
panic!(
"{actual_label} != {expected_label}:\n{actual_label} is {}, {expected_label} is null",
format_wkb(actual_wkb)
)
}
(Some(actual_wkb), Some(expected_wkb)) => {
// Quick test: if the binary of the WKB is the same, they are equal
if actual_wkb != expected_wkb {
let is_equals = if compare_topologically {
compare_wkb_topologically(expected_wkb, actual_wkb)
} else {
false
};
if !is_equals {
let (actual_wkt, expected_wkt) =
(format_wkb(actual_wkb), format_wkb(expected_wkb));
panic!("{actual_label} != {expected_label}\n{actual_label}:\n {actual_wkt}\n{expected_label}:\n {expected_wkt}")
}
}
}
}
}
fn compare_wkb_topologically(
#[allow(unused)] expected_wkb: &[u8],
#[allow(unused)] actual_wkb: &[u8],
) -> bool {
#[cfg(feature = "geo")]
{
use geo::Relate;
use geo_traits::to_geo::ToGeoGeometry;
use geo_traits::Dimensions;
use geo_traits::GeometryTrait;
let expected = wkb::reader::read_wkb(expected_wkb);
let actual = wkb::reader::read_wkb(actual_wkb);
match (expected, actual) {
(Ok(expected_geom), Ok(actual_geom)) => {
if expected_geom.dim() == Dimensions::Xy && actual_geom.dim() == Dimensions::Xy {
let expected_geom = expected_geom.to_geometry();
let actual_geom = actual_geom.to_geometry();
expected_geom.relate(&actual_geom).is_equal_topo()
} else {
// geo crate does not support 3D/4D geometry operations, so we fall back to using the result
// of byte-wise comparison
false
}
}
_ => false,
}
}
#[cfg(not(feature = "geo"))]
{
panic!("Topological comparison requires the 'geo' feature to be enabled");
}
}
fn format_wkb(value: &[u8]) -> String {
if let Ok(geom) = wkb::reader::read_wkb(value) {
let mut wkt = String::new();
wkt::to_wkt::write_geometry(&mut wkt, &geom).unwrap();
wkt
} else {
format!("Invalid WKB: {value:?}")
}
}
#[cfg(test)]
mod tests {
use arrow_array::create_array;
use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
use crate::create::{create_array, create_array_value, create_scalar, create_scalar_value};
use super::*;
// For lower-level tests
const POINT: [u8; 21] = [
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
];
#[test]
fn values_equal() {
assert_value_equal(
&create_scalar_value(Some("POINT (0 1)"), &WKB_GEOMETRY),
&create_scalar_value(Some("POINT (0 1)"), &WKB_GEOMETRY),
);
assert_value_equal(
&create_array_value(&[Some("POINT (0 1)")], &WKB_GEOMETRY),
&create_array_value(&[Some("POINT (0 1)")], &WKB_GEOMETRY),
);
}
#[test]
#[should_panic(expected = "ColumnarValues not equal: actual is Scalar, expected Array")]
fn values_expected_scalar() {
assert_value_equal(
&create_scalar_value(None, &WKB_GEOMETRY),
&create_array_value(&[], &WKB_GEOMETRY),
);
}
#[test]
#[should_panic(expected = "ColumnarValues not equal: actual is Array, expected Scalar")]
fn values_expected_array() {
assert_value_equal(
&create_array_value(&[], &WKB_GEOMETRY),
&create_scalar_value(None, &WKB_GEOMETRY),
);
}
#[test]
fn arrays_equal() {
let arrow: ArrayRef = create_array!(Utf8, [Some("foofy"), None, Some("foofy2")]);
let wkbs = [Some("POINT (0 1)"), None, Some("POINT (1 2)")];
assert_array_equal(&arrow, &arrow);
assert_array_equal(
&create_array(&wkbs, &WKB_GEOMETRY),
&create_array(&wkbs, &WKB_GEOMETRY),
);
assert_array_equal(
&create_array(&wkbs, &WKB_VIEW_GEOMETRY),
&create_array(&wkbs, &WKB_VIEW_GEOMETRY),
);
}
#[test]
#[should_panic(
expected = "Lengths not equal: actual Array has length 1, expected Array has length 0"
)]
fn arrays_different_length() {
assert_array_equal(
&create_array(&[None], &WKB_GEOMETRY),
&create_array(&[], &WKB_GEOMETRY),
);
}
#[test]
#[should_panic(expected = "assertion `left == right` failed
left: StringArray
[
\"foofy\",
null,
]
right: StringArray
[
null,
\"foofy\",
]")]
fn arrays_arrow_not_equal() {
let lhs: ArrayRef = create_array!(Utf8, [Some("foofy"), None]);
let rhs: ArrayRef = create_array!(Utf8, [None, Some("foofy")]);
assert_array_equal(&lhs, &rhs);
}
#[test]
fn scalars_equal() {
assert_scalar_equal(
&ScalarValue::Utf8(Some("foofy".to_string())),
&ScalarValue::Utf8(Some("foofy".to_string())),
);
assert_scalar_equal(
&create_scalar(Some("POINT (0 1)"), &WKB_GEOMETRY),
&create_scalar(Some("POINT (0 1)"), &WKB_GEOMETRY),
);
assert_scalar_equal(
&create_scalar(Some("POINT (0 1)"), &WKB_VIEW_GEOMETRY),
&create_scalar(Some("POINT (0 1)"), &WKB_VIEW_GEOMETRY),
);
}
#[test]
#[should_panic(expected = "Arrow ScalarValues not equal:
actual is Utf8(\"foofy\"), expected Utf8(\"not foofy\")")]
fn scalars_unequal_arrow() {
assert_scalar_equal(
&ScalarValue::Utf8(Some("foofy".to_string())),
&ScalarValue::Utf8(Some("not foofy".to_string())),
);
}
#[test]
fn sequences_equal() {
let sequence: Vec<Option<&[u8]>> = vec![Some(&POINT), None, Some(&[])];
assert_wkb_sequences_equal(sequence.clone(), sequence);
}
#[test]
#[should_panic(expected = "actual Array element #0 != expected Array element #0:
actual Array element #0 is POINT(1 2), expected Array element #0 is null")]
fn sequences_with_difference() {
let lhs: Vec<Option<&[u8]>> = vec![Some(&POINT), None, Some(&[])];
let rhs: Vec<Option<&[u8]>> = vec![None, Some(&POINT), Some(&[])];
assert_wkb_sequences_equal(lhs, rhs);
}
#[test]
fn wkb_value_equal() {
assert_wkb_value_equal(None, None, "lhs", "rhs", false);
assert_wkb_value_equal(Some(&[]), Some(&[]), "lhs", "rhs", false);
}
#[test]
#[should_panic(expected = "lhs != rhs:\nlhs is POINT(1 2), rhs is null")]
fn wkb_value_expected_null() {
assert_wkb_value_equal(Some(&POINT), None, "lhs", "rhs", false);
}
#[test]
#[should_panic(expected = "lhs != rhs:\nlhs is null, rhs is POINT(1 2)")]
fn wkb_value_actual_null() {
assert_wkb_value_equal(None, Some(&POINT), "lhs", "rhs", false);
}
#[test]
#[should_panic(expected = "lhs != rhs
lhs:
Invalid WKB: []
rhs:
POINT(1 2)")]
fn wkb_value_values_not_equal() {
assert_wkb_value_equal(Some(&[]), Some(&POINT), "lhs", "rhs", false);
}
#[cfg(feature = "geo")]
#[test]
fn wkb_value_equal_topologically() {
use crate::create::make_wkb;
assert_wkb_value_equal(Some(&POINT), Some(&POINT), "lhs", "rhs", true);
let lhs = make_wkb("POLYGON ((0 0, 1 0, 0 1, 0 0))");
let rhs = make_wkb("POLYGON ((0 0, 0 1, 1 0, 0 0))");
assert_wkb_value_equal(Some(&lhs), Some(&rhs), "lhs", "rhs", true);
}
#[cfg(feature = "geo")]
#[test]
#[should_panic(expected = "lhs != rhs
lhs:
POLYGON((0 0,1 0,0 1,0 0))
rhs:
POLYGON((0 0,1 0,0 0))")]
fn wkb_value_not_equal_topologically() {
use crate::create::make_wkb;
let lhs = make_wkb("POLYGON ((0 0, 1 0, 0 1, 0 0))");
let rhs = make_wkb("POLYGON ((0 0, 1 0, 0 0))");
assert_wkb_value_equal(Some(&lhs), Some(&rhs), "lhs", "rhs", true);
}
#[test]
fn wkb_formatter() {
assert_eq!(format_wkb(&POINT), "POINT(1 2)");
assert_eq!(format_wkb(&[]), "Invalid WKB: []");
}
}