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 pocketdbComparison 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
| Method | Returns | Use When |
|---|---|---|
find(&filter) | Vec<Document> | Simple filter, no sorting/pagination needed |
query(&filter) | CollectionQuery | Need 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 examinedComplete 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(())
}