Skip to main content

ogc_cql2/ds/
gpkg.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#![warn(missing_docs)]
4
5//! Artifacts specific to handling geospatial data stored in GeoPackage database
6//! files.
7//!
8
9use crate::{
10    CRS, E, Expression, MyError, QString,
11    config::config,
12    ds::{DataSource, sql::MIN_DATE_SQL},
13    op::Op,
14};
15use sqlx::{AssertSqlSafe, FromRow, Pool, Sqlite, pool::PoolOptions, sqlite::SqliteConnectOptions};
16use std::{cmp::Ordering, str::FromStr};
17use tracing::{debug, info};
18use unicode_normalization::{UnicodeNormalization, char::is_combining_mark};
19
20const GPKG_APPLICATION_ID: i32 = 0x47504B47;
21const FIND_TABLE: &str = "SELECT * FROM gpkg_contents WHERE table_name = $1";
22const FIND_SRS: &str = "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = $1";
23const EPSG_AUTH: &str = "EPSG";
24
25/// Name of a collation that is case-insensitive.
26const CQL2_CI: &str = "CQL2_CI";
27/// Name of a collation that is accent-insensitive.
28const CQL2_AI: &str = "CQL2_AI";
29/// Name of a collation that is both case- and accent-insensitive.
30const CQL2_CAI: &str = "CQL2_CI_AI";
31
32// structure to read back a textual PRAGMA value.
33#[derive(Debug, FromRow)]
34struct Pragma(String);
35
36// Structure to use when SQL query is returning an integer be it a row ID or a
37// numeric PRAGMA value.
38#[derive(Debug, FromRow)]
39struct RowID(i32);
40
41// Partial representation of a `gpkg_spatial_ref_sys` table row.
42#[derive(Debug, FromRow)]
43struct TSpatialRefSys {
44    organization: String,
45    organization_coordsys_id: i32,
46}
47
48// Partial representation of a GeoPackage `gpkg_contents` table row.
49#[allow(dead_code)]
50#[derive(Debug, FromRow)]
51pub(crate) struct TContents {
52    table_name: String,
53    data_type: String,
54    srs_id: Option<i32>,
55}
56
57/// _GeoPackage_ [`DataSource`] binding a `.gpkg` database file + a layer name that
58/// maps rows to _Features_ and [Resources][crate::Resource].
59#[derive(Debug)]
60#[allow(dead_code)]
61pub struct GPkgDataSource {
62    layer: String,
63    pool: Pool<Sqlite>,
64    srid: Option<u32>,
65}
66
67impl DataSource for GPkgDataSource {
68    fn srid(&self) -> Option<u32> {
69        self.srid
70    }
71}
72
73impl GPkgDataSource {
74    /// Constructor.
75    pub async fn from(gpkg_url: &str, layer_name: &str) -> Result<Self, MyError> {
76        // FIXME (rsn) 20251023 - allow configuring the pool from environment
77        // variables.
78
79        // closure for case-insesnitive string comparisons.
80        // let collate_ci = |a: &str, b: &str| QString::cmp_ci(a, b);
81        let collate_ci = |a: &str, b: &str| cmp_ci(a, b);
82
83        // closure for accent-insensitive string comparisons.
84        let collate_ai = |a: &str, b: &str| cmp_ai(a, b);
85
86        // closure for accent- and case-insensitive string comparisons.
87        let collate_aci = |a: &str, b: &str| cmp_aci(a, b);
88
89        // IMPORTANT - this is UNSAFE but i have no control over how to do it
90        // differently since handling GeoPackage data sources is a no go w/o
91        // `spatialite`...
92        let pool_opts = unsafe {
93            SqliteConnectOptions::from_str(gpkg_url)?
94                .extension("mod_spatialite")
95                .collation(CQL2_CI, collate_ci)
96                .collation(CQL2_AI, collate_ai)
97                .collation(CQL2_CAI, collate_aci)
98        };
99
100        let pool = PoolOptions::new().connect_with(pool_opts).await?;
101
102        // GeoPackage SQLite DB files are expected to have 0x47504B47 (or 1196444487)
103        // as their `application_id` in the DB header.
104        let pragma = sqlx::query_as::<_, RowID>("PRAGMA application_id")
105            .fetch_one(&pool)
106            .await?;
107        let application_id = pragma.0;
108        if application_id != GPKG_APPLICATION_ID {
109            return Err(MyError::Runtime("Unexpected application_id".into()));
110        }
111
112        // ensure it passes integerity checks...
113        let pragma = sqlx::query_as::<_, Pragma>("PRAGMA integrity_check")
114            .fetch_one(&pool)
115            .await?;
116        if pragma.0 != "ok" {
117            return Err(MyError::Runtime("Failed integrity_check".into()));
118        }
119
120        // ensure it has no invalid foreign key values...
121        let fk_values: Vec<_> = sqlx::query("PRAGMA foreign_key_check")
122            .fetch_all(&pool)
123            .await?;
124        if !fk_values.is_empty() {
125            return Err(MyError::Runtime("Found invalid FK value(s)".into()));
126        }
127
128        // ensure designated layer/table exists...
129        let layer = sqlx::query_as::<_, TContents>(FIND_TABLE)
130            .bind(layer_name)
131            .fetch_one(&pool)
132            .await?;
133        // we only handle vector-based features, not tiles. check...
134        if layer.data_type != "features" {
135            return Err(MyError::Runtime("Layer is NOT vector-based".into()));
136        }
137
138        // also create a virtual table using `spatialite` _VirtualGPKG_...
139        let sql = format!(
140            r#"CREATE VIRTUAL TABLE IF NOT EXISTS "vgpkg_{0}" USING VirtualGPKG("{0}");"#,
141            layer_name
142        );
143        let safe_sql = AssertSqlSafe(sql);
144        sqlx::query(safe_sql).execute(&pool).await?;
145
146        let srid = match layer.srs_id {
147            // NOTE (rsn) 20251021 - the specs mandate the support for at least
148            // 3 values: `4326`, `-1`, and `0` w/ the last 2 to indicate an
149            // "undefined" cartesian or geographic system respectively.  ensure
150            // we can represent it as a valid CRS but only if it's not an
151            // undefined standard indicator...
152            Some(fk) => match fk {
153                -1 => {
154                    info!("GeoPackage uses the undefined Cartesian SRS");
155                    None
156                }
157                0 => {
158                    info!("GeoPackage uses the undefined geographic SRS");
159                    None
160                }
161                x => {
162                    // NOTE (rsn) 20251023 - while the specs mandate the support
163                    // for a `4326` value, there's no guarantee that this is in
164                    // fact the EPSG:4326 SRS code.  what is guaranteed is that
165                    // it's a foreign key into: `gpkg_spatial_ref_sys`.
166                    let srs = sqlx::query_as::<_, TSpatialRefSys>(FIND_SRS)
167                        .bind(x)
168                        .fetch_one(&pool)
169                        .await?;
170                    // FIXME (rsn) 20251024 - handle other authorities.
171                    let authority = srs.organization;
172                    if !authority.eq_ignore_ascii_case(EPSG_AUTH) {
173                        return Err(MyError::Runtime(
174                            format!("Unexpected ({authority}) Authority").into(),
175                        ));
176                    }
177
178                    let it = srs.organization_coordsys_id;
179                    let epsg_code = format!("{authority}:{x}");
180                    // raise an error if Proj cannot handle it...
181                    let _ = CRS::new(&epsg_code)?;
182                    Some(u32::try_from(it)?)
183                }
184            },
185            None => None,
186        };
187        debug!("srid = {srid:?}");
188
189        Ok(Self {
190            layer: layer_name.to_owned(),
191            pool,
192            srid,
193        })
194    }
195
196    /// Return a reference to the connection pool.
197    pub fn pool(&self) -> &Pool<Sqlite> {
198        &self.pool
199    }
200
201    /// Return name of the virtual table created for querying this
202    /// GeoPackage table.
203    ///
204    /// This name is manufactured by pre-pending "vgpkg_" to the layer name
205    /// in a similar way to how `spatialite` handles _GeoPackage_ files.
206    pub fn vtable(&self) -> String {
207        format!("vgpkg_{}", self.layer)
208    }
209
210    /// Transform given [Expression] to an SQL _WHERE_ clause that can be used
211    /// for selecting a subset of this data source items.
212    pub fn to_sql(&self, exp: &Expression) -> Result<String, MyError> {
213        let mut e = exp.to_inner()?;
214        let it = E::reduce(&mut e)?;
215        let res = self.to_sql_impl(it);
216        debug!("to_sql: {res:?}");
217        res
218    }
219
220    fn to_sql_impl(&self, exp: E) -> Result<String, MyError> {
221        match exp {
222            E::Null => Ok("NULL".to_owned()),
223            E::Unbounded => Ok(MIN_DATE_SQL.to_owned()),
224            E::Bool(true) => Ok("TRUE".to_owned()),
225            E::Bool(false) => Ok("FALSE".to_owned()),
226            E::Num(x) => Ok(x.to_string()),
227            E::Str(x) => qstr_to_sql(x),
228            E::Date(x) => Ok(format!("'{}'", x.date())),
229            E::Timestamp(x) => Ok(format!("'{}'", x.datetime())),
230            E::Spatial(x) => Ok(x.to_sql()?),
231            E::Id(x) => Ok(x.to_owned()),
232            // some work need to be done when handling these options...
233            E::Monadic(op, x) if op.nullable() => {
234                let is_literal = x.is_literal_or_id();
235                let lhs = self.to_sql_impl(*x)?;
236                let z_op = op.to_sql();
237                if is_literal {
238                    Ok(format!("{lhs} {z_op}"))
239                } else {
240                    Ok(format!("({lhs}) {z_op}"))
241                }
242            }
243            E::Monadic(op, x) => match op {
244                Op::Neg | Op::Minus => {
245                    let is_literal = x.is_literal_or_id();
246                    let rhs = self.to_sql_impl(*x)?;
247                    let z_op = op.to_sql();
248                    if is_literal {
249                        Ok(format!("{z_op} {rhs}"))
250                    } else {
251                        Ok(format!("{z_op} ({rhs})"))
252                    }
253                }
254                Op::CaseI => match *x {
255                    E::Monadic(Op::AccentI, y) => {
256                        let rhs = self.to_sql_impl(*y)?;
257                        Ok(format!("{rhs} COLLATE {CQL2_CAI}"))
258                    }
259                    _ => {
260                        let rhs = self.to_sql_impl(*x)?;
261                        Ok(format!("{rhs} COLLATE {CQL2_CI}"))
262                    }
263                },
264                Op::AccentI => match *x {
265                    E::Monadic(Op::CaseI, y) => {
266                        let rhs = self.to_sql_impl(*y)?;
267                        Ok(format!("{rhs} COLLATE {CQL2_CAI}"))
268                    }
269                    _ => {
270                        let rhs = self.to_sql_impl(*x)?;
271                        Ok(format!("{rhs} COLLATE {CQL2_AI}"))
272                    }
273                },
274                x => unreachable!("Unexpected ({x}) monadic operator"),
275            },
276            E::Dyadic(op, a, b)
277                if matches!(op, Op::IsBetween) || matches!(op, Op::IsNotBetween) =>
278            {
279                // RHS of [NOT] BETWEEN is an array of 2 numeric expressions...
280                match *b {
281                    E::Array(rhs) => {
282                        let z_op = op.to_sql();
283                        let lhs = self.to_sql_impl(*a)?;
284                        let lo = self.to_sql_impl(rhs[0].to_owned())?;
285                        let hi = self.to_sql_impl(rhs[1].to_owned())?;
286                        Ok(format!("{lhs} {z_op} {lo} AND {hi}"))
287                    }
288                    _ => unreachable!("Expetced [NOT] BETWEEN's RHS expression to be an array"),
289                }
290            }
291            E::Dyadic(op, a, b) if op.spatial() => match op {
292                Op::SWithin | Op::SOverlaps | Op::STouches => self.reduce_precision(op, *a, *b),
293                _ => {
294                    let lhs = self.to_sql_impl(*a)?;
295                    let rhs = self.to_sql_impl(*b)?;
296                    let z_op = op.to_sql();
297                    Ok(format!("{z_op}({lhs}, {rhs})"))
298                }
299            },
300            E::Dyadic(op, a, b) if op.temporal() => match op {
301                Op::TAfter => self.t_after_sql(*a, *b),
302                Op::TBefore => self.t_before_sql(*a, *b),
303                Op::TDisjoint => self.t_disjoint_sql(*a, *b),
304                Op::TEquals => self.t_equals_sql(*a, *b),
305                Op::TIntersects => self.t_intersects_sql(*a, *b),
306
307                Op::TContains => self.t_contains_sql(*a, *b),
308                Op::TDuring => self.t_during_sql(*a, *b),
309                Op::TFinishedBy => self.t_finished_by_sql(*a, *b),
310                Op::TFinishes => self.t_finishes_sql(*a, *b),
311                Op::TMeets => self.t_meets_sql(*a, *b),
312                Op::TMetBy => self.t_met_by_sql(*a, *b),
313                Op::TOverlappedBy => self.t_overlapped_by_sql(*a, *b),
314                Op::TOverlaps => self.t_overlaps_sql(*a, *b),
315                Op::TStartedBy => self.t_started_by_sql(*a, *b),
316                Op::TStarts => self.t_starts_sql(*a, *b),
317                x => unreachable!("Unexpected ({x:?}) operator"),
318            },
319            E::Dyadic(op, a, b) if op.array() => {
320                let z_op = op.to_sql();
321                let lhs = self.to_sql_impl(*a)?;
322                let rhs = self.to_sql_impl(*b)?;
323                Ok(format!("{lhs} {z_op} {rhs}"))
324            }
325            E::Dyadic(op, a, b) if matches!(op, Op::IsLike) || matches!(op, Op::IsNotLike) => {
326                let a_is_literal = a.is_literal_or_id();
327                let lhs = self.to_sql_impl(*a)?;
328                let rhs = self.to_sql_impl(*b)?;
329                let z_op = op.to_sql();
330                match a_is_literal {
331                    true => Ok(format!("{lhs} {z_op} ({rhs})")),
332                    false => Ok(format!("({lhs}) {z_op} ({rhs})")),
333                }
334            }
335            E::Dyadic(op, a, b) => {
336                let a_is_literal = a.is_literal_or_id();
337                let b_is_literal = b.is_literal_or_id();
338                let lhs = self.to_sql_impl(*a)?;
339                let rhs = self.to_sql_impl(*b)?;
340                let z_op = op.to_sql();
341                match (a_is_literal, b_is_literal) {
342                    (true, true) => Ok(format!("{lhs} {z_op} {rhs}")),
343                    (true, false) => Ok(format!("{lhs} {z_op} ({rhs})")),
344                    (false, true) => Ok(format!("({lhs}) {z_op} {rhs}")),
345                    (false, false) => Ok(format!("({lhs}) {z_op} ({rhs})")),
346                }
347            }
348            E::Function(x) => {
349                let params: Result<Vec<String>, MyError> =
350                    x.params.into_iter().map(|x| self.to_sql_impl(x)).collect();
351                let params_ = params?;
352                Ok(format!("{}({})", x.name, params_.join(", ")))
353            }
354            // NOTE (rsn) 20251105 - SQLite does not accept array elements w/in
355            // square brackets; only parenthesis...
356            E::Array(x) => {
357                let items: Result<Vec<String>, MyError> =
358                    x.into_iter().map(|x| self.to_sql_impl(x)).collect();
359                let items_ = items?;
360                Ok(format!("({})", items_.join(", ")))
361            }
362            x => unreachable!("{x:?} cannot be translated to SQL"),
363        }
364    }
365
366    // NOTE (rsn) 20251120 - Some spatial functions (i.e. `ST_Within`, `ST_Covers`,
367    // and `ST_Touches`) w/ GeoPackage data sources do NOT yield same results to
368    // those obtained when directly using GEOS, when one of the arguments is a table
369    // column.
370    // we work around this by applying `ST_ReducePrecision` *before* calling those
371    // functions. the precision value used in those instances is the same one
372    // configured as the default (see DEFAULT_PRECISION in `config::config()`) which
373    // we already use when outputing WKT strings...
374    fn reduce_precision(&self, op: Op, a: E, b: E) -> Result<String, MyError> {
375        let a_is_id = a.is_id();
376        let b_is_id = b.is_id();
377        let (lhs, rhs) = match (a_is_id, b_is_id) {
378            (true, false) => {
379                let lhs = self.reduce_precision_sql(a)?;
380                let rhs = self.to_sql_impl(b)?;
381                (lhs, rhs)
382            }
383            (false, true) => {
384                let lhs = self.to_sql_impl(a)?;
385                let rhs = self.reduce_precision_sql(b)?;
386                (lhs, rhs)
387            }
388            _ => {
389                let lhs = self.to_sql_impl(a)?;
390                let rhs = self.to_sql_impl(b)?;
391                (lhs, rhs)
392            }
393        };
394        let z_op = op.to_sql();
395        Ok(format!("{z_op}({lhs}, {rhs})"))
396    }
397
398    fn reduce_precision_sql(&self, a: E) -> Result<String, MyError> {
399        let it = format!(
400            "ST_ReducePrecision({}, 1E-{})",
401            self.to_sql_impl(a)?,
402            config().default_precision()
403        );
404        Ok(it)
405    }
406
407    // mixed (instant and interval) arguments...
408    fn t_after_sql(&self, a: E, b: E) -> Result<String, MyError> {
409        let (a_is_interval, b_is_interval, e0, e1, e2, e3) = crate::unfold_expressions!(a, b);
410        match (a_is_interval, b_is_interval) {
411            (false, false) => Ok(format!(
412                "{} > {}",
413                self.to_sql_impl(e0)?,
414                self.to_sql_impl(e2)?
415            )),
416            // w/ the remaining cases, we may need additional xxx IS NOT NULL fragments...
417            (false, true) => {
418                let base = format!("{} > {}", self.to_sql_impl(e0)?, self.to_sql_impl(e3)?);
419                let sql = crate::check_ids!(e2, base);
420                Ok(sql)
421            }
422            (true, false) => {
423                let base = format!("{} > {}", self.to_sql_impl(e0)?, self.to_sql_impl(e2)?);
424                let sql = crate::check_ids!(e1, base);
425                Ok(sql)
426            }
427            (true, true) => {
428                let base = format!("{} > {}", self.to_sql_impl(e0)?, self.to_sql_impl(e3)?);
429                let sql = crate::check_ids!(e1, e2, base);
430                Ok(sql)
431            }
432        }
433    }
434
435    fn t_before_sql(&self, a: E, b: E) -> Result<String, MyError> {
436        let (a_is_interval, b_is_interval, e0, e1, e2, e3) = crate::unfold_expressions!(a, b);
437        match (a_is_interval, b_is_interval) {
438            (false, false) => Ok(format!(
439                "{} < {}",
440                self.to_sql_impl(e0)?,
441                self.to_sql_impl(e2)?
442            )),
443            (false, true) => {
444                let base = format!("{} < {}", self.to_sql_impl(e0)?, self.to_sql_impl(e2)?);
445                let sql = crate::check_ids!(e3, base);
446                Ok(sql)
447            }
448            (true, false) => {
449                let base = format!("{} < {}", self.to_sql_impl(e1)?, self.to_sql_impl(e2)?);
450                let sql = crate::check_ids!(e0, base);
451                Ok(sql)
452            }
453            (true, true) => {
454                let base = format!("{} < {}", self.to_sql_impl(e1)?, self.to_sql_impl(e2)?);
455                let sql = crate::check_ids!(e0, e3, base);
456                Ok(sql)
457            }
458        }
459    }
460
461    fn t_disjoint_sql(&self, a: E, b: E) -> Result<String, MyError> {
462        let (a_is_interval, b_is_interval, e0, e1, e2, e3) = crate::unfold_expressions!(a, b);
463        match (a_is_interval, b_is_interval) {
464            (false, false) => Ok(format!(
465                "{} != {}",
466                self.to_sql_impl(e0)?,
467                self.to_sql_impl(e2)?
468            )),
469            (false, true) => {
470                let e2_ = e2.clone();
471                let e3_ = e3.clone();
472                let s0 = self.to_sql_impl(e0)?;
473                let s2 = self.to_sql_impl(e2)?;
474                let s3 = self.to_sql_impl(e3)?;
475                let base1 = format!("{s0} < {s2}");
476                let sql1 = crate::check_ids!(e3_, base1);
477                let base2 = format!("{s0} > {s3}");
478                let sql2 = crate::check_ids!(e2_, base2);
479                Ok(format!("({sql1}) OR ({sql2})"))
480            }
481            (true, false) => {
482                let e0_ = e0.clone();
483                let e1_ = e1.clone();
484                let s0 = self.to_sql_impl(e0)?;
485                let s1 = self.to_sql_impl(e1)?;
486                let s2 = self.to_sql_impl(e2)?;
487                let base1 = format!("{s1} < {s2}");
488                let sql1 = crate::check_ids!(e0_, base1);
489                let base2 = format!("{s0} > {s2}");
490                let sql2 = crate::check_ids!(e1_, base2);
491                Ok(format!("({sql1}) OR ({sql2})"))
492            }
493            (true, true) => {
494                let e0_ = e0.clone();
495                let e1_ = e1.clone();
496                let e2_ = e2.clone();
497                let e3_ = e3.clone();
498                let s0 = self.to_sql_impl(e0)?;
499                let s1 = self.to_sql_impl(e1)?;
500                let s2 = self.to_sql_impl(e2)?;
501                let s3 = self.to_sql_impl(e3)?;
502                let base1 = format!("{s1} < {s2}");
503                let sql1 = crate::check_ids!(e0_, e3_, base1);
504                let base2 = format!("{s0} > {s3}");
505                let sql2 = crate::check_ids!(e1_, e2_, base2);
506                Ok(format!("({sql1}) OR ({sql2})"))
507            }
508        }
509    }
510
511    fn t_equals_sql(&self, a: E, b: E) -> Result<String, MyError> {
512        let (a_is_interval, b_is_interval, e0, e1, e2, e3) = crate::unfold_expressions!(a, b);
513        match (a_is_interval, b_is_interval) {
514            (false, false) => Ok(format!(
515                "{} = {}",
516                self.to_sql_impl(e0)?,
517                self.to_sql_impl(e2)?
518            )),
519            (false, true) => Ok(format!(
520                "({0} = {1}) AND ({0} = {2})",
521                self.to_sql_impl(e0)?,
522                self.to_sql_impl(e2)?,
523                self.to_sql_impl(e3)?
524            )),
525            (true, false) => Ok(format!(
526                "({0} = {2}) AND ({1} = {2})",
527                self.to_sql_impl(e0)?,
528                self.to_sql_impl(e1)?,
529                self.to_sql_impl(e2)?
530            )),
531            (true, true) => Ok(format!(
532                "({0} = {2}) AND ({1} = {3})",
533                self.to_sql_impl(e0)?,
534                self.to_sql_impl(e1)?,
535                self.to_sql_impl(e2)?,
536                self.to_sql_impl(e3)?
537            )),
538        }
539    }
540
541    fn t_intersects_sql(&self, a: E, b: E) -> Result<String, MyError> {
542        let (a_is_interval, b_is_interval, e0, e1, e2, e3) = crate::unfold_expressions!(a, b);
543        match (a_is_interval, b_is_interval) {
544            (false, false) => Ok(format!(
545                "{} = {}",
546                self.to_sql_impl(e0)?,
547                self.to_sql_impl(e2)?
548            )),
549            (false, true) => Ok(format!(
550                "NOT(({0} < {1}) OR ({0} > {2}))",
551                self.to_sql_impl(e0)?,
552                self.to_sql_impl(e2)?,
553                self.to_sql_impl(e3)?
554            )),
555            (true, false) => Ok(format!(
556                "NOT(({1} < {2}) OR ({0} > {2}))",
557                self.to_sql_impl(e0)?,
558                self.to_sql_impl(e1)?,
559                self.to_sql_impl(e2)?
560            )),
561            (true, true) => Ok(format!(
562                "NOT(({1} < {2}) OR ({0} > {3}))",
563                self.to_sql_impl(e0)?,
564                self.to_sql_impl(e1)?,
565                self.to_sql_impl(e2)?,
566                self.to_sql_impl(e3)?
567            )),
568        }
569    }
570
571    // intervals only...
572    fn t_contains_sql(&self, a: E, b: E) -> Result<String, MyError> {
573        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
574        Ok(format!(
575            "({0} < {2}) AND ({1} > {3})",
576            self.to_sql_impl(e0)?,
577            self.to_sql_impl(e1)?,
578            self.to_sql_impl(e2)?,
579            self.to_sql_impl(e3)?
580        ))
581    }
582
583    fn t_during_sql(&self, a: E, b: E) -> Result<String, MyError> {
584        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
585        Ok(format!(
586            "({0} > {2}) AND ({1} < {3})",
587            self.to_sql_impl(e0)?,
588            self.to_sql_impl(e1)?,
589            self.to_sql_impl(e2)?,
590            self.to_sql_impl(e3)?
591        ))
592    }
593
594    fn t_finished_by_sql(&self, a: E, b: E) -> Result<String, MyError> {
595        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
596        Ok(format!(
597            "({0} < {2}) AND ({1} = {3})",
598            self.to_sql_impl(e0)?,
599            self.to_sql_impl(e1)?,
600            self.to_sql_impl(e2)?,
601            self.to_sql_impl(e3)?
602        ))
603    }
604
605    fn t_finishes_sql(&self, a: E, b: E) -> Result<String, MyError> {
606        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
607        Ok(format!(
608            "({0} > {2}) AND ({1} = {3})",
609            self.to_sql_impl(e0)?,
610            self.to_sql_impl(e1)?,
611            self.to_sql_impl(e2)?,
612            self.to_sql_impl(e3)?
613        ))
614    }
615
616    fn t_meets_sql(&self, a: E, b: E) -> Result<String, MyError> {
617        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
618        let base = format!("{0} = {1}", self.to_sql_impl(e1)?, self.to_sql_impl(e2)?);
619        let sql = crate::check_ids!(e0, e3, base);
620        Ok(sql)
621    }
622
623    fn t_met_by_sql(&self, a: E, b: E) -> Result<String, MyError> {
624        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
625        let base = format!("{0} = {1}", self.to_sql_impl(e0)?, self.to_sql_impl(e3)?);
626        let sql = crate::check_ids!(e1, e2, base);
627        Ok(sql)
628    }
629
630    fn t_overlapped_by_sql(&self, a: E, b: E) -> Result<String, MyError> {
631        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
632        Ok(format!(
633            "({0} > {2}) AND ({0} < {3}) AND ({1} > {3})",
634            self.to_sql_impl(e0)?,
635            self.to_sql_impl(e1)?,
636            self.to_sql_impl(e2)?,
637            self.to_sql_impl(e3)?
638        ))
639    }
640
641    fn t_overlaps_sql(&self, a: E, b: E) -> Result<String, MyError> {
642        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
643        Ok(format!(
644            "({0} < {2}) AND ({1} > {2}) AND ({1} < {3})",
645            self.to_sql_impl(e0)?,
646            self.to_sql_impl(e1)?,
647            self.to_sql_impl(e2)?,
648            self.to_sql_impl(e3)?
649        ))
650    }
651
652    fn t_started_by_sql(&self, a: E, b: E) -> Result<String, MyError> {
653        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
654        Ok(format!(
655            "({0} = {2}) AND ({1} > {3})",
656            self.to_sql_impl(e0)?,
657            self.to_sql_impl(e1)?,
658            self.to_sql_impl(e2)?,
659            self.to_sql_impl(e3)?
660        ))
661    }
662
663    fn t_starts_sql(&self, a: E, b: E) -> Result<String, MyError> {
664        let (e0, e1, e2, e3) = crate::unfold_intervals!(a, b);
665        Ok(format!(
666            "({0} = {2}) AND ({1} < {3})",
667            self.to_sql_impl(e0)?,
668            self.to_sql_impl(e1)?,
669            self.to_sql_impl(e2)?,
670            self.to_sql_impl(e3)?
671        ))
672    }
673}
674
675/// Return the [Ordering] when comparing `a` to `b` ignoring case.
676fn cmp_ci(a: &str, b: &str) -> Ordering {
677    a.to_lowercase().cmp(&b.to_lowercase())
678}
679
680/// Return the [Ordering] when comparing `a` to `b` ignoring accents.
681fn cmp_ai(a: &str, b: &str) -> Ordering {
682    let lhs = a.nfd().filter(|x| !is_combining_mark(*x)).nfc();
683    let rhs = b.nfd().filter(|x| !is_combining_mark(*x)).nfc();
684    lhs.cmp(rhs)
685}
686
687/// Return the [Ordering] when comparing `a` to `b` ignoring both accents
688/// and case.
689fn cmp_aci(a: &str, b: &str) -> Ordering {
690    let x = a.to_lowercase();
691    let y = b.to_lowercase();
692    let lhs = x.nfd().filter(|x| !is_combining_mark(*x)).nfc();
693    let rhs = y.nfd().filter(|x| !is_combining_mark(*x)).nfc();
694    lhs.cmp(rhs)
695}
696
697/// Generate a string that can be used in composing an SQL WHERE clause.
698fn qstr_to_sql(qs: QString) -> Result<String, MyError> {
699    match qs.flags() {
700        0 => Ok(format!("'{}'", qs.inner())),
701        1 => Ok(format!("'{}' COLLATE {CQL2_CI}", qs.inner())),
702        2 => Ok(format!("'{}' COLLATE {CQL2_AI}", qs.inner())),
703        3 => Ok(format!("'{}' COLLATE {CQL2_CAI}", qs.inner())),
704        x => {
705            let msg = format!("String w/ '{x}' flags has NO direct SQL representation");
706            Err(MyError::Runtime(msg.into()))
707        }
708    }
709}
710
711/// Macro to generate a concrete [GPkgDataSource].
712///
713/// Caller must provide the following parameters:
714/// * `$vis`: Visibility specifier of the generated artifacts; e.g. `pub(crate)`.
715/// * `$name`: Prefix of the concrete data source structure name to materialize.
716///   The final name will have a 'GPkg' suffix appended; eg. `Foo` -> `FooGPkg`.
717/// * `$gpkg_url`: Database URL to an accessible _GeoPackage_ DB; e.g.
718///   `sqlite:path/to/a/geo_package.gpkg`
719/// * `$layer`: Name of the table/layer containing the features' data.
720/// * `$feature`: `sqlx` _FromRow_ convertible structure to map database layer
721///   table rows to _Features_.
722#[macro_export]
723macro_rules! gen_gpkg_ds {
724    ($vis:vis, $name:expr, $gpkg_url:expr, $layer:expr, $feature:expr) => {
725        ::paste::paste! {
726            /// Concrete GeoPackage source.
727            $vis struct [<$name GPkg>](GPkgDataSource);
728
729            impl [<$name GPkg>] {
730                /// Constructor.
731                $vis async fn new() -> Result<Self, MyError> {
732                    let gpkp = GPkgDataSource::from($gpkg_url, $layer).await?;
733                    Ok(Self(gpkp))
734                }
735
736                /// Convert a GeoPackage row (aka Feature) to a generic Resource.
737                $vis fn to_resource(r: $feature) -> Result<Resource, Box<dyn Error>> {
738                    let row = $feature::try_from(r)?;
739                    Ok(Resource::try_from(row)?)
740                }
741
742                /// Convenience method. Calls inner's samilarly named method.
743                $vis fn vtable(&self) -> String {
744                    self.0.vtable()
745                }
746
747                /// Return a reference to the inner model data source.
748                $vis fn inner(&self) -> &GPkgDataSource {
749                    &self.0
750                }
751            }
752
753            impl ::core::fmt::Display for [<$name GPkg>] {
754                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
755                    write!(f, "{}GPkg({})", $name, $layer)
756                }
757            }
758
759            #[::async_trait::async_trait]
760            impl StreamableDS for [<$name GPkg>] {
761                type Item = $feature;
762                type Err = MyError;
763
764                async fn fetch(
765                    &self
766                ) -> Result<::futures::stream::BoxStream<'_, Result<$feature, MyError>>, MyError> {
767                    let sql = format!("SELECT * FROM {}", $layer);
768                    let safe_sql = ::sqlx::AssertSqlSafe(sql);
769                    let it = sqlx::query_as::<_, $feature>(safe_sql)
770                        .fetch(self.0.pool())
771                        .map_err(MyError::SQL);
772                    Ok(Box::pin(it))
773                }
774
775                async fn stream(
776                    &self
777                ) -> Result<::futures::stream::BoxStream<'_, Result<Resource, MyError>>, MyError> {
778                    let rows = self.fetch().await?;
779                    let resources = rows
780                        .try_filter_map(|row| async move {
781                            match Resource::try_from(row) {
782                                Ok(x) => Ok(Some(x)),
783                                Err(x) => Err(x),
784                            }
785                        })
786                        .boxed();
787                    Ok(resources)
788                }
789
790                async fn fetch_where(
791                    &self,
792                    exp: &Expression,
793                ) -> Result<::futures::stream::BoxStream<'_, Result<$feature, MyError>>, MyError> {
794                    let where_clause = self.0.to_sql(exp)?;
795                    let sql = format!(r#"SELECT * FROM "{}" WHERE {}"#, self.vtable(), where_clause);
796                    let safe_sql = ::sqlx::AssertSqlSafe(sql);
797                    let it = sqlx::query_as::<_, $feature>(safe_sql)
798                        .fetch(self.0.pool())
799                        .map_err(MyError::SQL);
800                    Ok(Box::pin(it))
801                }
802
803                async fn stream_where(
804                    &self,
805                    exp: &Expression,
806                ) -> Result<::futures::stream::BoxStream<'_, Result<Resource, MyError>>, MyError> {
807                    let rows = self.fetch_where(exp).await?;
808                    let resources = rows
809                        .try_filter_map(|row| async move {
810                            match Resource::try_from(row) {
811                                Ok(x) => Ok(Some(x)),
812                                Err(x) => Err(x),
813                            }
814                        })
815                        .boxed();
816                    Ok(resources)
817                }
818            }
819        }
820    };
821}
822
823#[cfg(test)]
824mod tests {
825    use super::*;
826
827    #[test]
828    fn test_cmp_ci() {
829        let eq = cmp_ci("abc", "ABC");
830        assert_eq!(eq, Ordering::Equal);
831
832        let eq = cmp_ci("ABC", "abc");
833        assert_eq!(eq, Ordering::Equal);
834
835        let eq = cmp_ci("aBc", "AbC");
836        assert_eq!(eq, Ordering::Equal);
837
838        let eq = cmp_ci("abcd", "ABCe");
839        assert_eq!(eq, Ordering::Less);
840
841        let eq = cmp_ci("bcd", "ACz");
842        assert_eq!(eq, Ordering::Greater);
843    }
844
845    #[test]
846    fn test_cmp_ai() {
847        let eq = cmp_ai("ÁBC", "ABC");
848        assert_eq!(eq, Ordering::Equal);
849
850        let eq = cmp_ai("ÁBC", "ABÇ");
851        assert_eq!(eq, Ordering::Equal);
852    }
853
854    #[test]
855    fn test_cmp_aci() {
856        let eq = cmp_aci("ábc", "ABÇ");
857        assert_eq!(eq, Ordering::Equal);
858    }
859}