Line data Source code
1 : // SPDX-License-Identifier: EUPL-1.2
2 : //! AST → (SQL fragment, binds). The structure of every statement comes from
3 : //! this module; every value a client supplied travels as a bind.
4 :
5 : pub mod geo;
6 : /// The `q=` SQL lowering, shared with a gateway through `antares_ql`.
7 : pub use antares_ql::sql as q;
8 : pub mod qprefilter;
9 : pub mod scope;
10 : pub mod temporal;
11 :
12 : #[cfg(test)]
13 : mod tests {
14 : use super::{geo, q, scope};
15 : use serde_json::json;
16 :
17 : /// One statement carries several fragments, each numbered from the count
18 : /// of binds already collected. Two fragments sharing a `$n` would silently
19 : /// compare a jsonpath against a scope regex, so assert the whole statement
20 : /// references every placeholder exactly once, densely, from 1.
21 : #[test]
22 2 : fn fragments_combined_at_an_offset_never_share_a_placeholder() {
23 : // $1 is the tenant, as the entity query lays it out
24 2 : let mut binds = 1usize;
25 2 : let mut sql = vec!["tenant_id = $1".to_owned()];
26 :
27 2 : let node = antares_ql::parse_q("a==1|b>2").expect("parse");
28 4 : let c = q::compile_q(&node, "entity", binds + 1, &|t| t.to_owned()).expect("q compiles");
29 2 : binds += c.binds.len();
30 2 : sql.push(c.sql);
31 :
32 2 : let c = scope::compile_scope_q("/A;/B,/C", "scopes", binds + 1).expect("scope compiles");
33 2 : binds += c.binds.len();
34 2 : sql.push(c.sql);
35 :
36 2 : let coords = json!([2.29, 48.85]);
37 2 : let c = geo::compile_geo(
38 2 : &geo::GeoSpec {
39 2 : rel: geo::Rel::Near {
40 2 : max: Some(2000.0),
41 2 : min: Some(500.0),
42 2 : },
43 2 : geometry: "Point",
44 2 : coordinates: &coords,
45 2 : geoproperty_iri: "",
46 2 : },
47 : "location",
48 2 : binds + 1,
49 : )
50 2 : .expect("geo compiles");
51 2 : binds += c.geo_binds.len() + c.num_binds.len() + c.cell_binds.len();
52 2 : sql.push(c.sql);
53 :
54 2 : let statement = sql.join(" AND ");
55 2 : let mut seen: Vec<usize> = statement
56 2 : .split('$')
57 2 : .skip(1)
58 80 : .filter_map(|t| {
59 80 : t.chars()
60 80 : .take_while(char::is_ascii_digit)
61 80 : .collect::<String>()
62 80 : .parse()
63 80 : .ok()
64 80 : })
65 2 : .collect();
66 2 : seen.sort_unstable();
67 : // a fragment may reference its OWN bind twice (`near` measures from
68 : // the same query geometry twice); what must never happen is a second
69 : // fragment claiming an index the first already owns, which shows up as
70 : // a gap at the top of the range
71 2 : seen.dedup();
72 2 : assert_eq!(
73 : seen,
74 2 : (1..=binds).collect::<Vec<_>>(),
75 : "placeholders must be dense and unique: {statement}"
76 : );
77 2 : }
78 : }
|