1use 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
30pub 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
57pub 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
102pub 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#[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
128pub 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 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 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 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}