Documents & PocketValue
Documents are the primary data unit in PocketDB. Each document is an ordered map of field names to PocketValue instances, with a unique _id (ObjectId) field.
Installation
Add PocketDB to your Cargo.toml:
toml
[dependencies]
pocketdb = "0.1"Or via the command line:
bash
cargo add pocketdbDocument
Creating a Document
rust
use pocketdb::{Document, PocketValue};
// Create an empty document
let mut doc = Document::new();
// Set fields
doc.set("name", PocketValue::String("Alice".to_string()));
doc.set("age", PocketValue::Int(30));
doc.set("active", PocketValue::Bool(true));
doc.set("score", PocketValue::Float(95.5));Using the doc! Macro
The doc! macro provides a concise syntax for creating documents:
rust
use pocketdb::doc;
let d = doc! {
"name" => "Alice",
"age" => 30,
"active" => true,
"score" => 95.5
};Values are automatically converted to PocketValue via the From trait.
Accessing Fields
rust
// Get a reference to a field value
let name = doc.get("name"); // Option<&PocketValue>
// Get with type extraction
if let Some(age) = doc.get("age").and_then(|v| v.as_i64()) {
println!("Age: {}", age);
}
if let Some(name) = doc.get("name").and_then(|v| v.as_str()) {
println!("Name: {}", name);
}
if let Some(active) = doc.get("active").and_then(|v| v.as_bool()) {
println!("Active: {}", active);
}
if let Some(score) = doc.get("score").and_then(|v| v.as_f64()) {
println!("Score: {}", score);
}Nested Documents
rust
use pocketdb::doc;
let d = doc! {
"name" => "Alice",
"address" => {
"street" => "123 Main St",
"city" => "NYC",
"zip" => "10001"
},
"scores" => [95, 87, 92]
};
// Access nested fields
if let Some(obj) = d.get("address").and_then(|v| v.as_object()) {
if let Some(city) = obj.get("city").and_then(|v| v.as_str()) {
println!("City: {}", city);
}
}
// Access array elements
if let Some(arr) = d.get("scores").and_then(|v| v.as_array()) {
println!("First score: {:?}", arr.first());
println!("Total scores: {}", arr.len());
}Modifying Documents
rust
// Set a field (returns previous value if it existed)
let old = doc.set("name", PocketValue::String("Bob".to_string()));
// Remove a field
let removed = doc.remove("age");
// Check if a field exists
if doc.contains("email") {
// ...
}
// Merge fields from another document
let mut other = Document::new();
other.set("email", PocketValue::String("bob@example.com".to_string()));
doc.merge(&other);Document Metadata
Every document has built-in timestamps:
rust
// Creation timestamp (milliseconds since epoch)
let created = doc.created_at();
// Last modification timestamp
let updated = doc.updated_at();
// The document's unique ID
let id = doc.id(); // &ObjectId
let hex = doc.id_hex(); // String (24-char hex)
// Touch updates the updated_at timestamp
doc.touch();JSON Conversion
rust
// Convert to serde_json::Value
let json = doc.to_json();
// Create from serde_json::Value
let doc = Document::from_json(json)?;Display
rust
println!("{}", doc);
// Output: {"name": "Alice", "age": 30, "active": true}ObjectId
ObjectId is a 12-byte unique identifier composed of:
- 4-byte timestamp (seconds since custom epoch)
- 4-byte random component
- 4-byte atomic counter
rust
use pocketdb::ObjectId;
// Generate a new ObjectId
let id = ObjectId::new();
assert_eq!(id.to_hex().len(), 24);
// Parse from hex string
let parsed = ObjectId::from_hex("507f1f77bcf86cd799439011")?;
// Extract timestamp
let ts = id.timestamp_ms();
// Convert to/from bytes
let bytes = id.to_bytes();
let id2 = ObjectId::from_bytes(&bytes)?;
// Display
println!("{}", id); // ObjectId("507f1f77bcf86cd799439011")PocketValue
PocketValue is the enum representing all supported value types:
| Variant | Rust Type | Description |
|---|---|---|
Null | () | Null/absent value |
Bool(bool) | bool | Boolean |
Int(i64) | i64 | 64-bit signed integer |
Float(f64) | f64 | 64-bit float |
String(String) | String | UTF-8 string |
Array(Vec<PocketValue>) | Vec<PocketValue> | Ordered array |
Object(HashMap<String, PocketValue>) | HashMap | Embedded object |
ObjectId(ObjectId) | ObjectId | Document identifier |
Binary(Vec<u8>) | Vec<u8> | Raw binary data |
DateTime(i64) | i64 | Date/time as milliseconds since epoch |
Regex(String, String) | (String, String) | Regular expression (pattern, options) |
Decimal128(String) | String | 128-bit decimal (stored as string) |
Type Conversions
PocketValue implements From for common Rust types:
rust
use pocketdb::PocketValue;
let v1: PocketValue = "hello".into(); // String
let v2: PocketValue = 42i64.into(); // Int
let v3: PocketValue = 42u32.into(); // Int
let v4: PocketValue = 3.14f64.into(); // Float
let v5: PocketValue = true.into(); // Bool
let v6: PocketValue = serde_json::json!([1,2,3]).into(); // ArrayJSON Conversions
Convert between PocketValue and serde_json::Value:
rust
use pocketdb::PocketValue;
// PocketValue -> serde_json::Value
let pv = PocketValue::String("hello".into());
let json: serde_json::Value = pv.into();
// serde_json::Value -> PocketValue
let pv: PocketValue = serde_json::json!({
"name": "Alice",
"age": 30,
"tags": ["admin", "user"]
}).into();
// Special JSON types are preserved
let pv: PocketValue = serde_json::json!({ "$oid": "507f1f77bcf86cd799439011" }).into();
// -> PocketValue::ObjectId(...)Type Checking and Extraction
rust
let val = PocketValue::Int(42);
val.type_name() // "int"
val.is_null() // false
val.is_number() // true
val.as_i64() // Some(42)
val.as_f64() // Some(42.0)Ordering
PocketValues are ordered by type, then by value:
Null < Bool < Int < Float < String < Array < Object < ObjectId < Binary < DateTime < Regex < Decimal128Within the same type, natural ordering applies (numeric for numbers, lexicographic for strings).
Display
rust
println!("{}", PocketValue::Null); // null
println!("{}", PocketValue::Bool(true)); // true
println!("{}", PocketValue::Int(42)); // 42
println!("{}", PocketValue::Float(3.14)); // 3.14
println!("{}", PocketValue::String("hello".into())); // "hello"
println!("{}", PocketValue::Array(vec![].into())); // []
println!("{}", PocketValue::DateTime(1700000000000)); // DateTime(1700000000000)Complete Example
rust
use pocketdb::{Database, doc, Document, PocketValue};
fn main() -> pocketdb::Result<()> {
let db = Database::open("documents_example.pdb")?;
let users = db.collection("users")?;
// Create document with doc! macro
let id = users.insert(doc! {
"name" => "Alice",
"age" => 30,
"email" => "alice@example.com",
"active" => true,
"scores" => [95, 87, 92],
"address" => {
"city" => "NYC",
"zip" => "10001"
}
})?;
// Find and access
if let Some(user) = users.find_one(&pocketdb::query::Filter::eq(
"_id", PocketValue::ObjectId(id),
)) {
if let Some(name) = user.get("name").and_then(|v| v.as_str()) {
println!("Name: {}", name);
}
if let Some(arr) = user.get("scores").and_then(|v| v.as_array()) {
println!("Scores: {:?}", arr);
}
}
Ok(())
}