Skip to content

Aggregation Pipelines ​

PocketDB supports a full aggregation pipeline for data analysis and transformation. Pipelines process documents through a sequence of stages, where each stage's output becomes the next stage's input.

Installation ​

Add PocketDB to your Cargo.toml:

toml
[dependencies]
pocketdb = "0.1"

Or via the command line:

bash
cargo add pocketdb

Pipeline Stages ​

$match — Filter Documents ​

Filters documents using a Filter expression:

rust
use pocketdb::query::AggregationPipeline;
use pocketdb::{filter, PocketValue};

let results = collection.aggregate()
    .match_(filter! { "age" => >= 18, "active" => == true })
    .execute(&collection.all_documents());

$sort — Order Documents ​

Sort by one or more fields:

rust
use pocketdb::query::{AggregationPipeline, SortField};

let results = collection.aggregate()
    .sort(vec![
        SortField::asc("name"),
        SortField::desc("age"),
    ])
    .execute(&collection.all_documents());

$limit — Limit Results ​

rust
let results = collection.aggregate()
    .limit(5)
    .execute(&collection.all_documents());

$skip — Skip Documents ​

rust
let results = collection.aggregate()
    .skip(10)
    .limit(10) // Pagination: page 2 with 10 items per page
    .execute(&collection.all_documents());

$project — Select Fields ​

Control which fields appear in results:

rust
use pocketdb::query::Projection;

// Include only specific fields
let results = collection.aggregate()
    .project(Projection::include(&["name", "email"]))
    .execute(&collection.all_documents());

// Exclude specific fields
let results = collection.aggregate()
    .project(Projection::exclude(&["password", "internal_id"]))
    .execute(&collection.all_documents());

$unwind — Deconstruct Arrays ​

Creates one document per array element:

rust
let results = collection.aggregate()
    .unwind("tags")
    .execute(&collection.all_documents());

// Input:  { "name": "Alice", "tags": ["rust", "db"] }
// Output: { "name": "Alice", "tags": "rust", "tags_index": 0 }
//         { "name": "Alice", "tags": "db", "tags_index": 1 }

$count — Count Documents ​

rust
let results = collection.aggregate()
    .count("total")
    .execute(&collection.all_documents());
// Returns: [{ "total": 42 }]

$sample — Random Selection ​

Randomly select N documents from the input:

rust
let results = collection.aggregate()
    .sample(5)
    .execute(&collection.all_documents());
// Returns 5 random documents

$set — Add/Modify Fields ​

Add new fields or overwrite existing ones:

rust
use pocketdb::PocketValue;
use std::collections::HashMap;

let mut fields = HashMap::new();
fields.insert("full_name".to_string(), PocketValue::String("Alice Smith".into()));

let results = collection.aggregate()
    .set(fields)
    .execute(&collection.all_documents());

$unset — Remove Fields ​

Remove specified fields from documents:

rust
let results = collection.aggregate()
    .unset(vec!["password".to_string(), "internal_id".to_string()])
    .execute(&collection.all_documents());

$replaceRoot — Replace Document Root ​

Replace the entire document with a field value:

rust
let results = collection.aggregate()
    .replace_root("address")
    .execute(&collection.all_documents());
// Input:  { "_id": "...", "name": "Alice", "address": { "city": "NYC" } }
// Output: { "city": "NYC" }

$sortByCount — Sort by Count ​

Sort documents by count in descending order (useful after $group):

rust
let results = collection.aggregate()
    .unwind("tags")
    .add_stage(PipelineStage::SortByCount)
    .execute(&collection.all_documents());

Grouping ​

$group — Group by Field ​

Group documents by a field and apply accumulators:

rust
use pocketdb::query::{AggregationPipeline, PipelineStage, Accumulator, AccumulatorOp};

let results = collection.aggregate()
    .add_stage(PipelineStage::Group {
        id: Some("department".to_string()),
        accumulators: vec![
            Accumulator {
                field: "avg_age".to_string(),
                op: AccumulatorOp::Avg,
                input_field: "age".to_string(),
            },
            Accumulator {
                field: "count".to_string(),
                op: AccumulatorOp::Count,
                input_field: "age".to_string(),
            },
        ],
    })
    .execute(&collection.all_documents());

Accumulator Operations ​

OperationDescriptionInput Type
SumSum of numeric valuesInt, Float
AvgAverage of numeric valuesInt, Float
MinMinimum valueAny ordered type
MaxMaximum valueAny ordered type
PushCollect values into an arrayAny
FirstFirst value in the groupAny
LastLast value in the groupAny
CountCount of documentsAny (uses _id)

Global Group (No ID) ​

Group all documents into a single result:

rust
let results = collection.aggregate()
    .add_stage(PipelineStage::Group {
        id: None, // No grouping field = global group
        accumulators: vec![
            Accumulator {
                field: "total".to_string(),
                op: AccumulatorOp::Count,
                input_field: "_id".to_string(),
            },
        ],
    })
    .execute(&collection.all_documents());

Group with Multiple Accumulators ​

rust
let results = collection.aggregate()
    .add_stage(PipelineStage::Group {
        id: Some("category".to_string()),
        accumulators: vec![
            Accumulator { field: "total_sales".to_string(), op: AccumulatorOp::Sum, input_field: "amount".to_string() },
            Accumulator { field: "avg_sale".to_string(), op: AccumulatorOp::Avg, input_field: "amount".to_string() },
            Accumulator { field: "min_sale".to_string(), op: AccumulatorOp::Min, input_field: "amount".to_string() },
            Accumulator { field: "max_sale".to_string(), op: AccumulatorOp::Max, input_field: "amount".to_string() },
            Accumulator { field: "deal_count".to_string(), op: AccumulatorOp::Count, input_field: "amount".to_string() },
        ],
    })
    .sort(vec![SortField::desc("total_sales")])
    .execute(&collection.all_documents());

Lookup (Joins) ​

$lookup — Join with Another Collection ​

rust
use pocketdb::query::relationship_lookup::{LookupStage, JoinType};

// Left outer join (default)
let results = collection.aggregate()
    .add_stage(PipelineStage::Lookup(
        LookupStage::new("orders", "user_id", "customer_id", "purchases")
            .with_join_type(JoinType::LeftOuter)
    ))
    .execute(&collection.all_documents());

See Joins Guide for all 4 join types.

Chaining Stages ​

Pipeline stages are executed in order — each stage's output becomes the next stage's input:

rust
let results = collection.aggregate()
    .match_(filter! { "status" => == "active" })  // 1. Filter
    .unwind("tags")                                // 2. Unwind arrays
    .add_stage(PipelineStage::Group {              // 3. Group
        id: Some("tags".to_string()),
        accumulators: vec![
            Accumulator {
                field: "count".to_string(),
                op: AccumulatorOp::Count,
                input_field: "tags".to_string(),
            },
        ],
    })
    .sort(vec![SortField::desc("count")])          // 4. Sort
    .limit(10)                                      // 5. Limit
    .execute(&collection.all_documents());

Complete Example ​

rust
use pocketdb::{Database, doc, filter, PocketValue};
use pocketdb::query::{AggregationPipeline, SortField, Accumulator, AccumulatorOp, PipelineStage};

fn analyze_sales(db: &Database) -> pocketdb::Result<()> {
    let sales = db.collection("sales")?;

    // Insert sample data
    sales.insert(doc! { "region" => "US", "product" => "A", "amount" => 100 })?;
    sales.insert(doc! { "region" => "US", "product" => "B", "amount" => 200 })?;
    sales.insert(doc! { "region" => "EU", "product" => "A", "amount" => 150 })?;
    sales.insert(doc! { "region" => "EU", "product" => "B", "amount" => 250 })?;

    // Group by region, sum amounts, sort by total descending
    let results = sales.aggregate()
        .add_stage(PipelineStage::Group {
            id: Some("region".to_string()),
            accumulators: vec![
                Accumulator {
                    field: "total_sales".to_string(),
                    op: AccumulatorOp::Sum,
                    input_field: "amount".to_string(),
                },
                Accumulator {
                    field: "deal_count".to_string(),
                    op: AccumulatorOp::Count,
                    input_field: "amount".to_string(),
                },
            ],
        })
        .sort(vec![SortField::desc("total_sales")])
        .execute(&sales.all_documents());

    for doc in &results {
        println!("{}", doc);
    }

    Ok(())
}

Pipeline Stage Reference ​

StageMethodDescription
$match.match_(filter)Filter documents
$sort.sort(fields)Order by fields
$limit.limit(n)Take first N
$skip.skip(n)Skip first N
$project.project(proj)Select/exclude fields
$unwind.unwind(field)Deconstruct arrays
$count.count(name)Count documents
$sample.sample(n)Random N documents
$set.set(fields)Add/modify fields
$unset.unset(fields)Remove fields
$replaceRoot.replace_root(field)Replace doc root
$sortByCount.add_stage(SortByCount)Sort by count desc
$group.add_stage(Group{...})Group by field
$lookup.add_stage(Lookup(...))Join collections

Released under the MIT License. Built by Muhammad Fiaz.