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