linkrs-query 0.1.0

Query engine, parser, and executor for Linkrs
Documentation
use std::path::Path;
use std::sync::Arc;

use linkrs_core::Value;

use super::super::udf::{SharedPlugin, UdfError, UdfPlugin};
use super::*;
use crate::executor::expression::{ExpressionError, ExpressionErrorType};

struct AddOne;

impl UdfPlugin for AddOne {
    fn name(&self) -> &str {
        "test_add_one"
    }

    fn description(&self) -> &str {
        "adds one"
    }

    fn min_arity(&self) -> usize {
        1
    }

    fn max_arity(&self) -> usize {
        1
    }

    fn is_pure(&self) -> bool {
        true
    }

    fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
        match &args[0] {
            Value::Int(v) => Ok(Value::Int(v + 1)),
            other => Err(ExpressionError::new(
                ExpressionErrorType::TypeError,
                format!("expected int, got {other:?}"),
            )),
        }
    }
}

fn plugin() -> SharedPlugin {
    Arc::new(AddOne)
}

#[test]
fn test_register_and_execute_dynamic_plugin() {
    let mut registry = FunctionRegistry::new();
    let name = registry
        .register_dynamic_plugin(plugin())
        .expect("registration failed");
    assert_eq!(name, "test_add_one");
    assert!(registry.is_dynamic("TEST_ADD_ONE"));
    let result = registry
        .execute("test_add_one", &[Value::Int(41)])
        .expect("execution failed");
    assert_eq!(result, Value::Int(42));
    let infos = registry.list_dynamic_udfs();
    assert_eq!(infos.len(), 1);
    assert_eq!(infos[0].name, "test_add_one");
}

#[test]
fn test_dynamic_plugin_duplicate_rejected() {
    let mut registry = FunctionRegistry::new();
    registry
        .register_dynamic_plugin(plugin())
        .expect("first registration failed");
    let err = registry.register_dynamic_plugin(plugin()).unwrap_err();
    assert!(matches!(err, UdfError::AlreadyLoaded(_, _)));
}

#[test]
fn test_dynamic_plugin_builtin_conflict_rejected() {
    let mut registry = FunctionRegistry::new();
    struct ShadowAbs;
    impl UdfPlugin for ShadowAbs {
        fn name(&self) -> &str {
            "abs"
        }
        fn description(&self) -> &str {
            "shadow"
        }
        fn min_arity(&self) -> usize {
            1
        }
        fn max_arity(&self) -> usize {
            1
        }
        fn is_pure(&self) -> bool {
            true
        }
        fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
            Ok(args[0].clone())
        }
    }
    let err = registry
        .register_dynamic_plugin(Arc::new(ShadowAbs))
        .unwrap_err();
    assert!(matches!(err, UdfError::BuiltinConflict(_)));
}

#[test]
fn test_unload_dynamic_plugin() {
    let mut registry = FunctionRegistry::new();
    registry
        .register_dynamic_plugin(plugin())
        .expect("registration failed");
    registry
        .unload_dynamic_udf("test_add_one")
        .expect("unload failed");
    assert!(!registry.is_dynamic("test_add_one"));
    let err = registry
        .execute("test_add_one", &[Value::Int(1)])
        .unwrap_err();
    assert_eq!(err.error_type, ExpressionErrorType::UndefinedFunction);
    let err = registry.unload_dynamic_udf("test_add_one").unwrap_err();
    assert!(matches!(err, UdfError::NotLoaded(_)));
}

#[test]
fn test_reload_in_process_plugin_skipped() {
    let mut registry = FunctionRegistry::new();
    registry
        .register_dynamic_plugin(plugin())
        .expect("registration failed");
    let reloaded = registry
        .reload_dynamic_udf("test_add_one")
        .expect("reload failed");
    assert!(!reloaded);
}

#[test]
fn test_load_dynamic_udf_missing_file() {
    let mut registry = FunctionRegistry::new();
    let err = registry
        .load_dynamic_udf(Path::new("/nonexistent/udf_plugin.so"))
        .unwrap_err();
    assert!(matches!(err, UdfError::InvalidPath(_, _)));
}

#[test]
fn test_install_dynamic_udf_rejects_remote_source() {
    let mut registry = FunctionRegistry::new();
    let err = registry
        .install_dynamic_udf("https://example.com/udf.so")
        .unwrap_err();
    assert!(matches!(err, UdfError::RepoDownloadUnsupported(_)));
}