Skip to main content

rust_rule_engine/engine/
knowledge_base.rs

1#![allow(deprecated)]
2
3use crate::engine::rule::Rule;
4use crate::errors::{Result, RuleEngineError};
5use crate::parser::grl::GRLParser;
6use crate::types::Value;
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9
10/// Knowledge Base - manages collections of rules and facts
11/// Similar to Grule's KnowledgeBase concept
12#[derive(Debug)]
13pub struct KnowledgeBase {
14    name: String,
15    rules: Arc<RwLock<Vec<Rule>>>,
16    rule_index: Arc<RwLock<HashMap<String, usize>>>,
17    version: Arc<RwLock<u64>>,
18}
19
20impl KnowledgeBase {
21    /// Create a new knowledge base
22    pub fn new(name: &str) -> Self {
23        Self {
24            name: name.to_string(),
25            rules: Arc::new(RwLock::new(Vec::new())),
26            rule_index: Arc::new(RwLock::new(HashMap::new())),
27            version: Arc::new(RwLock::new(0)),
28        }
29    }
30
31    /// Get the knowledge base name
32    pub fn name(&self) -> &str {
33        &self.name
34    }
35
36    /// Get the current version of the knowledge base
37    pub fn version(&self) -> u64 {
38        *self.version.read().unwrap()
39    }
40
41    /// Add a rule to the knowledge base
42    pub fn add_rule(&self, rule: Rule) -> Result<()> {
43        let mut rules = self.rules.write().unwrap();
44        let mut index = self.rule_index.write().unwrap();
45        let mut version = self.version.write().unwrap();
46
47        // Check for duplicate rule names
48        if index.contains_key(&rule.name) {
49            return Err(RuleEngineError::ParseError {
50                message: format!("Rule '{}' already exists", rule.name),
51            });
52        }
53
54        let rule_position = rules.len();
55        index.insert(rule.name.clone(), rule_position);
56        rules.push(rule);
57
58        // Sort rules by priority (salience)
59        // Sort by salience descending using sort_by_key + Reverse
60        rules.sort_by_key(|b| std::cmp::Reverse(b.salience));
61
62        // Rebuild index after sorting
63        index.clear();
64        for (pos, rule) in rules.iter().enumerate() {
65            index.insert(rule.name.clone(), pos);
66        }
67
68        *version += 1;
69        Ok(())
70    }
71
72    /// Add multiple rules from GRL text
73    pub fn add_rules_from_grl(&self, grl_text: &str) -> Result<usize> {
74        let rules = GRLParser::parse_rules(grl_text)?;
75        let count = rules.len();
76
77        for rule in rules {
78            self.add_rule(rule)?;
79        }
80
81        Ok(count)
82    }
83
84    /// Remove a rule by name
85    pub fn remove_rule(&self, rule_name: &str) -> Result<bool> {
86        let mut rules = self.rules.write().unwrap();
87        let mut index = self.rule_index.write().unwrap();
88        let mut version = self.version.write().unwrap();
89
90        if let Some(&position) = index.get(rule_name) {
91            rules.remove(position);
92
93            // Rebuild index
94            index.clear();
95            for (pos, rule) in rules.iter().enumerate() {
96                index.insert(rule.name.clone(), pos);
97            }
98
99            *version += 1;
100            Ok(true)
101        } else {
102            Ok(false)
103        }
104    }
105
106    /// Get a rule by name
107    pub fn get_rule(&self, rule_name: &str) -> Option<Rule> {
108        let rules = self.rules.read().unwrap();
109        let index = self.rule_index.read().unwrap();
110
111        if let Some(&position) = index.get(rule_name) {
112            rules.get(position).cloned()
113        } else {
114            None
115        }
116    }
117
118    /// Get all rules
119    pub fn get_rules(&self) -> Vec<Rule> {
120        let rules = self.rules.read().unwrap();
121        rules.clone()
122    }
123
124    /// Get rules sorted by salience without cloning individual rules
125    /// Returns references to rules in descending salience order
126    pub fn get_rules_by_salience(&self) -> Vec<usize> {
127        let rules = self.rules.read().unwrap();
128        let mut indices: Vec<usize> = (0..rules.len()).collect();
129        indices.sort_by(|&a, &b| rules[b].salience.cmp(&rules[a].salience));
130        indices
131    }
132
133    /// Get rule by index - avoids cloning
134    pub fn get_rule_by_index(&self, index: usize) -> Option<Rule> {
135        let rules = self.rules.read().unwrap();
136        rules.get(index).cloned()
137    }
138
139    /// Get all rule names
140    pub fn get_rule_names(&self) -> Vec<String> {
141        let index = self.rule_index.read().unwrap();
142        index.keys().cloned().collect()
143    }
144
145    /// Get rule count
146    pub fn rule_count(&self) -> usize {
147        let rules = self.rules.read().unwrap();
148        rules.len()
149    }
150
151    /// Enable or disable a rule
152    pub fn set_rule_enabled(&self, rule_name: &str, enabled: bool) -> Result<bool> {
153        let mut rules = self.rules.write().unwrap();
154        let index = self.rule_index.read().unwrap();
155        let mut version = self.version.write().unwrap();
156
157        if let Some(&position) = index.get(rule_name) {
158            if let Some(rule) = rules.get_mut(position) {
159                rule.enabled = enabled;
160                *version += 1;
161                Ok(true)
162            } else {
163                Ok(false)
164            }
165        } else {
166            Ok(false)
167        }
168    }
169
170    /// Clear all rules
171    pub fn clear(&self) {
172        let mut rules = self.rules.write().unwrap();
173        let mut index = self.rule_index.write().unwrap();
174        let mut version = self.version.write().unwrap();
175
176        rules.clear();
177        index.clear();
178        *version += 1;
179    }
180
181    /// Get a snapshot of all rules (for execution)
182    pub fn get_rules_snapshot(&self) -> Vec<Rule> {
183        let rules = self.rules.read().unwrap();
184        rules.clone()
185    }
186
187    /// Get knowledge base statistics
188    pub fn get_statistics(&self) -> KnowledgeBaseStats {
189        let rules = self.rules.read().unwrap();
190
191        let enabled_count = rules.iter().filter(|r| r.enabled).count();
192        let disabled_count = rules.len() - enabled_count;
193
194        let mut priority_distribution = HashMap::new();
195        for rule in rules.iter() {
196            *priority_distribution.entry(rule.salience).or_insert(0) += 1;
197        }
198
199        KnowledgeBaseStats {
200            name: self.name.clone(),
201            version: self.version(),
202            total_rules: rules.len(),
203            enabled_rules: enabled_count,
204            disabled_rules: disabled_count,
205            priority_distribution,
206        }
207    }
208
209    /// Export rules to GRL format
210    pub fn export_to_grl(&self) -> String {
211        let rules = self.rules.read().unwrap();
212        let mut grl_output = String::new();
213
214        grl_output.push_str(&format!("// Knowledge Base: {}\n", self.name));
215        grl_output.push_str(&format!("// Version: {}\n", self.version()));
216        grl_output.push_str(&format!("// Rules: {}\n\n", rules.len()));
217
218        for rule in rules.iter() {
219            grl_output.push_str(&rule.to_grl());
220            grl_output.push_str("\n\n");
221        }
222
223        grl_output
224    }
225}
226
227impl Clone for KnowledgeBase {
228    fn clone(&self) -> Self {
229        let rules = self.rules.read().unwrap();
230        let new_kb = KnowledgeBase::new(&self.name);
231
232        for rule in rules.iter() {
233            let _ = new_kb.add_rule(rule.clone());
234        }
235
236        new_kb
237    }
238}
239
240/// Statistics about a Knowledge Base
241#[derive(Debug, Clone)]
242pub struct KnowledgeBaseStats {
243    /// The name of the knowledge base
244    pub name: String,
245    /// The version number of the knowledge base
246    pub version: u64,
247    /// Total number of rules in the knowledge base
248    pub total_rules: usize,
249    /// Number of enabled rules
250    pub enabled_rules: usize,
251    /// Number of disabled rules
252    pub disabled_rules: usize,
253    /// Distribution of rules by priority/salience
254    pub priority_distribution: HashMap<i32, usize>,
255}
256
257impl std::fmt::Display for KnowledgeBaseStats {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        writeln!(f, "Knowledge Base: {}", self.name)?;
260        writeln!(f, "Version: {}", self.version)?;
261        writeln!(f, "Total Rules: {}", self.total_rules)?;
262        writeln!(f, "Enabled Rules: {}", self.enabled_rules)?;
263        writeln!(f, "Disabled Rules: {}", self.disabled_rules)?;
264        writeln!(f, "Priority Distribution:")?;
265
266        let mut priorities: Vec<_> = self.priority_distribution.iter().collect();
267        priorities.sort_by(|a, b| b.0.cmp(a.0));
268
269        for (priority, count) in priorities {
270            writeln!(f, "  Priority {}: {} rules", priority, count)?;
271        }
272
273        Ok(())
274    }
275}
276
277/// Extension trait to add GRL export functionality to Rule
278trait RuleGRLExport {
279    fn to_grl(&self) -> String;
280}
281
282impl RuleGRLExport for Rule {
283    fn to_grl(&self) -> String {
284        let mut grl = String::new();
285
286        // Rule declaration
287        grl.push_str(&format!("rule {}", self.name));
288
289        if let Some(ref description) = self.description {
290            grl.push_str(&format!(" \"{}\"", description));
291        }
292
293        if self.salience != 0 {
294            grl.push_str(&format!(" salience {}", self.salience));
295        }
296
297        grl.push_str(" {\n");
298
299        // When clause
300        grl.push_str("    when\n");
301        grl.push_str(&format!("        {}\n", self.conditions.to_grl()));
302
303        // Then clause
304        grl.push_str("    then\n");
305        for action in &self.actions {
306            grl.push_str(&format!("        {};\n", action.to_grl()));
307        }
308
309        grl.push('}');
310
311        if !self.enabled {
312            grl = format!("// DISABLED\n{}", grl);
313        }
314
315        grl
316    }
317}
318
319/// Extension trait for ConditionGroup GRL export
320trait ConditionGroupGRLExport {
321    fn to_grl(&self) -> String;
322}
323
324impl ConditionGroupGRLExport for crate::engine::rule::ConditionGroup {
325    fn to_grl(&self) -> String {
326        match self {
327            crate::engine::rule::ConditionGroup::Single(condition) => {
328                format!(
329                    "{} {} {}",
330                    condition.field,
331                    condition.operator.to_grl(),
332                    condition.value.to_grl()
333                )
334            }
335            crate::engine::rule::ConditionGroup::Compound {
336                left,
337                operator,
338                right,
339            } => {
340                let op_str = match operator {
341                    crate::types::LogicalOperator::And => "&&",
342                    crate::types::LogicalOperator::Or => "||",
343                    crate::types::LogicalOperator::Not => "!",
344                };
345                format!("{} {} {}", left.to_grl(), op_str, right.to_grl())
346            }
347            crate::engine::rule::ConditionGroup::Not(condition) => {
348                format!("!{}", condition.to_grl())
349            }
350            crate::engine::rule::ConditionGroup::Exists(condition) => {
351                format!("exists({})", condition.to_grl())
352            }
353            crate::engine::rule::ConditionGroup::Forall(condition) => {
354                format!("forall({})", condition.to_grl())
355            }
356            crate::engine::rule::ConditionGroup::Accumulate {
357                source_pattern,
358                extract_field,
359                source_conditions,
360                function,
361                function_arg,
362                ..
363            } => {
364                let conditions_str = if source_conditions.is_empty() {
365                    String::new()
366                } else {
367                    format!(", {}", source_conditions.join(", "))
368                };
369                format!(
370                    "accumulate({}(${}: {}{}), {}({}))",
371                    source_pattern,
372                    function_arg.trim_start_matches('$'),
373                    extract_field,
374                    conditions_str,
375                    function,
376                    function_arg
377                )
378            }
379
380            #[cfg(feature = "streaming")]
381            crate::engine::rule::ConditionGroup::StreamPattern {
382                var_name,
383                event_type,
384                stream_name,
385                window,
386            } => {
387                // Format: login: LoginEvent from stream("logins") over window(10 min, sliding)
388                let event_type_str = event_type
389                    .as_ref()
390                    .map(|t| format!("{} ", t))
391                    .unwrap_or_default();
392                let window_str = window
393                    .as_ref()
394                    .map(|w| {
395                        let dur_secs = w.duration.as_secs();
396                        let (dur_val, dur_unit) = if dur_secs >= 3600 {
397                            (dur_secs / 3600, "hour")
398                        } else if dur_secs >= 60 {
399                            (dur_secs / 60, "min")
400                        } else {
401                            (dur_secs, "sec")
402                        };
403                        let window_type_str = match &w.window_type {
404                            crate::engine::rule::StreamWindowType::Sliding => "sliding",
405                            crate::engine::rule::StreamWindowType::Tumbling => "tumbling",
406                            crate::engine::rule::StreamWindowType::Session { .. } => "session",
407                        };
408                        format!(
409                            " over window({} {}, {})",
410                            dur_val, dur_unit, window_type_str
411                        )
412                    })
413                    .unwrap_or_default();
414                format!(
415                    "{}: {}from stream(\"{}\"){}",
416                    var_name, event_type_str, stream_name, window_str
417                )
418            }
419        }
420    }
421}
422
423/// Extension trait for Operator GRL export
424trait OperatorGRLExport {
425    fn to_grl(&self) -> &'static str;
426}
427
428impl OperatorGRLExport for crate::types::Operator {
429    fn to_grl(&self) -> &'static str {
430        match self {
431            crate::types::Operator::Equal => "==",
432            crate::types::Operator::NotEqual => "!=",
433            crate::types::Operator::GreaterThan => ">",
434            crate::types::Operator::GreaterThanOrEqual => ">=",
435            crate::types::Operator::LessThan => "<",
436            crate::types::Operator::LessThanOrEqual => "<=",
437            crate::types::Operator::Contains => "contains",
438            crate::types::Operator::NotContains => "not_contains",
439            crate::types::Operator::StartsWith => "startsWith",
440            crate::types::Operator::EndsWith => "endsWith",
441            crate::types::Operator::Matches => "matches",
442            crate::types::Operator::In => "in",
443        }
444    }
445}
446
447/// Extension trait for Value GRL export
448trait ValueGRLExport {
449    fn to_grl(&self) -> String;
450}
451
452impl ValueGRLExport for Value {
453    fn to_grl(&self) -> String {
454        match self {
455            Value::String(s) => format!("\"{}\"", s),
456            Value::Number(n) => n.to_string(),
457            Value::Integer(i) => i.to_string(),
458            Value::Boolean(b) => b.to_string(),
459            Value::Null => "null".to_string(),
460            Value::Array(_) => "[array]".to_string(),
461            Value::Object(_) => "{object}".to_string(),
462            Value::Expression(expr) => expr.clone(), // Export as-is
463        }
464    }
465}
466
467/// Extension trait for ActionType GRL export
468trait ActionTypeGRLExport {
469    fn to_grl(&self) -> String;
470}
471
472impl ActionTypeGRLExport for crate::types::ActionType {
473    fn to_grl(&self) -> String {
474        match self {
475            crate::types::ActionType::Set { field, value } => {
476                format!("{} = {}", field, value.to_grl())
477            }
478            crate::types::ActionType::Log { message } => {
479                format!("Log(\"{}\")", message)
480            }
481            crate::types::ActionType::MethodCall {
482                object,
483                method,
484                args,
485            } => {
486                let args_str = args
487                    .iter()
488                    .map(|arg| arg.to_grl())
489                    .collect::<Vec<_>>()
490                    .join(", ");
491                format!("{}.{}({})", object, method, args_str)
492            }
493            crate::types::ActionType::Retract { object } => {
494                format!("retract(${})", object)
495            }
496            crate::types::ActionType::Custom { action_type, .. } => {
497                format!("Custom(\"{}\")", action_type)
498            }
499            crate::types::ActionType::ActivateAgendaGroup { group } => {
500                format!("ActivateAgendaGroup(\"{}\")", group)
501            }
502            crate::types::ActionType::ScheduleRule {
503                rule_name,
504                delay_ms,
505            } => {
506                format!("ScheduleRule({}, \"{}\")", delay_ms, rule_name)
507            }
508            crate::types::ActionType::CompleteWorkflow { workflow_name } => {
509                format!("CompleteWorkflow(\"{}\")", workflow_name)
510            }
511            crate::types::ActionType::SetWorkflowData { key, value } => {
512                format!("SetWorkflowData(\"{}={}\")", key, value.to_grl())
513            }
514            crate::types::ActionType::Append { field, value } => {
515                format!("{} += {}", field, value.to_grl())
516            }
517        }
518    }
519}