Skip to content

Queries & Filters ​

PocketDB provides a comprehensive set of filter operators for querying documents, with composable filters, query builders, and full sorting/pagination support.

Installation ​

Add PocketDB to your Cargo.toml:

toml
[dependencies]
pocketdb = "0.1"

Or via the command line:

bash
cargo add pocketdb

Comparison Operators ​

Equality ($eq) ​

rust
use pocketdb::{filter, PocketValue};

// Exact match using filter! macro
let results = users.find(&filter! { "name" => == "Alice" });

// Programmatic construction
let results = users.find(&Filter::eq("name", PocketValue::String("Alice".into())));

Not Equal ($ne) ​

rust
let results = users.find(&filter! { "status" => != "banned" });

Greater Than ($gt) ​

rust
let results = users.find(&filter! { "age" => > 18 });

Greater Than or Equal ($gte) ​

rust
let results = users.find(&filter! { "age" => >= 18 });

Less Than ($lt) ​

rust
let results = users.find(&filter! { "price" => < 100.0 });

Less Than or Equal ($lte) ​

rust
let results = users.find(&filter! { "price" => <= 50.0 });

Range Queries ​

Combine operators for range queries:

rust
use pocketdb::{filter, PocketValue};

// Price between $50 and $300
let results = users.find(&Filter::and(
    Filter::gte("price", PocketValue::Int(50)),
    Filter::lte("price", PocketValue::Int(300)),
));

// Age 18-65 inclusive
let results = users.find(&Filter::and(
    filter! { "age" => >= 18 },
    filter! { "age" => <= 65 },
));

List Operators ​

$in — Match Any Value in List ​

rust
use pocketdb::{filter, PocketValue};

let results = users.find(&filter! { "color" => == "red" });

// Or programmatically with multiple values:
let results = users.find(&Filter::in_list("status", vec![
    PocketValue::String("active".into()),
    PocketValue::String("pending".into()),
    PocketValue::String("verified".into()),
]));

$nin — Match None of the Values ​

rust
let results = users.find(&Filter::nin("role", vec![
    PocketValue::String("banned".into()),
    PocketValue::String("deleted".into()),
]));

Logical Operators ​

$and — Both Conditions Must Match ​

rust
use pocketdb::{filter, PocketValue};

// Using filter! macro (implicit AND for multiple conditions)
let results = users.find(&filter! { "age" => > 18, "active" => == true });

// Explicit AND
let results = users.find(&Filter::and(
    filter! { "age" => > 18 },
    filter! { "active" => == true },
));

// Chained AND
let results = users.find(&Filter::and(
    Filter::and(
        filter! { "age" => > 18 },
        filter! { "active" => == true },
    ),
    filter! { "verified" => == true },
));

$or — At Least One Condition Must Match ​

rust
let results = users.find(&Filter::or(
    filter! { "role" => == "admin" },
    filter! { "role" => == "superadmin" },
));

// Complex OR with AND
let results = users.find(&Filter::or(
    Filter::and(
        filter! { "category" => == "electronics" },
        filter! { "price" => < 100 },
    ),
    Filter::and(
        filter! { "category" => == "furniture" },
        filter! { "price" => > 200 },
    ),
));

$not — Negate a Condition ​

rust
let results = users.find(&Filter::not(filter! { "status" => == "banned" }));

// Not in a range
let results = users.find(&Filter::not(Filter::and(
    Filter::gte("age", PocketValue::Int(13)),
    Filter::lte("age", PocketValue::Int(17)),
)));

$nor — Neither Condition Matches ​

rust
let results = users.find(&Filter::nor(
    filter! { "status" => == "banned" },
    filter! { "status" => == "deleted" },
));

Element Operators ​

$exists — Check Field Existence ​

rust
// Field must exist
let results = users.find(&Filter::exists("email", true));

// Field must NOT exist
let results = users.find(&Filter::exists("phone", false));

$typeIs — Check Field Type ​

rust
// Field must be an integer
let results = users.find(&Filter::type_is("age", "int"));

// Field must be a string
let results = users.find(&Filter::type_is("name", "string"));

// Field must be a boolean
let results = users.find(&Filter::type_is("active", "bool"));

String Operators ​

$regex — Regular Expression Match ​

rust
// Case-insensitive substring match
let results = users.find(&Filter::regex("email", r"@example\.com$"));

// Name starts with 'Al'
let results = users.find(&Filter::regex("name", r"^Al"));

$startsWith — String Prefix ​

rust
let results = users.find(&Filter::starts_with("name", "Al"));
// Matches: "Alice", "Alex", "Alan"

$endsWith — String Suffix ​

rust
let results = users.find(&Filter::ends_with("email", "@example.com"));

$contains — Substring / Array Element ​

rust
use pocketdb::PocketValue;

// String contains substring
let results = users.find(&Filter::contains("bio", PocketValue::String("Rust".into())));

// Array contains element
let results = users.find(&Filter::contains("tags", PocketValue::String("featured".into())));

Array Operators ​

$all — Array Contains All Values ​

rust
use pocketdb::PocketValue;

let results = users.find(&Filter::all("tags", vec![
    PocketValue::String("rust".into()),
    PocketValue::String("database".into()),
]));

$size — Array Length ​

rust
// Array must have exactly 3 elements
let results = users.find(&Filter::size("tags", 3));

$elemMatch — Element Matches Sub-criteria ​

rust
let results = users.find(&Filter::elem_match("scores", vec![
    Filter::gte("_elem", PocketValue::Int(90)),
]));

Always-True Filter ​

Match all documents:

rust
let all = users.find(&Filter::always_true());

Query Builder (Chained API) ​

The find method returns results directly, but you can use the query builder for fluent chaining:

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

// Sort + skip + limit
let results = users.query(&filter! { "age" => > 18 })
    .sort("age", SortDirection::Asc)
    .skip(10)
    .limit(20)
    .exec();

// Count matching documents
let count = users.query(&filter! { "active" => == true })
    .count();

Sort ​

rust
use pocketdb::query::SortDirection;

// Sort by name ascending
let results = users.query(&Filter::always_true())
    .sort("name", SortDirection::Asc)
    .exec();

// Sort by name descending
let results = users.query(&Filter::always_true())
    .sort("name", SortDirection::Desc)
    .exec();

Skip & Limit (Pagination) ​

rust
// Page 1: items 1-10
let page1 = users.query(&Filter::always_true())
    .sort("name", SortDirection::Asc)
    .skip(0)
    .limit(10)
    .exec();

// Page 2: items 11-20
let page2 = users.query(&Filter::always_true())
    .sort("name", SortDirection::Asc)
    .skip(10)
    .limit(10)
    .exec();

Count ​

rust
let total = users.query(&filter! { "active" => == true }).count();

Find vs Query ​

MethodReturnsUse When
find(&filter)Vec<Document>Simple filter, no sorting/pagination needed
query(&filter)CollectionQueryNeed sort, skip, limit, or count
find_one(&filter)Option<Document>Need only the first match
count(&filter)Result<usize>Just need the count
distinct("field", &filter)Result<Vec<PocketValue>>Unique values of a field

Combining Filters — Complex Conditions ​

Filters are composable — build complex queries by combining simpler ones:

rust
use pocketdb::{filter, PocketValue};

// Active users who are either admins or over 25
let results = users.find(&Filter::and(
    Filter::eq("active", PocketValue::Bool(true)),
    Filter::or(
        Filter::eq("role", PocketValue::String("admin".into())),
        Filter::gt("age", PocketValue::Int(25)),
    ),
));

// Products: (Electronics under $100) OR (Furniture over $200) with stock
let results = products.find(&Filter::and(
    Filter::or(
        Filter::and(
            Filter::eq("category", PocketValue::String("electronics".into())),
            Filter::lt("price", PocketValue::Int(100)),
        ),
        Filter::and(
            Filter::eq("category", PocketValue::String("furniture".into())),
            Filter::gt("price", PocketValue::Int(200)),
        ),
    ),
    Filter::gt("stock", PocketValue::Int(0)),
));

// Users with email, name starting with 'A', or age under 21
let results = users.find(&Filter::or(
    Filter::and(
        Filter::exists("email", true),
        Filter::starts_with("name", "A"),
    ),
    Filter::lt("age", PocketValue::Int(21)),
));

Distinct Values ​

Get unique values of a field:

rust
use pocketdb::filter;

let cities = users.distinct("city", &filter::always_true())?;
for city in &cities {
    println!("City: {}", city);
}

// Distinct with filter
let active_cities = users.distinct("city", &filter! { "active" => == true })?;

Query Plans ​

Use explain to see which index a query uses:

rust
use pocketdb::{filter, PocketValue};

let plan = users.explain(&filter! { "email" => == "alice@example.com" });
println!("Scan: {:?}", plan.scan);       // IndexLookup or FullScan
println!("Index: {:?}", plan.index);     // Some("email_idx") or None
println!("Examined: {}", plan.examined); // Documents examined

Complete Example ​

rust
use pocketdb::{Database, doc, filter, PocketValue};
use pocketdb::query::{Filter, SortDirection};

fn main() -> pocketdb::Result<()> {
    let db = Database::open("queries_example.pdb")?;
    let products = db.collection("products")?;

    // Insert sample data
    products.insert(doc! { "name" => "Laptop", "price" => 999, "category" => "electronics", "stock" => 50 })?;
    products.insert(doc! { "name" => "Mouse", "price" => 25, "category" => "electronics", "stock" => 200 })?;
    products.insert(doc! { "name" => "Book", "price" => 20, "category" => "books", "stock" => 100 })?;
    products.insert(doc! { "name" => "Desk", "price" => 300, "category" => "furniture", "stock" => 15 })?;
    products.insert(doc! { "name" => "Chair", "price" => 150, "category" => "furniture", "stock" => 30 })?;

    // Range: electronics under $500
    let cheap_electronics = products.find(&Filter::and(
        Filter::eq("category", PocketValue::String("electronics".into())),
        Filter::lt("price", PocketValue::Int(500)),
    ));
    println!("Cheap electronics: {}", cheap_electronics.len());

    // OR: electronics or furniture
    let result = products.find(&Filter::or(
        Filter::eq("category", PocketValue::String("electronics".into())),
        Filter::eq("category", PocketValue::String("furniture".into())),
    ));
    println!("Electronics or furniture: {}", result.len());

    // Complex: (cheap electronics) OR (expensive furniture)
    let result = products.find(&Filter::or(
        Filter::and(
            Filter::eq("category", PocketValue::String("electronics".into())),
            Filter::lt("price", PocketValue::Int(100)),
        ),
        Filter::and(
            Filter::eq("category", PocketValue::String("furniture".into())),
            Filter::gt("price", PocketValue::Int(200)),
        ),
    ));

    // Exists check
    let has_stock = products.find(&Filter::exists("stock", true));

    // Sort + paginate
    let sorted = products.query(&Filter::always_true())
        .sort("price", SortDirection::Asc)
        .skip(0)
        .limit(3)
        .exec();

    // Count
    let count = products.count(&filter! { "category" => == "electronics" })?;
    println!("Electronics count: {}", count);

    // Distinct
    let categories = products.distinct("category", &Filter::always_true())?;
    println!("Categories: {:?}", categories);

    // Explain
    let plan = products.explain(&filter! { "name" => == "Laptop" });
    println!("Plan: {:?}", plan);

    Ok(())
}

Released under the MIT License. Built by Muhammad Fiaz.