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