Skip to main content

sedona_testing/
compare.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17use std::iter::zip;
18
19use arrow_array::ArrayRef;
20use arrow_schema::DataType;
21use datafusion_common::{
22    cast::{as_binary_array, as_binary_view_array},
23    ScalarValue,
24};
25use datafusion_expr::ColumnarValue;
26use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY};
27
28use crate::create::create_scalar;
29
30/// Assert two [`ColumnarValue`]s are equal
31///
32/// Panics if the values' Scalar/Array status is different or if the content
33/// is not equal. This can be used in place of `assert_eq!()` to generate reasonable
34/// failure messages for geometry values where the default failure message would
35/// otherwise be uninformative.
36///
37/// This is intended to be used with `create_array_value()` functions
38/// for geometry types. It can be used with `create_array!()` and Arrow types as well;
39/// however, `assert_eq!()` is usually sufficient for those cases.
40pub fn assert_value_equal(actual: &ColumnarValue, expected: &ColumnarValue) {
41    match (actual, expected) {
42        (ColumnarValue::Array(actual_array), ColumnarValue::Array(expected_array)) => {
43            assert_array_equal(actual_array, expected_array);
44        }
45        (ColumnarValue::Scalar(actual_scalar), ColumnarValue::Scalar(expected_scalar)) => {
46            assert_scalar_equal(actual_scalar, expected_scalar);
47        }
48        (ColumnarValue::Array(_), ColumnarValue::Scalar(_)) => {
49            panic!("ColumnarValues not equal: actual is Array, expected Scalar");
50        }
51        (ColumnarValue::Scalar(_), ColumnarValue::Array(_)) => {
52            panic!("ColumnarValues not equal: actual is Scalar, expected Array");
53        }
54    }
55}
56
57/// Assert two [`ArrayRef`]s are equal
58///
59/// Panics if the values' length or types are different or if the content is otherwise not
60/// equal. This can be used in place of `assert_eq!()` to generate reasonable
61/// failure messages for geometry arrays where the default failure message would
62/// otherwise be uninformative.
63pub fn assert_array_equal(actual: &ArrayRef, expected: &ArrayRef) {
64    let (actual_sedona, expected_sedona) = assert_type_equal(
65        actual.data_type(),
66        expected.data_type(),
67        "actual Array",
68        "expected Array",
69    );
70
71    if actual.len() != expected.len() {
72        panic!(
73            "Lengths not equal: actual Array has length {}, expected Array has length {}",
74            actual.len(),
75            expected.len()
76        )
77    }
78
79    match (&actual_sedona, &expected_sedona) {
80        (SedonaType::Arrow(_), SedonaType::Arrow(_)) => {
81            assert_eq!(actual, expected)
82        }
83
84        (SedonaType::Wkb(_, _), SedonaType::Wkb(_, _)) => {
85            assert_wkb_sequences_equal(
86                as_binary_array(&actual).unwrap(),
87                as_binary_array(&expected).unwrap(),
88            );
89        }
90        (SedonaType::WkbView(_, _), SedonaType::WkbView(_, _)) => {
91            assert_wkb_sequences_equal(
92                as_binary_view_array(&actual).unwrap(),
93                as_binary_view_array(&expected).unwrap(),
94            );
95        }
96        (_, _) => {
97            unreachable!()
98        }
99    }
100}
101
102/// Assert a [`ScalarValue`] is a WKB_GEOMETRY scalar corresponding to the given WKT
103///
104/// Panics if the values' are not equal, generating reasonable failure messages for geometry
105/// arrays where the default failure message would otherwise be uninformative.
106pub fn assert_scalar_equal_wkb_geometry(actual: &ScalarValue, expected_wkt: Option<&str>) {
107    let expected = create_scalar(expected_wkt, &WKB_GEOMETRY);
108    assert_eq!(actual.data_type(), DataType::Binary);
109    assert_wkb_scalar_equal(actual, &expected, false);
110}
111
112/// Assert a [`ScalarValue`] is a WKB_GEOMETRY scalar corresponding to the given WKT. This function
113/// compares the geometries topologically, so two geometries that are not byte-wise equal but are
114/// topologically equal will be considered equal.
115///
116/// Panics if the values' are not topologically equal, generating reasonable failure messages for geometry
117/// arrays where the default failure message would otherwise be uninformative.
118#[cfg(feature = "geo")]
119pub fn assert_scalar_equal_wkb_geometry_topologically(
120    actual: &ScalarValue,
121    expected_wkt: Option<&str>,
122) {
123    let expected = create_scalar(expected_wkt, &WKB_GEOMETRY);
124    assert_eq!(actual.data_type(), DataType::Binary);
125    assert_wkb_scalar_equal(actual, &expected, true);
126}
127
128/// Assert two [`ScalarValue`]s are equal
129///
130/// Panics if the values' are not equal, generating reasonable failure messages for geometry
131/// arrays where the default failure message would otherwise be uninformative.
132pub fn assert_scalar_equal(actual: &ScalarValue, expected: &ScalarValue) {
133    let (actual_sedona, expected_sedona) = assert_type_equal(
134        &actual.data_type(),
135        &expected.data_type(),
136        "actual ScalarValue",
137        "expected ScalarValue",
138    );
139
140    match (&actual_sedona, &expected_sedona) {
141        (SedonaType::Arrow(_), SedonaType::Arrow(_)) => assert_arrow_scalar_equal(actual, expected),
142        (SedonaType::Wkb(_, _), SedonaType::Wkb(_, _))
143        | (SedonaType::WkbView(_, _), SedonaType::WkbView(_, _)) => {
144            assert_wkb_scalar_equal(actual, expected, false);
145        }
146        (_, _) => unreachable!(),
147    }
148}
149
150fn assert_type_equal(
151    actual: &DataType,
152    expected: &DataType,
153    actual_label: &str,
154    expected_label: &str,
155) -> (SedonaType, SedonaType) {
156    let actual_sedona = SedonaType::Arrow(actual.clone());
157    let expected_sedona = SedonaType::Arrow(expected.clone());
158    if actual_sedona != expected_sedona {
159        panic!(
160            "{actual_label} != {expected_label}:\n{actual_label} has type {actual_sedona:?}, {expected_label} has type {expected_sedona:?}"
161        );
162    }
163
164    (actual_sedona, expected_sedona)
165}
166
167fn assert_arrow_scalar_equal(actual: &ScalarValue, expected: &ScalarValue) {
168    if actual != expected {
169        panic!("Arrow ScalarValues not equal:\nactual is {actual:?}, expected {expected:?}")
170    }
171}
172
173fn assert_wkb_sequences_equal<'a, 'b, TActual, TExpected>(actual: TActual, expected: TExpected)
174where
175    TActual: IntoIterator<Item = Option<&'a [u8]>>,
176    TExpected: IntoIterator<Item = Option<&'b [u8]>>,
177{
178    for (i, (actual_item, expected_item)) in zip(actual, expected).enumerate() {
179        let actual_label = format!("actual Array element #{i}");
180        let expected_label = format!("expected Array element #{i}");
181        assert_wkb_value_equal(
182            actual_item,
183            expected_item,
184            &actual_label,
185            &expected_label,
186            false,
187        );
188    }
189}
190
191fn assert_wkb_scalar_equal(
192    actual: &ScalarValue,
193    expected: &ScalarValue,
194    compare_topologically: bool,
195) {
196    match (actual, expected) {
197        (ScalarValue::Binary(maybe_actual_wkb), ScalarValue::Binary(maybe_expected_wkb))
198        | (
199            ScalarValue::BinaryView(maybe_actual_wkb),
200            ScalarValue::BinaryView(maybe_expected_wkb),
201        ) => {
202            assert_wkb_value_equal(
203                maybe_actual_wkb.as_deref(),
204                maybe_expected_wkb.as_deref(),
205                "actual WKB scalar",
206                "expected WKB scalar",
207                compare_topologically,
208            );
209        }
210        (_, _) => {
211            unreachable!()
212        }
213    }
214}
215
216fn assert_wkb_value_equal(
217    actual: Option<&[u8]>,
218    expected: Option<&[u8]>,
219    actual_label: &str,
220    expected_label: &str,
221    compare_topologically: bool,
222) {
223    match (actual, expected) {
224        (None, None) => {}
225        (None, Some(expected_wkb)) => {
226            panic!(
227                "{actual_label} != {expected_label}:\n{actual_label} is null, {expected_label} is {}",
228                format_wkb(expected_wkb)
229            )
230        }
231        (Some(actual_wkb), None) => {
232            panic!(
233                "{actual_label} != {expected_label}:\n{actual_label} is {}, {expected_label} is null",
234                format_wkb(actual_wkb)
235            )
236        }
237        (Some(actual_wkb), Some(expected_wkb)) => {
238            // Quick test: if the binary of the WKB is the same, they are equal
239            if actual_wkb != expected_wkb {
240                let is_equals = if compare_topologically {
241                    compare_wkb_topologically(expected_wkb, actual_wkb)
242                } else {
243                    false
244                };
245
246                if !is_equals {
247                    let (actual_wkt, expected_wkt) =
248                        (format_wkb(actual_wkb), format_wkb(expected_wkb));
249                    panic!("{actual_label} != {expected_label}\n{actual_label}:\n  {actual_wkt}\n{expected_label}:\n  {expected_wkt}")
250                }
251            }
252        }
253    }
254}
255
256fn compare_wkb_topologically(
257    #[allow(unused)] expected_wkb: &[u8],
258    #[allow(unused)] actual_wkb: &[u8],
259) -> bool {
260    #[cfg(feature = "geo")]
261    {
262        use geo::Relate;
263        use geo_traits::to_geo::ToGeoGeometry;
264        use geo_traits::Dimensions;
265        use geo_traits::GeometryTrait;
266
267        let expected = wkb::reader::read_wkb(expected_wkb);
268        let actual = wkb::reader::read_wkb(actual_wkb);
269        match (expected, actual) {
270            (Ok(expected_geom), Ok(actual_geom)) => {
271                if expected_geom.dim() == Dimensions::Xy && actual_geom.dim() == Dimensions::Xy {
272                    let expected_geom = expected_geom.to_geometry();
273                    let actual_geom = actual_geom.to_geometry();
274                    expected_geom.relate(&actual_geom).is_equal_topo()
275                } else {
276                    // geo crate does not support 3D/4D geometry operations, so we fall back to using the result
277                    // of byte-wise comparison
278                    false
279                }
280            }
281            _ => false,
282        }
283    }
284    #[cfg(not(feature = "geo"))]
285    {
286        panic!("Topological comparison requires the 'geo' feature to be enabled");
287    }
288}
289
290fn format_wkb(value: &[u8]) -> String {
291    if let Ok(geom) = wkb::reader::read_wkb(value) {
292        let mut wkt = String::new();
293        wkt::to_wkt::write_geometry(&mut wkt, &geom).unwrap();
294        wkt
295    } else {
296        format!("Invalid WKB: {value:?}")
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use arrow_array::create_array;
303    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
304
305    use crate::create::{create_array, create_array_value, create_scalar, create_scalar_value};
306
307    use super::*;
308
309    // For lower-level tests
310    const POINT: [u8; 21] = [
311        0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00,
312        0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
313    ];
314
315    #[test]
316    fn values_equal() {
317        assert_value_equal(
318            &create_scalar_value(Some("POINT (0 1)"), &WKB_GEOMETRY),
319            &create_scalar_value(Some("POINT (0 1)"), &WKB_GEOMETRY),
320        );
321        assert_value_equal(
322            &create_array_value(&[Some("POINT (0 1)")], &WKB_GEOMETRY),
323            &create_array_value(&[Some("POINT (0 1)")], &WKB_GEOMETRY),
324        );
325    }
326
327    #[test]
328    #[should_panic(expected = "ColumnarValues not equal: actual is Scalar, expected Array")]
329    fn values_expected_scalar() {
330        assert_value_equal(
331            &create_scalar_value(None, &WKB_GEOMETRY),
332            &create_array_value(&[], &WKB_GEOMETRY),
333        );
334    }
335
336    #[test]
337    #[should_panic(expected = "ColumnarValues not equal: actual is Array, expected Scalar")]
338    fn values_expected_array() {
339        assert_value_equal(
340            &create_array_value(&[], &WKB_GEOMETRY),
341            &create_scalar_value(None, &WKB_GEOMETRY),
342        );
343    }
344
345    #[test]
346    fn arrays_equal() {
347        let arrow: ArrayRef = create_array!(Utf8, [Some("foofy"), None, Some("foofy2")]);
348        let wkbs = [Some("POINT (0 1)"), None, Some("POINT (1 2)")];
349        assert_array_equal(&arrow, &arrow);
350
351        assert_array_equal(
352            &create_array(&wkbs, &WKB_GEOMETRY),
353            &create_array(&wkbs, &WKB_GEOMETRY),
354        );
355
356        assert_array_equal(
357            &create_array(&wkbs, &WKB_VIEW_GEOMETRY),
358            &create_array(&wkbs, &WKB_VIEW_GEOMETRY),
359        );
360    }
361
362    #[test]
363    #[should_panic(
364        expected = "Lengths not equal: actual Array has length 1, expected Array has length 0"
365    )]
366    fn arrays_different_length() {
367        assert_array_equal(
368            &create_array(&[None], &WKB_GEOMETRY),
369            &create_array(&[], &WKB_GEOMETRY),
370        );
371    }
372
373    #[test]
374    #[should_panic(expected = "assertion `left == right` failed
375  left: StringArray
376[
377  \"foofy\",
378  null,
379]
380 right: StringArray
381[
382  null,
383  \"foofy\",
384]")]
385    fn arrays_arrow_not_equal() {
386        let lhs: ArrayRef = create_array!(Utf8, [Some("foofy"), None]);
387        let rhs: ArrayRef = create_array!(Utf8, [None, Some("foofy")]);
388        assert_array_equal(&lhs, &rhs);
389    }
390
391    #[test]
392    fn scalars_equal() {
393        assert_scalar_equal(
394            &ScalarValue::Utf8(Some("foofy".to_string())),
395            &ScalarValue::Utf8(Some("foofy".to_string())),
396        );
397        assert_scalar_equal(
398            &create_scalar(Some("POINT (0 1)"), &WKB_GEOMETRY),
399            &create_scalar(Some("POINT (0 1)"), &WKB_GEOMETRY),
400        );
401        assert_scalar_equal(
402            &create_scalar(Some("POINT (0 1)"), &WKB_VIEW_GEOMETRY),
403            &create_scalar(Some("POINT (0 1)"), &WKB_VIEW_GEOMETRY),
404        );
405    }
406
407    #[test]
408    #[should_panic(expected = "Arrow ScalarValues not equal:
409actual is Utf8(\"foofy\"), expected Utf8(\"not foofy\")")]
410    fn scalars_unequal_arrow() {
411        assert_scalar_equal(
412            &ScalarValue::Utf8(Some("foofy".to_string())),
413            &ScalarValue::Utf8(Some("not foofy".to_string())),
414        );
415    }
416
417    #[test]
418    fn sequences_equal() {
419        let sequence: Vec<Option<&[u8]>> = vec![Some(&POINT), None, Some(&[])];
420        assert_wkb_sequences_equal(sequence.clone(), sequence);
421    }
422
423    #[test]
424    #[should_panic(expected = "actual Array element #0 != expected Array element #0:
425actual Array element #0 is POINT(1 2), expected Array element #0 is null")]
426    fn sequences_with_difference() {
427        let lhs: Vec<Option<&[u8]>> = vec![Some(&POINT), None, Some(&[])];
428        let rhs: Vec<Option<&[u8]>> = vec![None, Some(&POINT), Some(&[])];
429        assert_wkb_sequences_equal(lhs, rhs);
430    }
431
432    #[test]
433    fn wkb_value_equal() {
434        assert_wkb_value_equal(None, None, "lhs", "rhs", false);
435        assert_wkb_value_equal(Some(&[]), Some(&[]), "lhs", "rhs", false);
436    }
437
438    #[test]
439    #[should_panic(expected = "lhs != rhs:\nlhs is POINT(1 2), rhs is null")]
440    fn wkb_value_expected_null() {
441        assert_wkb_value_equal(Some(&POINT), None, "lhs", "rhs", false);
442    }
443
444    #[test]
445    #[should_panic(expected = "lhs != rhs:\nlhs is null, rhs is POINT(1 2)")]
446    fn wkb_value_actual_null() {
447        assert_wkb_value_equal(None, Some(&POINT), "lhs", "rhs", false);
448    }
449
450    #[test]
451    #[should_panic(expected = "lhs != rhs
452lhs:
453  Invalid WKB: []
454rhs:
455  POINT(1 2)")]
456    fn wkb_value_values_not_equal() {
457        assert_wkb_value_equal(Some(&[]), Some(&POINT), "lhs", "rhs", false);
458    }
459
460    #[cfg(feature = "geo")]
461    #[test]
462    fn wkb_value_equal_topologically() {
463        use crate::create::make_wkb;
464        assert_wkb_value_equal(Some(&POINT), Some(&POINT), "lhs", "rhs", true);
465        let lhs = make_wkb("POLYGON ((0 0, 1 0, 0 1, 0 0))");
466        let rhs = make_wkb("POLYGON ((0 0, 0 1, 1 0, 0 0))");
467        assert_wkb_value_equal(Some(&lhs), Some(&rhs), "lhs", "rhs", true);
468    }
469
470    #[cfg(feature = "geo")]
471    #[test]
472    #[should_panic(expected = "lhs != rhs
473lhs:
474  POLYGON((0 0,1 0,0 1,0 0))
475rhs:
476  POLYGON((0 0,1 0,0 0))")]
477    fn wkb_value_not_equal_topologically() {
478        use crate::create::make_wkb;
479        let lhs = make_wkb("POLYGON ((0 0, 1 0, 0 1, 0 0))");
480        let rhs = make_wkb("POLYGON ((0 0, 1 0, 0 0))");
481        assert_wkb_value_equal(Some(&lhs), Some(&rhs), "lhs", "rhs", true);
482    }
483
484    #[test]
485    fn wkb_formatter() {
486        assert_eq!(format_wkb(&POINT), "POINT(1 2)");
487        assert_eq!(format_wkb(&[]), "Invalid WKB: []");
488    }
489}