rust-rule-engine 1.0.1-alpha

A high-performance rule engine for Rust with RETE-UL algorithm (2-24x faster), CLIPS-inspired features (Template System, Defglobal, Deffacts, Test CE, Conflict Resolution), Parallel Execution
Documentation
use rust_rule_engine::engine::engine::RustRuleEngine;
use rust_rule_engine::engine::plugin::{PluginHealth, PluginMetadata, PluginState, RulePlugin};
use rust_rule_engine::errors::Result;
use rust_rule_engine::types::Value;
use std::collections::HashMap;

/// Database Operations Plugin for SQL and NoSQL database interactions
pub struct DatabasePlugin {
    metadata: PluginMetadata,
}

impl DatabasePlugin {
    pub fn new() -> Self {
        Self {
            metadata: PluginMetadata {
                name: "database".to_string(),
                version: "1.0.0".to_string(),
                description: "Database operations for SQL and NoSQL databases".to_string(),
                author: "Rust Rule Engine Team".to_string(),
                state: PluginState::Loaded,
                health: PluginHealth::Healthy,
                actions: vec![
                    "ExecuteSQL".to_string(),
                    "InsertRecord".to_string(),
                    "UpdateRecord".to_string(),
                    "DeleteRecord".to_string(),
                    "CreateIndex".to_string(),
                    "BackupDatabase".to_string(),
                    "MongoFind".to_string(),
                    "RedisSet".to_string(),
                    "RedisGet".to_string(),
                ],
                functions: vec![
                    "sqlQuery".to_string(),
                    "countRows".to_string(),
                    "getLastInsertId".to_string(),
                    "formatQuery".to_string(),
                    "sanitizeInput".to_string(),
                    "buildWhereClause".to_string(),
                    "aggregateData".to_string(),
                ],
                dependencies: vec![
                    "sqlx".to_string(),
                    "mongodb".to_string(),
                    "redis".to_string(),
                    "serde_json".to_string(),
                ],
            },
        }
    }
}

impl RulePlugin for DatabasePlugin {
    fn get_metadata(&self) -> &PluginMetadata {
        &self.metadata
    }

    fn register_actions(&self, engine: &mut RustRuleEngine) -> Result<()> {
        // Execute SQL action
        engine.register_action_handler("ExecuteSQL", |params, facts| {
            let query = params.get("0").map(|v| v.to_string()).unwrap_or_default();
            let result_key = params
                .get("1")
                .map(|v| v.to_string())
                .unwrap_or("sql_result".to_string());

            println!("💾 EXECUTING SQL:");
            println!("   Query: {}", query);

            // Simulate query execution based on query type
            if query.to_lowercase().contains("select") {
                println!("   ✅ Query executed - 3 rows returned");
                facts.add_value(&result_key, Value::String("3 rows".to_string()))?;
                facts.add_value("rows_affected", Value::Number(3.0))?;
            } else if query.to_lowercase().contains("insert") {
                println!("   ✅ Insert successful - 1 row inserted");
                facts.add_value(&result_key, Value::String("insert_success".to_string()))?;
                facts.add_value("last_insert_id", Value::Number(456.0))?;
            } else if query.to_lowercase().contains("update") {
                println!("   ✅ Update successful - 2 rows modified");
                facts.add_value(&result_key, Value::String("update_success".to_string()))?;
                facts.add_value("rows_affected", Value::Number(2.0))?;
            } else {
                println!("   ✅ Query executed successfully");
                facts.add_value(&result_key, Value::String("success".to_string()))?;
            }

            Ok(())
        });

        // Insert Record action
        engine.register_action_handler("InsertRecord", |params, facts| {
            let table = params
                .get("0")
                .map(|v| v.to_string())
                .unwrap_or("table".to_string());
            let data = params
                .get("1")
                .map(|v| v.to_string())
                .unwrap_or("{}".to_string());

            println!("📝 INSERT RECORD:");
            println!("   Table: {}", table);
            println!("   Data: {}", data);
            println!("   ✅ Record inserted with ID: 789");

            facts.add_value("last_insert_id", Value::Number(789.0))?;
            facts.add_value("insert_success", Value::Boolean(true))?;
            Ok(())
        });

        // MongoDB Find action
        engine.register_action_handler("MongoFind", |params, facts| {
            let collection = params
                .get("0")
                .map(|v| v.to_string())
                .unwrap_or("users".to_string());
            let filter = params
                .get("1")
                .map(|v| v.to_string())
                .unwrap_or("{}".to_string());

            println!("🍃 MONGODB FIND:");
            println!("   Collection: {}", collection);
            println!("   Filter: {}", filter);
            println!("   ✅ Found 5 documents");

            facts.add_value(
                "mongo_results",
                Value::String("[{\"_id\": 1, \"name\": \"John\"}]".to_string()),
            )?;
            facts.add_value("documents_found", Value::Number(5.0))?;
            Ok(())
        });

        // Redis Set action
        engine.register_action_handler("RedisSet", |params, _facts| {
            let key = params
                .get("0")
                .map(|v| v.to_string())
                .unwrap_or("key".to_string());
            let value = params
                .get("1")
                .map(|v| v.to_string())
                .unwrap_or("value".to_string());
            let ttl = params
                .get("2")
                .map(|v| v.to_string())
                .unwrap_or("3600".to_string());

            println!("🔴 REDIS SET:");
            println!("   Key: {}", key);
            println!("   Value: {}", value);
            println!("   TTL: {} seconds", ttl);
            println!("   ✅ Key cached successfully");

            Ok(())
        });

        // Redis Get action
        engine.register_action_handler("RedisGet", |params, facts| {
            let key = params
                .get("0")
                .map(|v| v.to_string())
                .unwrap_or("key".to_string());
            let result_key = params
                .get("1")
                .map(|v| v.to_string())
                .unwrap_or("redis_value".to_string());

            println!("🔴 REDIS GET:");
            println!("   Key: {}", key);

            // Simulate cache hit/miss
            if key.contains("user") {
                println!("   ✅ Cache HIT - User data found");
                facts.add_value(
                    &result_key,
                    Value::String("{\"id\": 123, \"name\": \"Cached User\"}".to_string()),
                )?;
                facts.add_value("cache_hit", Value::Boolean(true))?;
            } else {
                println!("   ❌ Cache MISS - Key not found");
                facts.add_value("cache_hit", Value::Boolean(false))?;
            }

            Ok(())
        });

        // Backup Database action
        engine.register_action_handler("BackupDatabase", |params, facts| {
            let database = params
                .get("0")
                .map(|v| v.to_string())
                .unwrap_or("main_db".to_string());
            let backup_path = params
                .get("1")
                .map(|v| v.to_string())
                .unwrap_or("/backups/".to_string());

            println!("💿 DATABASE BACKUP:");
            println!("   Database: {}", database);
            println!("   Backup Path: {}", backup_path);
            println!("   📊 Backing up 150,000 records...");
            println!("   ✅ Backup completed - backup_20241016_143022.sql");

            facts.add_value(
                "backup_file",
                Value::String("backup_20241016_143022.sql".to_string()),
            )?;
            facts.add_value("backup_success", Value::Boolean(true))?;
            Ok(())
        });

        Ok(())
    }

    fn register_functions(&self, engine: &mut RustRuleEngine) -> Result<()> {
        // SQL Query function
        engine.register_function("sqlQuery", |args, _facts| {
            let table = args
                .get(0)
                .map(|v| v.to_string())
                .unwrap_or("users".to_string());
            let columns = args
                .get(1)
                .map(|v| v.to_string())
                .unwrap_or("*".to_string());
            let condition = args
                .get(2)
                .map(|v| v.to_string())
                .unwrap_or("1=1".to_string());

            let query = format!("SELECT {} FROM {} WHERE {}", columns, table, condition);
            println!("🔧 Generated SQL: {}", query);

            Ok(Value::String(query))
        });

        // Count Rows function
        engine.register_function("countRows", |args, _facts| {
            let table = args
                .get(0)
                .map(|v| v.to_string())
                .unwrap_or("users".to_string());

            println!("🔢 Counting rows in table: {}", table);

            // Simulate different counts for different tables
            let count = match table.as_str() {
                "users" => 1250.0,
                "orders" => 8943.0,
                "products" => 567.0,
                _ => 100.0,
            };

            Ok(Value::Number(count))
        });

        // Sanitize Input function
        engine.register_function("sanitizeInput", |args, _facts| {
            let input = args.get(0).map(|v| v.to_string()).unwrap_or_default();

            // Simulate SQL injection protection
            let sanitized = input.replace("'", "''").replace("--", "").replace(";", "");

            println!("🛡️  Sanitized input: {} -> {}", input, sanitized);

            Ok(Value::String(sanitized))
        });

        // Build Where Clause function
        engine.register_function("buildWhereClause", |args, _facts| {
            let field = args
                .get(0)
                .map(|v| v.to_string())
                .unwrap_or("id".to_string());
            let operator = args
                .get(1)
                .map(|v| v.to_string())
                .unwrap_or("=".to_string());
            let value = args
                .get(2)
                .map(|v| v.to_string())
                .unwrap_or("1".to_string());

            let clause = if value.parse::<f64>().is_ok() {
                format!("{} {} {}", field, operator, value)
            } else {
                format!("{} {} '{}'", field, operator, value)
            };

            println!("🔧 Built WHERE clause: {}", clause);

            Ok(Value::String(clause))
        });

        // Aggregate Data function
        engine.register_function("aggregateData", |args, _facts| {
            let function = args
                .get(0)
                .map(|v| v.to_string())
                .unwrap_or("COUNT".to_string());
            let column = args
                .get(1)
                .map(|v| v.to_string())
                .unwrap_or("*".to_string());
            let table = args
                .get(2)
                .map(|v| v.to_string())
                .unwrap_or("data".to_string());

            println!("📊 Aggregating: {} {} from {}", function, column, table);

            // Simulate different aggregation results
            let result = match function.to_uppercase().as_str() {
                "COUNT" => 1547.0,
                "SUM" => 98765.43,
                "AVG" => 423.21,
                "MAX" => 999.99,
                "MIN" => 1.0,
                _ => 0.0,
            };

            Ok(Value::Number(result))
        });

        Ok(())
    }

    fn unload(&mut self) -> Result<()> {
        self.metadata.state = PluginState::Unloaded;
        println!("💾 Database Plugin unloaded");
        Ok(())
    }

    fn health_check(&mut self) -> PluginHealth {
        // Simulate database connectivity check
        println!("🏥 Database health check: All connections healthy");
        PluginHealth::Healthy
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rust_rule_engine::engine::knowledge_base::KnowledgeBase;
    use rust_rule_engine::Facts;

    #[test]
    fn test_database_plugin() {
        let kb = KnowledgeBase::new("DatabaseTest");
        let mut engine = RustRuleEngine::new(kb);
        let facts = Facts::new();

        let plugin = DatabasePlugin::new();

        // Test plugin registration
        assert!(plugin.register_actions(&mut engine).is_ok());
        assert!(plugin.register_functions(&mut engine).is_ok());

        // Test function availability
        assert!(engine.has_function("sqlQuery"));
        assert!(engine.has_function("countRows"));
        assert!(engine.has_function("aggregateData"));

        // Test action availability
        assert!(engine.has_action_handler("ExecuteSQL"));
        assert!(engine.has_action_handler("MongoFind"));
        assert!(engine.has_action_handler("RedisSet"));
    }
}