Line data Source code
1 : // SPDX-License-Identifier: EUPL-1.2
2 : //! What a temporal read keeps of an Evolution: the rules per instance and the 5.7.4.4 window conditions.
3 :
4 : use super::*;
5 :
6 : /// Whether `rules` decide an instance on the Entity's state at its time (a
7 : /// rule with a condition), which [`rules_per_instance`] rebuilds from the
8 : /// whole history. A rule of types, Attributes and windows decides each
9 : /// instance alone, so the store may prune to the query's window.
10 2090 : pub(super) fn replays_state(rules: Option<&crate::policy::Filter>) -> bool {
11 2090 : rules.is_some_and(|r| r.rules.iter().any(crate::policy::Rule::is_conditional))
12 2090 : }
13 :
14 : /// Whether the store's per-Attribute cap keeps every instance `rules` would
15 : /// show of the page: with no rules always; under rules only when no rule
16 : /// reads state and every rule window stays open on the page's side: towards
17 : /// now for lastN, and from no later than the query's `start` (its `after`
18 : /// or `between` instant) oldest first, so the rules keep a leading run of
19 : /// what the store kept. A window that closes before now under lastN, or
20 : /// opens after the start oldest first, would have the store keep instances
21 : /// the rules then drop. Rule windows judge observedAt, so
22 : /// a window under a cap ordered by another `timeproperty` (a backfill: new
23 : /// createdAt, old observedAt) keeps the cap in the API too.
24 1502 : pub(super) fn cap_keeps_shown(
25 1502 : rules: Option<&crate::policy::Filter>,
26 1502 : page: &InstancePage,
27 1502 : timeprop: &str,
28 1502 : start: Option<&str>,
29 1502 : ) -> bool {
30 : // an `Ago` bound resolved an hour late: the rules resolve theirs after
31 : // the store answered, by then a little later than now
32 1502 : let late = chrono::Utc::now()
33 1502 : .checked_add_signed(chrono::Duration::hours(1))
34 1502 : .unwrap_or(chrono::DateTime::<chrono::Utc>::MAX_UTC);
35 1502 : rules.is_none_or(|r| {
36 82 : !replays_state(Some(r))
37 84 : && r.rules.iter().all(|rule| {
38 84 : rule.temporal.is_empty()
39 58 : || (timeprop == "observedAt"
40 54 : && rule.temporal.iter().all(|w| {
41 54 : if page.desc {
42 8 : w.to.is_none()
43 : } else {
44 46 : start.is_some_and(|s| w.opens_by(s, late))
45 : }
46 54 : }))
47 84 : })
48 82 : })
49 1502 : }
50 :
51 : /// The Attributes the store may qualify an Entity for a page through under
52 : /// `rules`: `Some(None)` any, `Some(Some(list))` only these, `None`
53 : /// where the rules' verdict on an Entity is not the store's, so it may not
54 : /// page. It is where every rule names Entities by type alone (no condition,
55 : /// id or idPattern) through the same windows, every type the rules name
56 : /// shows the same Attributes (the union of its rules: a list longer than a
57 : /// rule holds is split over several rules of the same types), no
58 : /// type hides Attributes of its own, each requested type is named by a
59 : /// rule, and the windows hold the whole request window (open towards now,
60 : /// opened by its `after` or `between` instant on `observedAt`): an Entity
61 : /// with an instance of a shown Attribute in the request window then shows
62 : /// that instance, and one without shows none. `attrs`, the request's own,
63 : /// narrow what is shown. Without it a view read every Entity of the type
64 : /// for each page: 577 bike stations, past the 30 s statement timeout.
65 48 : pub(super) fn page_attrs(
66 48 : rules: &crate::policy::Filter,
67 48 : types: Option<&[String]>,
68 48 : tq: Option<&TemporalQ>,
69 48 : attrs: Option<&[String]>,
70 48 : ) -> Option<Option<Vec<String>>> {
71 48 : let first = rules.rules.first()?;
72 48 : let start = range_start(tq);
73 48 : let late = chrono::Utc::now()
74 48 : .checked_add_signed(chrono::Duration::hours(1))
75 48 : .unwrap_or(chrono::DateTime::<chrono::Utc>::MAX_UTC);
76 48 : let windows_hold = first.temporal.is_empty()
77 42 : || (tq.is_none_or(|t| t.timeproperty == "observedAt")
78 40 : && start.is_some_and(|s| {
79 40 : first
80 40 : .temporal
81 40 : .iter()
82 40 : .all(|w| w.to.is_none() && w.opens_by(s, late))
83 40 : }));
84 48 : let alike = rules
85 48 : .rules
86 48 : .iter()
87 54 : .all(|r| r.admits_by_type_alone() && r.temporal == first.temporal);
88 : // what an Entity of `t` shows: every Attribute, or the union of its rules' lists
89 62 : let shown = |t: &String| {
90 184 : let of = || rules.rules.iter().filter(|r| r.types.contains(t));
91 62 : let every = of().any(|r| r.every_attribute);
92 62 : let names: std::collections::BTreeSet<&String> = if every {
93 0 : Default::default()
94 : } else {
95 62 : of().flat_map(|r| &r.attributes).collect()
96 : };
97 62 : (every, names)
98 62 : };
99 48 : let mut typed = rules.rules.iter().flat_map(|r| &r.types).map(shown);
100 48 : let (every, names) = typed.next()?;
101 48 : let uniform = typed.all(|other| other == (every, names.clone()));
102 48 : let named = types.is_some_and(|ts| {
103 48 : ts.iter()
104 52 : .all(|t| rules.rules.iter().any(|r| r.types.contains(t)))
105 48 : });
106 48 : if !(windows_hold && alike && uniform && named && rules.omit_by_type.is_empty()) {
107 26 : return None;
108 22 : }
109 22 : Some(match (every, attrs) {
110 0 : (true, None) => None,
111 0 : (true, Some(a)) => Some(a.to_vec()),
112 18 : (false, None) => Some(names.into_iter().cloned().collect()),
113 4 : (false, Some(a)) => Some(a.iter().filter(|x| names.contains(x)).cloned().collect()),
114 : })
115 48 : }
116 :
117 : /// The instant a window read oldest first starts at: `timeAt` of `after`
118 : /// and `between`; `before` and no window start at the beginning of time.
119 1550 : pub(super) fn range_start(tq: Option<&TemporalQ>) -> Option<&str> {
120 1550 : tq.filter(|t| matches!(t.timerel.as_str(), "after" | "between"))
121 1550 : .map(|t| t.time_at.as_str())
122 1550 : }
123 :
124 : /// 5.7.4.4 S2/S3: the values filter and the geoquery are judged on the
125 : /// Attribute instances WITHIN the temporal-query interval, so an
126 : /// out-of-window instance must not satisfy either.
127 : /// the narrowing rules, temporal per instance: an instance of an Attribute
128 : /// is kept only where a rule showing the Attribute held at the instance's
129 : /// time (`observedAt`, else `modifiedAt`). A rule without a condition holds
130 : /// at every time; a conditional one is read on the Entity's state at that
131 : /// time ([`state_at`]) and does not hold where a member its condition reads
132 : /// has no instance yet, or where it reads `scope`, which keeps no history:
133 : /// a state that cannot be rebuilt hides the instance (fail closed). `None`
134 : /// when no rule names one of the Entity's types.
135 88 : pub(crate) fn rules_per_instance(doc: &Value, rules: &crate::policy::Filter) -> Option<Value> {
136 88 : let ctx = crate::repr::policy_context();
137 88 : let types = crate::policy::entity_types(doc);
138 126 : let typed: Vec<&crate::policy::Rule> = rules.rules.iter().filter(|r| r.selects(doc)).collect();
139 88 : if !rules.rules.is_empty() && typed.is_empty() {
140 4 : return None;
141 84 : }
142 84 : let omitted: std::collections::BTreeSet<&str> = types
143 84 : .iter()
144 84 : .filter_map(|t| rules.omit_by_type.get(*t))
145 84 : .flatten()
146 84 : .map(String::as_str)
147 84 : .collect();
148 : // a rule with windows decides per instance too
149 292 : let per_instance = |r: &crate::policy::Rule| r.is_conditional() || !r.temporal.is_empty();
150 84 : let always: std::collections::BTreeSet<&str> = typed
151 84 : .iter()
152 98 : .filter(|r| !per_instance(r))
153 84 : .flat_map(|r| r.attributes.iter().map(String::as_str))
154 84 : .collect();
155 84 : let conditional: Vec<(&crate::policy::Rule, Vec<String>)> = typed
156 84 : .iter()
157 98 : .filter(|r| per_instance(r) && r.scope_q.is_none())
158 84 : .map(|r| (*r, r.reads(ctx)))
159 84 : .collect();
160 : // an unconditioned rule over every Attribute keeps every instance
161 96 : let every = typed.iter().any(|r| !per_instance(r) && r.every_attribute);
162 84 : let now = chrono::Utc::now();
163 84 : let Some(obj) = doc.as_object() else {
164 0 : return Some(doc.clone());
165 : };
166 84 : let mut out = Map::new();
167 508 : for (k, v) in obj {
168 508 : if is_meta(k) {
169 336 : out.insert(k.clone(), v.clone());
170 336 : continue;
171 172 : }
172 172 : if omitted.contains(k.as_str()) {
173 0 : continue;
174 172 : }
175 172 : if rules.rules.is_empty() || every || always.contains(k.as_str()) {
176 24 : out.insert(k.clone(), v.clone());
177 24 : continue;
178 148 : }
179 148 : let deciding: Vec<&(&crate::policy::Rule, Vec<String>)> = conditional
180 148 : .iter()
181 152 : .filter(|(r, _)| r.every_attribute || r.attributes.iter().any(|a| a == k))
182 148 : .collect();
183 148 : let Some(instances) = v.as_array().filter(|_| !deciding.is_empty()) else {
184 68 : continue;
185 : };
186 : // A condition rebuilds the state per instance, O(instances x
187 : // history); sort each Attribute's history once if long histories show
188 : // up under one. A rule without a condition reads no state: `typed`
189 : // already selected the Entity by its types and ids, which is all
190 : // `holds` reads of it, so only its windows decide.
191 80 : let reads_state = deciding.iter().any(|(r, _)| r.is_conditional());
192 80 : let kept: Vec<Value> = instances
193 80 : .iter()
194 2464 : .filter(|inst| {
195 2464 : instant(inst).is_some_and(|t| {
196 2464 : let state = reads_state.then(|| state_at(doc, t));
197 2464 : let observed = inst.get("observedAt").and_then(Value::as_str);
198 2464 : deciding.iter().any(|(r, reads)| {
199 2464 : (!r.is_conditional()
200 148 : || state.as_ref().is_some_and(|state| {
201 148 : reads.iter().all(|a| state.get(a).is_some()) && r.holds(state, ctx)
202 148 : }))
203 2434 : && r.temporal
204 2434 : .iter()
205 2434 : .all(|w| observed.is_some_and(|o| w.admits(o, now)))
206 2464 : })
207 2464 : })
208 2464 : })
209 80 : .cloned()
210 80 : .collect();
211 80 : if !kept.is_empty() {
212 80 : out.insert(k.clone(), Value::Array(kept));
213 80 : }
214 : }
215 84 : Some(Value::Object(out))
216 88 : }
217 :
218 : /// When an instance holds: its `observedAt`, else its `modifiedAt`.
219 4896 : pub(super) fn instant(inst: &Value) -> Option<&str> {
220 4896 : inst.get("observedAt")
221 4896 : .or_else(|| inst.get("modifiedAt"))
222 4896 : .and_then(Value::as_str)
223 4896 : }
224 :
225 : /// The Entity as it stood at `t`, rebuilt from its history: the frame, and
226 : /// for each Attribute the latest instance at or before `t`, if any.
227 148 : pub(super) fn state_at(doc: &Value, t: &str) -> Value {
228 148 : let at = dt_key(t);
229 148 : let Some(obj) = doc.as_object() else {
230 0 : return Value::Null;
231 : };
232 148 : let mut out = Map::new();
233 936 : for (k, v) in obj {
234 936 : if is_meta(k) {
235 592 : if k == "id" || k == "type" {
236 296 : out.insert(k.clone(), v.clone());
237 296 : }
238 592 : continue;
239 344 : }
240 344 : let latest = v
241 344 : .as_array()
242 344 : .into_iter()
243 344 : .flatten()
244 2432 : .filter_map(|inst| instant(inst).map(|i| (dt_key(i), inst)))
245 2432 : .filter(|(i, _)| *i <= at)
246 1038 : .max_by(|a, b| a.0.cmp(&b.0))
247 344 : .map(|(_, inst)| inst.clone());
248 344 : if let Some(inst) = latest {
249 338 : out.insert(k.clone(), Value::Array(vec![inst]));
250 338 : }
251 : }
252 148 : Value::Object(out)
253 148 : }
254 :
255 1026 : pub(super) fn window_conditions_match(
256 1026 : doc: &Value,
257 1026 : tq: Option<&TemporalQ>,
258 1026 : q_ast: Option<&antares_ql::QNode>,
259 1026 : geos: &[&antares_ql::geo::GeoQuery],
260 1026 : ctx: &Context,
261 1026 : ) -> bool {
262 : // 5.7.4.4 S2/S3: the values filter and geoquery are checked against
263 : // the Attribute instances WITHIN the temporal-query interval — an
264 : // out-of-window instance must not satisfy them
265 1026 : let mut eval_doc = doc.clone();
266 1026 : if let (Some(tqv), Some(o)) = (tq, eval_doc.as_object_mut()) {
267 5580 : for (k, v) in o.iter_mut() {
268 5580 : if is_meta(k) {
269 4072 : continue;
270 1508 : }
271 1508 : if let Some(arr) = v.as_array_mut() {
272 1774 : arr.retain(|inst| tqv.instance_matches(inst));
273 0 : }
274 : }
275 4 : }
276 1026 : if let Some(ast) = q_ast {
277 904 : if !antares_ql::eval::eval_q(ast, &eval_doc, ctx, &|_| None) {
278 666 : return false;
279 238 : }
280 122 : }
281 360 : geos.iter().all(|g| g.matches(&eval_doc, ctx))
282 1026 : }
283 :
284 : /// 5.7.4.4 S9: of each Attribute `q` reads, keep the instances that meet
285 : /// the query: an instance meets it when `q` holds on the Entity with that
286 : /// Attribute narrowed to the one instance (the other Attributes in the
287 : /// window), so a term on another Attribute still acts at Entity level.
288 238 : pub(super) fn retain_q_instances(
289 238 : doc: &mut Value,
290 238 : tq: Option<&TemporalQ>,
291 238 : ast: &antares_ql::QNode,
292 238 : ctx: &Context,
293 238 : ) {
294 238 : let mut heads: Vec<String> = ast
295 238 : .attribute_paths()
296 238 : .into_iter()
297 2322 : .filter_map(|p| p.split(['.', '[', '{']).next().map(|h| ctx.expand_key(h)))
298 238 : .collect();
299 238 : heads.sort();
300 238 : heads.dedup();
301 238 : let mut eval_doc = doc.clone();
302 238 : if let (Some(tqv), Some(o)) = (tq, eval_doc.as_object_mut()) {
303 1390 : for (k, v) in o.iter_mut() {
304 1390 : if !is_meta(k) {
305 454 : if let Some(arr) = v.as_array_mut() {
306 568 : arr.retain(|inst| tqv.instance_matches(inst));
307 0 : }
308 936 : }
309 : }
310 0 : }
311 238 : let (Some(obj), Value::Object(eval)) = (doc.as_object_mut(), &mut eval_doc) else {
312 0 : return;
313 : };
314 282 : for head in heads {
315 282 : let Some(arr) = obj.get_mut(&head).and_then(Value::as_array_mut) else {
316 20 : continue;
317 : };
318 262 : let windowed = eval.get(&head).cloned();
319 354 : arr.retain(|inst| {
320 354 : eval.insert(head.clone(), Value::Array(vec![inst.clone()]));
321 : // `eval` is lent back as a document for the one evaluation
322 354 : let probe = Value::Object(std::mem::take(eval));
323 354 : let holds = antares_ql::eval::eval_q(ast, &probe, ctx, &|_| None);
324 354 : if let Value::Object(m) = probe {
325 354 : *eval = m;
326 354 : }
327 354 : holds
328 354 : });
329 262 : if let Some(w) = windowed {
330 262 : eval.insert(head, w);
331 262 : }
332 : }
333 238 : }
334 :
335 : /// 5.7.4.4 S4 — and S7: the federation merge precedes this check, so
336 : /// split and aggregated entities are re-filtered here too. The entity
337 : /// qualifies if one of its scope's 4.18 validity intervals intersects
338 : /// the query window; attribute instances outside every matching interval
339 : /// are excluded (annex C.5.16).
340 372 : pub(super) fn scope_window_retain(
341 372 : doc: &mut Value,
342 372 : sq: &str,
343 372 : tq: Option<&TemporalQ>,
344 372 : timeprop: &str,
345 372 : ) -> bool {
346 : // every comparison on dt_key: raw strings put `…00.500Z` below `…00Z`
347 372 : let iv: Vec<(String, Option<String>)> = scope_match_intervals(doc, sq)
348 372 : .into_iter()
349 372 : .map(|(s, e)| (dt_key(&s), e.as_deref().map(dt_key)))
350 372 : .collect();
351 372 : let (wstart, wend) = match tq {
352 368 : Some(t) => match t.timerel.as_str() {
353 368 : "before" => (None, Some(dt_key(&t.time_at))),
354 368 : "after" => (Some(dt_key(&t.time_at)), None),
355 362 : "between" => (
356 362 : Some(dt_key(&t.time_at)),
357 362 : t.end_time_at.as_deref().map(dt_key),
358 362 : ),
359 0 : _ => (None, None),
360 : },
361 4 : None => (None, None),
362 : };
363 372 : let intersects = iv.iter().any(|(s, e)| {
364 110 : wend.as_deref().is_none_or(|w| s.as_str() < w)
365 100 : && e.as_deref()
366 100 : .is_none_or(|e| wstart.as_deref().is_none_or(|w| e > w))
367 110 : });
368 372 : if !intersects {
369 272 : return false;
370 100 : }
371 100 : if let Some(o) = doc.as_object_mut() {
372 592 : for (k, v) in o.iter_mut() {
373 592 : if is_meta(k) {
374 492 : continue;
375 100 : }
376 100 : if let Some(arr) = v.as_array_mut() {
377 134 : arr.retain(|inst| {
378 134 : inst.get(timeprop)
379 134 : .and_then(Value::as_str)
380 134 : .map(dt_key)
381 134 : .is_some_and(|t| {
382 134 : let t = t.as_str();
383 134 : iv.iter()
384 138 : .any(|(s, e)| t >= s.as_str() && e.as_deref().is_none_or(|e| t < e))
385 134 : })
386 134 : });
387 0 : }
388 : }
389 0 : }
390 100 : true
391 372 : }
|