ogc_cql2/
queryable.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#![warn(missing_docs)]
4
5//! OGC CQL2 [Queryable][Q] is a token that represents a property of a [Resource][1]
6//! that can be used in a [filter expression][2].
7//!
8//! [1][crate::Resource]
9//! [2][crate::Expression]
10//!
11
12use crate::{
13    MyError,
14    bound::Bound,
15    geom::{G, GTrait},
16    qstring::QString,
17};
18use core::fmt;
19use jiff::{Timestamp, Zoned, civil::Date, tz::TimeZone};
20use std::{cmp::Ordering, mem};
21use tracing::error;
22
23/// [Queryable][Q] type variants.
24#[derive(Debug)]
25pub enum DataType {
26    /// A Unicode UTF-8 string.
27    Str,
28    /// A numeric value including integers and floating points.
29    Num,
30    /// A boolean value.
31    Bool,
32    /// An _Instant_ with a granularity of a second or smaller. Timestamps are
33    /// always in the time zone UTC ("Z").
34    Timestamp,
35    /// An _Instant_ with a granularity of a day. Dates are local without an
36    /// associated time zone.
37    Date,
38    /// A temporal range of 2 _Instants_ each either _fixed_ or _unbounded_.
39    #[allow(dead_code)]
40    Interval,
41    /// A spatial (geometry) value.
42    Geom,
43    /// A collection of homogeneous values.
44    #[allow(dead_code)]
45    List,
46}
47
48/// A Resource queryable property possible concrete value variants.
49#[derive(Clone)]
50pub enum Q {
51    /// Unknown or undefined w/in the current context.
52    Null,
53    /// A known boolean value.
54    Bool(bool),
55    /// A known numeric literal.
56    Num(f64),
57    /// Either a known UTF8 character string literal, or one that when used in
58    /// comparisons, should be used ignoring its case and/or accent(s).
59    Str(QString),
60    /// A known geometry (spatial) instance.
61    Geom(G),
62    /// Either a known temporal instant or an unbounded value.
63    Instant(Bound),
64    /// A temporal interval.
65    Interval(Bound, Bound),
66    /// A list of other [Queryables][Q].
67    List(Vec<Q>),
68}
69
70impl fmt::Debug for Q {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        match self {
73            Self::Null => write!(f, "Null"),
74            Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
75            Self::Num(arg0) => f.debug_tuple("Num").field(arg0).finish(),
76            Self::Str(arg0) => f.debug_tuple("Str").field(arg0).finish(),
77            Self::Geom(x) => write!(f, "Geom({})", x.to_wkt()),
78            Self::Instant(arg0) => f.debug_tuple("Instant").field(arg0).finish(),
79            Self::Interval(arg0, arg1) => {
80                f.debug_tuple("Interval").field(arg0).field(arg1).finish()
81            }
82            Self::List(arg0) => f.debug_tuple("List").field(arg0).finish(),
83        }
84    }
85}
86
87impl PartialEq for Q {
88    fn eq(&self, other: &Self) -> bool {
89        match (self, other) {
90            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
91            (Self::Num(l0), Self::Num(r0)) => l0 == r0,
92            (Self::Str(l0), Self::Str(r0)) => l0 == r0,
93            (Self::Geom(l0), Self::Geom(r0)) => l0 == r0,
94            (Self::Instant(l0), Self::Instant(r0)) => l0 == r0,
95            (Self::Interval(l0, l1), Self::Interval(r0, r1)) => l0 == r0 && l1 == r1,
96            (Self::List(l0), Self::List(r0)) => l0 == r0,
97            _ => mem::discriminant(self) == mem::discriminant(other),
98        }
99    }
100}
101
102impl Eq for Q {}
103
104impl PartialOrd for Q {
105    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
106        match (self, other) {
107            (Q::Null, Q::Null) => Some(Ordering::Equal),
108            (Q::Bool(a), Q::Bool(b)) => a.partial_cmp(b),
109            (Q::Num(a), Q::Num(b)) => a.partial_cmp(b),
110            (Q::Str(a), Q::Str(b)) => a.partial_cmp(b),
111            (Q::Instant(a), Q::Instant(b)) => a.partial_cmp(b),
112            (Q::Interval(a0, a1), Q::Interval(b0, b1)) => match a0.partial_cmp(b0) {
113                Some(Ordering::Equal) => match (a1, b1) {
114                    (Bound::None, Bound::None) => Some(Ordering::Equal),
115                    (Bound::None, _) => Some(Ordering::Greater),
116                    (_, Bound::None) => Some(Ordering::Less),
117                    _ => a1.partial_cmp(b1),
118                },
119                x => x,
120            },
121            (Q::List(a), Q::List(b)) => a.partial_cmp(b),
122            // anything else, incl. geometries are incomparable...
123            _ => None,
124        }
125    }
126}
127
128impl fmt::Display for Q {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        match self {
131            Q::Null => write!(f, "Null"),
132            Q::Bool(x) => write!(f, "{x}"),
133            Q::Num(x) => write!(f, "{x}"),
134            Q::Str(x) => write!(f, "{x}"),
135            Q::Geom(x) => write!(f, "{}", x.to_wkt()),
136            Q::Instant(x) => write!(f, "{x}"),
137            Q::Interval(Bound::None, Bound::None) => write!(f, "[...]"),
138            Q::Interval(Bound::None, y) => write!(f, "[..{y}]"),
139            Q::Interval(x, Bound::None) => write!(f, "[{x}..]"),
140            Q::Interval(x, y) => write!(f, "[{x}..{y}]"),
141            Q::List(x) => write!(f, "{x:?}"),
142        }
143    }
144}
145
146impl Q {
147    /// Create a new instance as a plain literal string from given argument
148    /// **after trimming it**.
149    pub fn new_plain_str(value: &str) -> Self {
150        Self::Str(QString::plain(value.trim()))
151    }
152
153    /// Try creating a new temporal timestamp variant instance from a string of
154    /// the form _fullDate_ followed by "T", followed by _utcTime_.
155    pub fn try_from_timestamp_str(value: &str) -> Result<Self, MyError> {
156        let x = value.parse::<Timestamp>()?;
157        let z = x.to_zoned(TimeZone::UTC);
158        Ok(Q::Instant(Bound::Timestamp(z)))
159    }
160
161    /// Try creating a new temporal timestamp variant instance from a number of
162    /// nanoseconds since the Unix epoch.
163    pub fn try_from_timestamp_ns(value: i128) -> Result<Self, MyError> {
164        let x = Timestamp::from_nanosecond(value)?;
165        let z = x.to_zoned(TimeZone::UTC);
166        Ok(Q::Instant(Bound::Timestamp(z)))
167    }
168
169    /// Try creating a new temporal date variant instance from a _fullDate_
170    /// string.
171    ///
172    /// **IMPORTANT** - CQL2 specs state that dates are to be considered as
173    /// local wrt. time zones. This implementation however always assigns a
174    /// UTC time zone.
175    pub fn try_from_date_str(value: &str) -> Result<Self, MyError> {
176        let x = value.parse::<Date>()?;
177        let z = x.to_zoned(TimeZone::UTC)?;
178        Ok(Q::Instant(Bound::Date(z)))
179    }
180
181    /// Try creating a new temporal date variant instance from a number of
182    /// nanoseconds since the Unix epoch.
183    pub fn try_from_date_ns(value: i128) -> Result<Self, MyError> {
184        let x = Timestamp::from_nanosecond(value)?;
185        let z = x.to_zoned(TimeZone::UTC);
186        Ok(Q::Instant(Bound::Date(z)))
187    }
188
189    /// Try creating a new instance from a Well Known Text encoded geometry.
190    pub fn try_from_wkt(value: &str) -> Result<Self, MyError> {
191        let g = G::try_from(value)?;
192        Ok(Q::Geom(g))
193    }
194
195    /// Try creating a new instance from a Well Known Binary encoded geometry.
196    pub fn try_from_wkb(value: &[u8]) -> Result<Self, MyError> {
197        let g = G::try_from(value)?;
198        Ok(Q::Geom(g))
199    }
200
201    /// Return TRUE if this is `Null`; FALSE otherwise.
202    pub(crate) fn is_null(&self) -> bool {
203        matches!(self, Q::Null)
204    }
205
206    /// Return TRUE if this is a temporal value; FALSE otherwise.
207    pub(crate) fn is_instant(&self) -> bool {
208        matches!(self, Q::Instant(_))
209    }
210
211    /// Return the current value of this if it's a boolean value.
212    pub fn to_bool(&self) -> Result<bool, MyError> {
213        match self {
214            Q::Bool(x) => Ok(*x),
215            _ => Err(MyError::Runtime(format!("{self} is not a boolean").into())),
216        }
217    }
218
219    /// Return the current value of this if it's a [string][QString] value.
220    pub fn to_str(&self) -> Result<QString, MyError> {
221        match self {
222            Q::Str(x) => Ok(x.to_owned()),
223            _ => Err(MyError::Runtime(format!("{self} is not a string").into())),
224        }
225    }
226
227    /// Return the current value of this if it's a number value.
228    pub fn to_num(&self) -> Result<f64, MyError> {
229        match self {
230            Q::Num(x) => Ok(*x),
231            _ => Err(MyError::Runtime(format!("{self} is not a number").into())),
232        }
233    }
234
235    /// Return the current value of this if it's a [Geometry][G] value.
236    pub fn to_geom(&self) -> Result<G, MyError> {
237        match self {
238            Q::Geom(x) => Ok(x.to_owned()),
239            _ => Err(MyError::Runtime(format!("{self} is not a geometry").into())),
240        }
241    }
242
243    /// Return the current value of this if it's a [Bound] value.
244    pub fn to_bound(&self) -> Result<Bound, MyError> {
245        match self {
246            Q::Instant(x) => Ok(x.to_owned()),
247            _ => Err(MyError::Runtime(
248                format!("{self} is not a bounded instant").into(),
249            )),
250        }
251    }
252
253    /// Return the current value of this if it's a _Interval_ value as a pair
254    /// of [Bound]s.
255    pub fn to_interval(&self) -> Result<(Bound, Bound), MyError> {
256        match self {
257            Q::Interval(x, y) => Ok((x.to_owned(), y.to_owned())),
258            _ => Err(MyError::Runtime(
259                format!("{self} is not an interval").into(),
260            )),
261        }
262    }
263
264    /// Return the current value of this if it's a collection.
265    pub fn to_list(&self) -> Result<Vec<Q>, MyError> {
266        match self {
267            Q::List(x) => Ok(x.to_owned()),
268            _ => Err(MyError::Runtime(format!("{self} is not a list").into())),
269        }
270    }
271
272    /// Return TRUE if both arguments are of the same type; FALSE otherwise.
273    pub(crate) fn same_type(this: &Self, that: &Self) -> bool {
274        mem::discriminant(this) == mem::discriminant(that)
275    }
276
277    // Return the optional literal data type of this.
278    pub(crate) fn literal_type(&self) -> Option<DataType> {
279        match self {
280            Q::Bool(_) => Some(DataType::Bool),
281            Q::Num(_) => Some(DataType::Num),
282            Q::Str(_) => Some(DataType::Str),
283            Q::Geom(_) => Some(DataType::Geom),
284            Q::Instant(x) => match x {
285                Bound::None => None,
286                Bound::Date(_) => Some(DataType::Date),
287                Bound::Timestamp(_) => Some(DataType::Timestamp),
288            },
289            // Q::Null | Q::Interval(_, _) | Q::List(qs)
290            _ => None,
291        }
292    }
293
294    pub(crate) fn contained_by(&self, list: Vec<Self>) -> Result<bool, MyError> {
295        if list.is_empty() {
296            return Ok(false);
297        }
298
299        if let Some(z_type) = self.literal_type() {
300            if matches!(z_type, DataType::Bool) {
301                let lhs = self.to_bool()?;
302                let rhs: Result<Vec<bool>, MyError> = list.iter().map(|e| e.to_bool()).collect();
303                let rhs = rhs?;
304                Ok(rhs.contains(&lhs))
305            } else if matches!(z_type, DataType::Num) {
306                let lhs = self.to_num()?;
307                let rhs: Result<Vec<f64>, MyError> = list.iter().map(|e| e.to_num()).collect();
308                let rhs = rhs?;
309                Ok(rhs.contains(&lhs))
310            } else if matches!(z_type, DataType::Str) {
311                let lhs = &self.to_str()?;
312                let rhs: Result<Vec<QString>, MyError> = list.iter().map(|e| e.to_str()).collect();
313                let rhs = rhs?;
314                Ok(rhs.contains(lhs))
315            } else if matches!(z_type, DataType::Date) || matches!(z_type, DataType::Timestamp) {
316                let lhs = self.to_bound()?.as_zoned().unwrap();
317                let rhs: Result<Vec<Zoned>, MyError> = list
318                    .iter()
319                    .map(|e| e.to_bound().and_then(|b| b.to_zoned()))
320                    .collect();
321                let rhs = rhs?;
322                Ok(rhs.contains(&lhs))
323            } else if matches!(z_type, DataType::Geom) {
324                let lhs = self.to_geom()?;
325                let rhs: Result<Vec<G>, MyError> = list.iter().map(|e| e.to_geom()).collect();
326                let rhs = rhs?;
327                Ok(rhs.contains(&lhs))
328            } else {
329                error!("Failed. self = {self:?}; list = {list:?}");
330                Ok(false)
331            }
332        } else {
333            Ok(false)
334        }
335    }
336}
337
338impl From<bool> for Q {
339    fn from(value: bool) -> Self {
340        Q::Bool(value)
341    }
342}
343
344// logic to convert integers, both signed and unsigned to E::Num...
345
346trait TryToF64<T> {
347    fn try_to_f64(self) -> Result<f64, MyError>;
348}
349
350// Implement trait for small integer types that involve safe conversion.
351macro_rules! impl_safe_try_to_f64 {
352    ($($t:ty),*) => {
353        $(
354            impl TryToF64<$t> for $t {
355                fn try_to_f64(self) -> Result<f64, $crate::MyError> {
356                    Ok(self as f64)
357                }
358            }
359        )*
360    };
361}
362
363// rinse + repeat...
364impl_safe_try_to_f64!(u8, u16, u32, i8, i16, i32);
365
366// Implement trait for unsigned integer types that may result in precision
367// loss.
368//
369// Constant values used for ensuring safe conversion of integers to `f64` are
370// based on the fact that in Rust `f64` numbers have a `52` bit long mantissa,
371// which implies that integers with abstract values bit-length greater than
372// `52` will not be accurately cast.
373macro_rules! impl_try_unsigned_to_f64 {
374    ($($t:ty),*) => {
375        $(
376            impl TryToF64<$t> for $t {
377                fn try_to_f64(self) -> Result<f64, $crate::MyError> {
378                    const MAX_LIMIT: $t = (1 << 53) - 1;
379
380                    if self <= MAX_LIMIT {
381                        Ok(self as f64)
382                    } else {
383                        Err(MyError::PrecisionLoss(self.to_string().into()))
384                    }
385                }
386            }
387        )*
388    };
389}
390
391impl_try_unsigned_to_f64!(u64, u128);
392
393// Implement trait for signed integer types that may result in precision loss.
394macro_rules! impl_try_signed_to_f64 {
395    ($($t:ty),*) => {
396        $(
397            impl TryToF64<$t> for $t {
398                fn try_to_f64(self) -> Result<f64, $crate::MyError> {
399                    const MAX_LIMIT: $t = (1 << 53) - 1;
400                    // const MIN_LIMIT: $t = -((1 << 53) - 1);
401                    const MIN_LIMIT: $t = - MAX_LIMIT;
402
403                    if (MIN_LIMIT..=MAX_LIMIT).contains(&self) {
404                        Ok(self as f64)
405                    } else {
406                        Err(MyError::PrecisionLoss(self.to_string().into()))
407                    }
408                }
409            }
410        )*
411    };
412}
413
414impl_try_signed_to_f64!(i64, i128);
415
416// special cases for both usize and isize to cater for platform-specific
417// bit-length of those types.
418
419impl TryToF64<usize> for usize {
420    fn try_to_f64(self) -> Result<f64, MyError> {
421        match usize::BITS {
422            32 => (self as u32).try_to_f64(),
423            _ => (self as u64).try_to_f64(),
424        }
425    }
426}
427
428impl TryToF64<isize> for isize {
429    fn try_to_f64(self) -> Result<f64, MyError> {
430        match isize::BITS {
431            32 => (self as i32).try_to_f64(),
432            _ => (self as i64).try_to_f64(),
433        }
434    }
435}
436
437// generate TryFrom<x> implementetation to Q...
438macro_rules! impl_try_from_int {
439    ($($t:ty),*) => {
440        $(
441            impl TryFrom<$t> for $crate::Q {
442                type Error = MyError;
443                fn try_from(value: $t) -> Result<Self, $crate::MyError> {
444                    let x = value.try_to_f64()?;
445                    Ok(Q::Num(x))
446                }
447            }
448        )*
449    };
450}
451
452impl_try_from_int!(
453    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
454);
455
456impl From<f64> for Q {
457    fn from(value: f64) -> Self {
458        Q::Num(value)
459    }
460}
461
462impl TryFrom<Date> for Q {
463    type Error = MyError;
464
465    fn try_from(value: Date) -> Result<Self, Self::Error> {
466        let z = value.to_zoned(TimeZone::UTC)?;
467        Ok(Q::Instant(Bound::Date(z)))
468    }
469}
470
471impl From<Timestamp> for Q {
472    fn from(value: Timestamp) -> Self {
473        let z = value.to_zoned(TimeZone::UTC);
474        Q::Instant(Bound::Timestamp(z))
475    }
476}
477
478impl From<Bound> for Q {
479    fn from(value: Bound) -> Self {
480        Q::Instant(value)
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use super::*;
487    use rand::Rng;
488
489    #[test]
490    fn test_usize_max() {
491        let x = usize::MAX.try_to_f64();
492        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
493    }
494
495    #[test]
496    fn test_usize() {
497        let x: usize = (1 << 53) - 1;
498        let y1 = x as f64;
499        let y2 = x.try_to_f64().expect("Failed");
500        assert_eq!(y1, y2)
501    }
502
503    #[test]
504    fn test_u128_max() {
505        let x = u128::MAX.try_to_f64();
506        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
507    }
508
509    #[test]
510    fn test_u128() {
511        let x: u128 = (1 << 53) - 1;
512        let y1 = x as f64;
513        let y2 = x.try_to_f64().expect("Failed");
514        assert_eq!(y1, y2)
515    }
516
517    #[test]
518    fn test_u64_max() {
519        let x = u64::MAX.try_to_f64();
520        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
521    }
522
523    #[test]
524    fn test_u64() {
525        let x: u64 = (1 << 53) - 1;
526        let y1 = x as f64;
527        let y2 = x.try_to_f64().expect("Failed");
528        assert_eq!(y1, y2)
529    }
530
531    #[test]
532    fn test_isize_max() {
533        let x = isize::MAX.try_to_f64();
534        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
535    }
536
537    #[test]
538    fn test_isize() {
539        let x1: isize = (1 << 53) - 1;
540        let y1 = x1 as f64;
541        let y2 = x1.try_to_f64().expect("Failed");
542        assert_eq!(y1, y2);
543
544        let x2 = -x1;
545        let y1 = x2 as f64;
546        let y2 = x2.try_to_f64().expect("Failed");
547        assert_eq!(y1, y2)
548    }
549
550    #[test]
551    fn test_isize_min() {
552        let x = isize::MIN.try_to_f64();
553        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
554    }
555
556    #[test]
557    fn test_i128_max() {
558        let x = i128::MAX.try_to_f64();
559        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
560    }
561
562    #[test]
563    fn test_i128() {
564        let x1: i128 = (1 << 53) - 1;
565        let y1 = x1 as f64;
566        let y2 = x1.try_to_f64().expect("Failed");
567        assert_eq!(y1, y2);
568
569        let x2 = -x1;
570        let y1 = x2 as f64;
571        let y2 = x2.try_to_f64().expect("Failed");
572        assert_eq!(y1, y2)
573    }
574
575    #[test]
576    fn test_i128_min() {
577        let x = i128::MIN.try_to_f64();
578        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
579    }
580
581    #[test]
582    fn test_i64_max() {
583        let x = i64::MAX.try_to_f64();
584        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
585    }
586
587    #[test]
588    fn test_i64_min() {
589        let x = i64::MIN.try_to_f64();
590        assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
591    }
592
593    #[test]
594    fn test_i64() {
595        let x1: i64 = (1 << 53) - 1;
596        let y1 = x1 as f64;
597        let y2 = x1.try_to_f64().expect("Failed");
598        assert_eq!(y1, y2);
599
600        let x2 = -x1;
601        let y1 = x2 as f64;
602        let y2 = x2.try_to_f64().expect("Failed");
603        assert_eq!(y1, y2)
604    }
605
606    #[test]
607    fn fuzz_test_i64() {
608        const LIMIT: i64 = (1 << 53) - 1;
609
610        fn random_i64() -> i64 {
611            let mut rng = rand::rng();
612            match rng.random_bool(0.5) {
613                true => LIMIT - rng.random_range(1..=LIMIT.abs()),
614                false => LIMIT + rng.random_range(1..=LIMIT.abs()),
615            }
616        }
617
618        let mut expected = 0;
619        let mut actual = 0;
620        for _ in 0..1000 {
621            let x = random_i64();
622            if !(-LIMIT..=LIMIT).contains(&x) {
623                expected += 1;
624            }
625            // else as f64 is ok...
626            match Q::try_from(x) {
627                Ok(_) => (), // cool
628                Err(MyError::PrecisionLoss(_)) => actual += 1,
629                Err(x) => panic!("Unexpected {x}"),
630            }
631        }
632
633        assert_eq!(expected, actual)
634    }
635
636    #[test]
637    fn fuzz_test_u64() {
638        const LIMIT: u64 = (1 << 53) - 1;
639
640        fn random_u64() -> u64 {
641            let mut rng = rand::rng();
642            match rng.random_bool(0.5) {
643                true => LIMIT.saturating_sub(rng.random_range(1..=LIMIT)),
644                false => LIMIT + rng.random_range(1..=LIMIT),
645            }
646        }
647
648        let mut expected = 0;
649        let mut actual = 0;
650        for _ in 0..1000 {
651            let x = random_u64();
652            if x > LIMIT {
653                expected += 1;
654            }
655            // else as f64 is ok...
656            match Q::try_from(x) {
657                Ok(_) => (), // cool
658                Err(MyError::PrecisionLoss(_)) => actual += 1,
659                Err(x) => panic!("Unexpected {x}"),
660            }
661        }
662
663        assert_eq!(expected, actual)
664    }
665
666    #[test]
667    #[tracing_test::traced_test]
668    fn test_like() {
669        // plain input and pattern.  no wildcards...
670        let input = QString::plain("hello");
671        let pattern = QString::plain("h%o");
672        let r1 = QString::like(&input, &pattern);
673        assert!(r1);
674
675        // case-insensitive input, plain pattern.  multi wildcard...
676        let input = QString::icase("HELLO");
677        let pattern = QString::plain("h%o");
678        let r2 = QString::like(&input, &pattern);
679        assert!(r2);
680        let input = QString::icase("HELLODOLLY");
681        let pattern = QString::plain("h%odo%y");
682        let r2p = QString::like(&input, &pattern);
683        assert!(r2p);
684
685        // plain input, case-insensitive pattern.  single wildcard...
686        let input = QString::plain("hello");
687        let pattern = QString::icase("h__lo");
688        let r3 = QString::like(&input, &pattern);
689        assert!(r3);
690        // multi wildcard...
691        let pattern = QString::icase("h%lo");
692        let r3p = QString::like(&input, &pattern);
693        assert!(r3p);
694
695        // plain input and pattern.  escaped multi wildcard...
696        let input = QString::plain("hello");
697        let pattern = QString::plain("h\\%o");
698        let r4 = QString::like(&input, &pattern);
699        assert!(!r4);
700
701        let input = QString::plain("h%llo");
702        let pattern = QString::plain("h\\%llo");
703        let r5 = QString::like(&input, &pattern);
704        assert!(r5);
705
706        // empty input and multi wildcard pattern should match
707        let input = QString::plain("");
708        let pattern = QString::plain("%");
709        let r6 = QString::like(&input, &pattern);
710        assert!(r6);
711
712        // non-empty input and empty pattern should fail
713        let input = QString::plain("abc");
714        let pattern = QString::plain("");
715        let r7 = QString::like(&input, &pattern);
716        assert!(!r7);
717
718        // w/ unicode... case-insensitive input and no wildcards...
719
720        let input = QString::icase("ß"); // small sharp s
721        let pattern = QString::icase("ẞ"); // capital sharp s
722        let u1 = QString::like(&input, &pattern);
723        assert!(u1);
724
725        let input = QString::icase("Σ");
726        let pattern = QString::plain("σ");
727        let u2 = QString::like(&input, &pattern);
728        assert!(u2);
729
730        // unicase bug?  Turkish dotted i
731        // let input = QString::plain("İ"); // capital dotted I
732        // let pattern = QString::icase("i"); // small dotted i
733        // let u3 = QString::like(&input, &pattern);
734        // assert!(u3);
735
736        // w/ unicode + wildcard...
737
738        let input = QString::plain("こんにちは");
739        let pattern = QString::plain("こ%は");
740        let u4 = QString::like(&input, &pattern);
741        assert!(u4);
742
743        let pattern = QString::icase("こ_にちは");
744        let u5 = QString::like(&input, &pattern);
745        assert!(u5);
746    }
747}