pgcel 0.1.0

PostgreSQL plugin to provide Google's Common Expression Language in queries
//! Criterion benchmarks for CEL compilation and evaluation performance.
//!
//! Run with: cargo bench --no-default-features --features pg16 --bench cel_benchmarks (adjust PG feature as needed).
//!
//! Note: These benchmarks test the CEL library directly, not through PostgreSQL.
//! For PostgreSQL-integrated benchmarks, use the SQL stress test.

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
#[path = "../src/conversion.rs"]
mod conversion;

use conversion::json_to_cel_value;

fn bench_compilation(c: &mut Criterion) {
    let mut group = c.benchmark_group("compilation");

    let expressions = [
        ("trivial", "true"),
        ("simple_comparison", "ctx.x > 10"),
        ("simple_string", "ctx.name == \"test\""),
        ("medium_logic", "ctx.a > 0 && ctx.b < 100"),
        (
            "medium_string_ops",
            "ctx.email.contains(\"@\") && ctx.email.size() > 5",
        ),
        (
            "complex_nested",
            "ctx.user.profile.settings.enabled && ctx.score > 50",
        ),
        (
            "complex_multi_cond",
            "ctx.a > 0 && ctx.b < 100 && ctx.c == \"active\" && ctx.d != null",
        ),
        (
            "complex_list",
            "\"admin\" in ctx.roles || \"superuser\" in ctx.roles",
        ),
        (
            "very_complex",
            "(ctx.price * ctx.quantity) * (1.0 - ctx.discount / 100.0) > ctx.minimum",
        ),
    ];

    for (name, expr) in expressions {
        group.bench_with_input(BenchmarkId::new("compile", name), &expr, |b, expr| {
            b.iter(|| cel_interpreter::Program::compile(black_box(*expr)).unwrap())
        });
    }

    group.finish();
}

fn bench_evaluation(c: &mut Criterion) {
    let mut group = c.benchmark_group("evaluation");

    let simple_program = cel_interpreter::Program::compile("ctx.x > 50").unwrap();
    let medium_program =
        cel_interpreter::Program::compile("ctx.a > 0 && ctx.b < 100 && ctx.enabled").unwrap();
    let complex_program = cel_interpreter::Program::compile(
        "ctx.user.profile.settings.enabled && ctx.score > 50 && \"admin\" in ctx.roles",
    )
    .unwrap();
    let math_program = cel_interpreter::Program::compile(
        "(ctx.price * ctx.quantity) * (1.0 - ctx.discount / 100.0) > ctx.minimum",
    )
    .unwrap();

    let small_context = serde_json::json!({
        "x": 75
    });

    let medium_context = serde_json::json!({
        "a": 50,
        "b": 25,
        "enabled": true,
        "email": "test@example.com"
    });

    let large_context = serde_json::json!({
        "user": {
            "id": 1,
            "name": "Test User",
            "profile": {
                "settings": { "enabled": true },
                "verified": true
            }
        },
        "roles": ["user", "admin"],
        "status": "active",
        "score": 85,
        "price": 100.0,
        "quantity": 5,
        "discount": 10.0,
        "minimum": 400.0
    });

    group.bench_function("eval_simple", |b| {
        let ctx_val = json_to_cel_value(&small_context);
        b.iter(|| {
            let mut context = cel_interpreter::Context::default();
            let _ = context.add_variable("ctx", ctx_val.clone());
            simple_program.execute(black_box(&context)).unwrap()
        })
    });

    group.bench_function("eval_medium", |b| {
        let ctx_val = json_to_cel_value(&medium_context);
        b.iter(|| {
            let mut context = cel_interpreter::Context::default();
            let _ = context.add_variable("ctx", ctx_val.clone());
            medium_program.execute(black_box(&context)).unwrap()
        })
    });

    group.bench_function("eval_complex", |b| {
        let ctx_val = json_to_cel_value(&large_context);
        b.iter(|| {
            let mut context = cel_interpreter::Context::default();
            let _ = context.add_variable("ctx", ctx_val.clone());
            complex_program.execute(black_box(&context)).unwrap()
        })
    });

    group.bench_function("eval_math", |b| {
        let ctx_val = json_to_cel_value(&large_context);
        b.iter(|| {
            let mut context = cel_interpreter::Context::default();
            let _ = context.add_variable("ctx", ctx_val.clone());
            math_program.execute(black_box(&context)).unwrap()
        })
    });

    group.finish();
}

fn bench_json_conversion(c: &mut Criterion) {
    let mut group = c.benchmark_group("json_conversion");

    let small_json = serde_json::json!({
        "x": 42,
        "name": "test"
    });

    let medium_json = serde_json::json!({
        "a": 50,
        "b": 25,
        "enabled": true,
        "email": "test@example.com",
        "tags": ["foo", "bar", "baz"]
    });

    let large_json = serde_json::json!({
        "user": {
            "id": 1,
            "name": "Test User",
            "profile": {
                "settings": { "enabled": true, "theme": "dark", "notifications": true },
                "verified": true,
                "metadata": { "created": "2024-01-01", "updated": "2024-06-15" }
            }
        },
        "roles": ["user", "admin", "moderator"],
        "permissions": ["read", "write", "delete", "admin"],
        "data": {
            "items": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            "nested": { "deep": { "value": 42 } }
        }
    });

    group.bench_function("convert_small", |b| {
        b.iter(|| json_to_cel_value(black_box(&small_json)))
    });

    group.bench_function("convert_medium", |b| {
        b.iter(|| json_to_cel_value(black_box(&medium_json)))
    });

    group.bench_function("convert_large", |b| {
        b.iter(|| json_to_cel_value(black_box(&large_json)))
    });

    group.finish();
}

fn bench_end_to_end(c: &mut Criterion) {
    let mut group = c.benchmark_group("end_to_end");

    let expressions = [
        ("simple", "ctx.x > 50"),
        ("medium", "ctx.a > 0 && ctx.b < 100 && ctx.enabled"),
        (
            "complex",
            "ctx.user.profile.settings.enabled && ctx.score > 50",
        ),
    ];

    let context_json = serde_json::json!({
        "x": 75,
        "a": 50,
        "b": 25,
        "enabled": true,
        "user": {
            "profile": {
                "settings": { "enabled": true }
            }
        },
        "score": 85
    });

    for (name, expr) in expressions {
        group.bench_with_input(
            BenchmarkId::new("full_pipeline", name),
            &(expr, &context_json),
            |b, (expr, json)| {
                b.iter(|| {
                    let program = cel_interpreter::Program::compile(black_box(*expr)).unwrap();
                    let ctx_val = json_to_cel_value(black_box(*json));
                    let mut context = cel_interpreter::Context::default();
                    let _ = context.add_variable("ctx", ctx_val);
                    program.execute(&context).unwrap()
                })
            },
        );
    }

    group.finish();
}

fn bench_throughput(c: &mut Criterion) {
    let mut group = c.benchmark_group("throughput");

    let program = cel_interpreter::Program::compile("ctx.value > 50 && ctx.active").unwrap();

    let contexts: Vec<serde_json::Value> = (0..1000)
        .map(|i| {
            serde_json::json!({
                "value": i % 100,
                "active": i % 3 != 0
            })
        })
        .collect();

    let cel_contexts: Vec<cel_interpreter::Value> =
        contexts.iter().map(json_to_cel_value).collect();

    group.throughput(Throughput::Elements(1000));

    group.bench_function("eval_1000_contexts", |b| {
        b.iter(|| {
            let mut matches = 0;
            for ctx_val in &cel_contexts {
                let mut context = cel_interpreter::Context::default();
                let _ = context.add_variable("ctx", ctx_val.clone());
                if let cel_interpreter::Value::Bool(true) =
                    program.execute(black_box(&context)).unwrap()
                {
                    matches += 1;
                }
            }
            matches
        })
    });

    group.finish();
}

fn bench_complexity_scaling(c: &mut Criterion) {
    let mut group = c.benchmark_group("complexity_scaling");

    let expressions = [
        ("1_condition", "ctx.a > 0"),
        ("2_conditions", "ctx.a > 0 && ctx.b > 0"),
        ("3_conditions", "ctx.a > 0 && ctx.b > 0 && ctx.c > 0"),
        (
            "4_conditions",
            "ctx.a > 0 && ctx.b > 0 && ctx.c > 0 && ctx.d > 0",
        ),
        (
            "5_conditions",
            "ctx.a > 0 && ctx.b > 0 && ctx.c > 0 && ctx.d > 0 && ctx.e > 0",
        ),
    ];

    let context = serde_json::json!({
        "a": 10, "b": 20, "c": 30, "d": 40, "e": 50
    });
    let ctx_val = json_to_cel_value(&context);

    for (name, expr) in expressions {
        let program = cel_interpreter::Program::compile(expr).unwrap();
        group.bench_with_input(BenchmarkId::new("eval", name), &program, |b, program| {
            b.iter(|| {
                let mut context = cel_interpreter::Context::default();
                let _ = context.add_variable("ctx", ctx_val.clone());
                program.execute(black_box(&context)).unwrap()
            })
        });
    }

    group.finish();
}

criterion_group!(
    benches,
    bench_compilation,
    bench_evaluation,
    bench_json_conversion,
    bench_end_to_end,
    bench_throughput,
    bench_complexity_scaling,
);

criterion_main!(benches);