1use std::collections::HashMap;
39use std::fmt;
40use std::path::{Path, PathBuf};
41
42use anyhow::{anyhow, Result};
43
44use crate::link::Link;
45use crate::link_storage::ChangeObserver;
46use crate::lino_link::LinoLink;
47use crate::named_type_links::{escape_lino_reference, NamedTypeLinks};
48use crate::named_types::NamedTypesDecorator;
49use crate::parser::Parser;
50use crate::query_processor::QueryProcessor;
51
52pub const INTERNAL_NAME_PREFIX: &str = "__persistent_transformation:";
56
57const MISSING_PARTS: &str =
58 "Persistent transformation query must contain a condition and a substitution.";
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum PersistentTransformationKind {
63 Once,
65 Always,
67}
68
69impl fmt::Display for PersistentTransformationKind {
70 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71 let text = match self {
72 Self::Once => "Once",
73 Self::Always => "Always",
74 };
75 formatter.write_str(text)
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct PersistentTransformation {
82 pub root: u32,
84 pub kind: PersistentTransformationKind,
85 pub condition: String,
87 pub substitution: String,
89}
90
91impl PersistentTransformation {
92 pub fn query(&self) -> String {
94 format!("({} {})", self.condition, self.substitution)
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct PersistentTransformationQuery {
106 pub condition: String,
107 pub substitution: String,
108}
109
110impl PersistentTransformationQuery {
111 pub fn parse(query: &str) -> Result<Self> {
116 let parsed = Parser::new().parse(query)?;
117 let outer = parsed.first().ok_or_else(|| anyhow!(MISSING_PARTS))?;
118
119 let (condition, substitution) = match outer.values.as_deref() {
120 Some(values) if values.len() >= 2 => (&values[0], &values[1]),
121 _ if parsed.len() >= 2 => (&parsed[0], &parsed[1]),
122 _ => return Err(anyhow!(MISSING_PARTS)),
123 };
124
125 Ok(Self {
126 condition: format_lino(condition),
127 substitution: format_lino(substitution),
128 })
129 }
130
131 pub fn query(&self) -> String {
133 format!("({} {})", self.condition, self.substitution)
134 }
135}
136
137fn format_lino(link: &LinoLink) -> String {
143 let values = link.values.as_deref().unwrap_or(&[]);
144 let id = link.id.as_deref().unwrap_or_default();
145
146 if values.is_empty() {
147 return if id.is_empty() {
148 "()".to_string()
149 } else {
150 escape_lino_reference(id)
151 };
152 }
153
154 let rendered = values.iter().map(format_lino).collect::<Vec<_>>().join(" ");
155
156 if id.is_empty() {
157 format!("({rendered})")
158 } else {
159 format!("({}: {})", escape_lino_reference(id), rendered)
160 }
161}
162
163pub fn make_triggers_database_filename<P: AsRef<Path>>(database_filename: P) -> PathBuf {
165 let path = database_filename.as_ref();
166 let stem = path
167 .file_stem()
168 .and_then(|stem| stem.to_str())
169 .unwrap_or_default();
170 let name = format!("{stem}.triggers.links");
171 match path.parent() {
172 Some(parent) if !parent.as_os_str().is_empty() => parent.join(name),
173 _ => PathBuf::from(name),
174 }
175}
176
177pub enum TriggerStore {
179 Embedded,
181 Sidecar(Box<NamedTypesDecorator>),
183}
184
185impl TriggerStore {
186 pub fn sidecar<P: AsRef<Path>>(path: P, trace: bool) -> Result<Self> {
188 Ok(Self::Sidecar(Box::new(NamedTypesDecorator::new(
189 path, trace,
190 )?)))
191 }
192}
193
194#[derive(Debug, Clone, Copy)]
200struct TriggerSchema {
201 once: u32,
202 always: u32,
203 condition: u32,
204 substitution: u32,
205}
206
207const SCHEMA_NAMES: [&str; 6] = [
208 "Type",
209 "Trigger",
210 "Once",
211 "Always",
212 "Condition",
213 "Substitution",
214];
215
216fn ensure_schema<L: NamedTypeLinks + ?Sized>(links: &mut L) -> Result<TriggerSchema> {
218 let r#type = links.get_or_create_named("Type")?;
219 let trigger = links.get_or_create_named("Trigger")?;
220 let once = links.get_or_create_named("Once")?;
221 let always = links.get_or_create_named("Always")?;
222 let condition = links.get_or_create_named("Condition")?;
223 let substitution = links.get_or_create_named("Substitution")?;
224
225 links.get_or_create(r#type, trigger);
226 links.get_or_create(trigger, once);
227 links.get_or_create(trigger, always);
228 links.get_or_create(r#type, condition);
229 links.get_or_create(r#type, substitution);
230
231 Ok(TriggerSchema {
232 once,
233 always,
234 condition,
235 substitution,
236 })
237}
238
239fn try_get_schema<L: NamedTypeLinks + ?Sized>(links: &mut L) -> Result<Option<TriggerSchema>> {
243 let mut ids = [0u32; 6];
244 for (slot, name) in ids.iter_mut().zip(SCHEMA_NAMES) {
245 match links.get_by_name(name)? {
246 Some(id) => *slot = id,
247 None => return Ok(None),
248 }
249 }
250
251 Ok(Some(TriggerSchema {
252 once: ids[2],
253 always: ids[3],
254 condition: ids[4],
255 substitution: ids[5],
256 }))
257}
258
259fn triggers_in<L: NamedTypeLinks + ?Sized>(links: &mut L) -> Result<Vec<PersistentTransformation>> {
261 let Some(schema) = try_get_schema(links)? else {
262 return Ok(Vec::new());
263 };
264
265 let mut all = links.all_links();
266 all.sort_by_key(|link| link.index);
267 let by_index: HashMap<u32, Link> = all.iter().map(|link| (link.index, *link)).collect();
268
269 let mut triggers = Vec::new();
270 for link in &all {
271 let kind = if link.source == schema.always {
272 PersistentTransformationKind::Always
273 } else if link.source == schema.once {
274 PersistentTransformationKind::Once
275 } else {
276 continue;
277 };
278
279 let Some(payload) = by_index.get(&link.target) else {
280 continue;
281 };
282 let (Some(condition_record), Some(substitution_record)) =
283 (by_index.get(&payload.source), by_index.get(&payload.target))
284 else {
285 continue;
286 };
287 if condition_record.source != schema.condition
288 || substitution_record.source != schema.substitution
289 {
290 continue;
291 }
292
293 let condition = links.get_name(condition_record.target)?;
294 let substitution = links.get_name(substitution_record.target)?;
295 let (Some(condition), Some(substitution)) = (
296 decode_text_name(condition.as_deref(), "condition"),
297 decode_text_name(substitution.as_deref(), "substitution"),
298 ) else {
299 continue;
300 };
301
302 triggers.push(PersistentTransformation {
303 root: link.index,
304 kind,
305 condition,
306 substitution,
307 });
308 }
309
310 Ok(triggers)
311}
312
313fn store_trigger_in<L: NamedTypeLinks + ?Sized>(
318 links: &mut L,
319 kind: PersistentTransformationKind,
320 parsed: &PersistentTransformationQuery,
321) -> Result<u32> {
322 let schema = ensure_schema(links)?;
323 let condition_text = links.get_or_create_named(&condition_text_name(&parsed.condition))?;
324 let substitution_text =
325 links.get_or_create_named(&substitution_text_name(&parsed.substitution))?;
326 let condition_record = links.get_or_create(schema.condition, condition_text);
327 let substitution_record = links.get_or_create(schema.substitution, substitution_text);
328 let payload = links.get_or_create(condition_record, substitution_record);
329 let trigger_type = match kind {
330 PersistentTransformationKind::Always => schema.always,
331 PersistentTransformationKind::Once => schema.once,
332 };
333 Ok(links.get_or_create(trigger_type, payload))
334}
335
336fn delete_trigger_root<L: NamedTypeLinks + ?Sized>(links: &mut L, root: u32) -> Result<bool> {
339 if !links.exists(root) {
340 return Ok(false);
341 }
342 links.delete(root)?;
343 Ok(true)
344}
345
346fn condition_text_name(condition: &str) -> String {
347 format!("{INTERNAL_NAME_PREFIX}condition:{condition}")
348}
349
350fn substitution_text_name(substitution: &str) -> String {
351 format!("{INTERNAL_NAME_PREFIX}substitution:{substitution}")
352}
353
354fn decode_text_name(name: Option<&str>, part: &str) -> Option<String> {
355 let prefix = format!("{INTERNAL_NAME_PREFIX}{part}:");
356 name?.strip_prefix(&prefix).map(str::to_string)
357}
358
359macro_rules! on_trigger_links {
367 ($self:expr, $call:ident($($arg:expr),* $(,)?)) => {
368 match $self.triggers {
369 TriggerStore::Sidecar(ref mut store) => $call(store.as_mut() $(, $arg)*),
370 TriggerStore::Embedded => $call(&mut $self.links $(, $arg)*),
371 }
372 };
373}
374
375pub struct PersistentTransformationDecorator<L: NamedTypeLinks> {
380 links: L,
381 triggers: TriggerStore,
382 trace: bool,
383 applying_triggers: bool,
384 suppress_triggers: bool,
385 auto_create_missing_references: bool,
386 pending_error: Option<anyhow::Error>,
393}
394
395impl<L: NamedTypeLinks> PersistentTransformationDecorator<L> {
396 pub fn new(links: L, triggers: TriggerStore, trace: bool) -> Self {
397 Self {
398 links,
399 triggers,
400 trace,
401 applying_triggers: false,
402 suppress_triggers: false,
403 auto_create_missing_references: false,
404 pending_error: None,
405 }
406 }
407
408 pub fn embedded(links: L, trace: bool) -> Self {
410 Self::new(links, TriggerStore::Embedded, trace)
411 }
412
413 pub fn with_sidecar(links: L, trigger_links: NamedTypesDecorator, trace: bool) -> Self {
415 Self::new(links, TriggerStore::Sidecar(Box::new(trigger_links)), trace)
416 }
417
418 pub fn with_auto_create_missing_references(mut self, enabled: bool) -> Self {
420 self.auto_create_missing_references = enabled;
421 self
422 }
423
424 pub fn auto_create_missing_references(&self) -> bool {
425 self.auto_create_missing_references
426 }
427
428 pub fn set_auto_create_missing_references(&mut self, enabled: bool) {
429 self.auto_create_missing_references = enabled;
430 }
431
432 pub fn inner(&self) -> &L {
433 &self.links
434 }
435
436 pub fn inner_mut(&mut self) -> &mut L {
437 &mut self.links
438 }
439
440 pub fn trigger_store(&self) -> &TriggerStore {
441 &self.triggers
442 }
443
444 pub fn trigger_store_mut(&mut self) -> &mut TriggerStore {
445 &mut self.triggers
446 }
447
448 pub fn into_parts(self) -> (L, TriggerStore) {
450 (self.links, self.triggers)
451 }
452
453 pub fn store_trigger(
455 &mut self,
456 kind: PersistentTransformationKind,
457 query: &str,
458 ) -> Result<u32> {
459 let parsed = PersistentTransformationQuery::parse(query)?;
460 let root = self.without_trigger_application(|this| {
461 on_trigger_links!(this, store_trigger_in(kind, &parsed))
462 })?;
463 self.trace_msg(&format!(
464 "Stored {kind} trigger #{root}: {}",
465 parsed.query()
466 ));
467 Ok(root)
468 }
469
470 pub fn remove_triggers(&mut self, query: &str) -> Result<usize> {
473 let parsed = PersistentTransformationQuery::parse(query)?;
474 self.without_trigger_application(|this| {
475 let matching: Vec<u32> = this
476 .triggers()?
477 .into_iter()
478 .filter(|trigger| {
479 trigger.condition == parsed.condition
480 && trigger.substitution == parsed.substitution
481 })
482 .map(|trigger| trigger.root)
483 .collect();
484
485 for root in &matching {
486 on_trigger_links!(this, delete_trigger_root(*root))?;
487 this.trace_msg(&format!("Deleted trigger #{root}"));
488 }
489
490 Ok(matching.len())
491 })
492 }
493
494 pub fn triggers(&mut self) -> Result<Vec<PersistentTransformation>> {
496 on_trigger_links!(self, triggers_in())
497 }
498
499 fn without_trigger_application<R>(&mut self, action: impl FnOnce(&mut Self) -> R) -> R {
503 let previous = self.suppress_triggers;
504 self.suppress_triggers = true;
505 let result = action(self);
506 self.suppress_triggers = previous;
507 result
508 }
509
510 fn after_write(&mut self) {
512 if let Err(error) = self.apply_triggers_after_operation() {
513 if self.pending_error.is_none() {
514 self.pending_error = Some(error);
515 }
516 }
517 }
518
519 fn take_pending_error(&mut self) -> Result<()> {
521 match self.pending_error.take() {
522 Some(error) => Err(error),
523 None => Ok(()),
524 }
525 }
526
527 fn apply_triggers_after_operation(&mut self) -> Result<()> {
528 if self.suppress_triggers || self.applying_triggers {
529 return Ok(());
530 }
531
532 let triggers = self.triggers()?;
533 if triggers.is_empty() {
534 return Ok(());
535 }
536
537 self.applying_triggers = true;
538 let outcome = self.apply_triggers(&triggers);
539 self.applying_triggers = false;
540 outcome
541 }
542
543 fn apply_triggers(&mut self, triggers: &[PersistentTransformation]) -> Result<()> {
544 let processor = QueryProcessor::new(self.trace)
545 .with_auto_create_missing_references(self.auto_create_missing_references);
546
547 for trigger in triggers {
548 let changes = processor.process_query(self, &trigger.query())?;
549 if changes.is_empty() || trigger.kind != PersistentTransformationKind::Once {
550 continue;
551 }
552
553 let root = trigger.root;
554 self.without_trigger_application(|this| {
555 on_trigger_links!(this, delete_trigger_root(root))
556 })?;
557 self.trace_msg(&format!("Deleted trigger #{root}"));
558 }
559
560 Ok(())
561 }
562
563 fn trace_msg(&self, message: &str) {
564 if self.trace {
565 println!("[PersistentTransformation] {message}");
566 }
567 }
568}
569
570impl<L: NamedTypeLinks> NamedTypeLinks for PersistentTransformationDecorator<L> {
571 fn create(&mut self, source: u32, target: u32) -> u32 {
572 let index = self.links.create(source, target);
573 self.after_write();
574 index
575 }
576
577 fn ensure_created(&mut self, id: u32) -> u32 {
586 let existed = self.links.exists(id);
587 let index = self.links.ensure_created(id);
588 if !existed {
589 self.after_write();
590 }
591 index
592 }
593
594 fn get_link(&mut self, id: u32) -> Option<Link> {
595 self.links.get_link(id)
596 }
597
598 fn exists(&mut self, id: u32) -> bool {
599 self.links.exists(id)
600 }
601
602 fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
603 let link = self.links.update(id, source, target)?;
604 self.apply_triggers_after_operation()?;
605 self.take_pending_error()?;
606 Ok(link)
607 }
608
609 fn delete(&mut self, id: u32) -> Result<Link> {
610 let link = self.links.delete(id)?;
611 self.apply_triggers_after_operation()?;
612 self.take_pending_error()?;
613 Ok(link)
614 }
615
616 fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
617 let link = self.links.delete_observed(id, observer)?;
618 self.apply_triggers_after_operation()?;
619 self.take_pending_error()?;
620 Ok(link)
621 }
622
623 fn all_links(&mut self) -> Vec<Link> {
624 self.links.all_links()
625 }
626
627 fn search(&mut self, source: u32, target: u32) -> Option<u32> {
628 self.links.search(source, target)
629 }
630
631 fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
634 if let Some(index) = self.links.search(source, target) {
635 return index;
636 }
637 let index = self.links.get_or_create(source, target);
638 self.after_write();
639 index
640 }
641
642 fn get_name(&mut self, id: u32) -> Result<Option<String>> {
643 self.links.get_name(id)
644 }
645
646 fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
647 self.links.set_name(id, name)
648 }
649
650 fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
651 self.links.get_by_name(name)
652 }
653
654 fn remove_name(&mut self, id: u32) -> Result<()> {
655 self.links.remove_name(id)
656 }
657
658 fn save(&mut self) -> Result<()> {
659 self.take_pending_error()?;
660 self.links.save()?;
661 if let TriggerStore::Sidecar(store) = &mut self.triggers {
662 store.save()?;
663 }
664 Ok(())
665 }
666}