PoloDB
An embedded document database in one file with a MongoDB-like API: insert maps, find them with $gt and $in filters, update with $set and $inc.
Version 1.0.0 · 5 functions · 2 crates
Functions
What use polodb.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use polodb_insert, polodb_find from polodb (Quillo 0.6).
polodb_insert(db_path: str, collection: str, doc: any) → stror an errorAdds a document (a map) to collection in the database file at db_path, made if missing, and gives its new _id as text.
polodb_find(db_path: str, collection: str, filter: any, limit: int) → listor an errorThe documents matching filter, as maps with _id: {} for all, { city: "Oslo" }, { age: { "$gte": 18 } }, $in, $ne, $lt... At most limit of them (1 to 10,000), in the order they were added.
polodb_update(db_path: str, collection: str, filter: any, update: any) → intor an errorChanges every document matching filter with a MongoDB update ({ "$set": { paid: true } }, { "$inc": { stock: -1 } }), and gives how many changed.
polodb_delete(db_path: str, collection: str, filter: any) → intor an errorRemoves every document matching filter ({} removes them all), and gives how many went.
polodb_count(db_path: str, collection: str) → intor an errorHow many documents collection holds (0 for one never used).
Get it
$ quillo install polodb # polodb.qwer into this folder$ quillo install polodb --example # and its example, polodb.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/polodb.qwer$ curl -O https://quillolang.org/plugins/files/polodb.qweThen, in your program, all of it or only what you call:
use polodb.qweruse polodb_insert, polodb_find from polodb // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
polodb_core = "3.5"bson = "2"Example
// A small contacts book in a PoloDB file: add people, find them by filter, mark and remove them.//// quillo run polodb.qwe then open http://localhost:8080/contacts/seeduse "polodb.qwer"let DB = "data/contacts.polo"route GET /contacts/seed { polodb_delete(DB, "contacts", {})? polodb_insert(DB, "contacts", { name: "Ada", city: "London", age: 36, tags: ["maths"] })? polodb_insert(DB, "contacts", { name: "Grace", city: "New York", age: 45, tags: ["navy", "cobol"] })? polodb_insert(DB, "contacts", { name: "Linus", city: "Portland", age: 29, tags: [] })? { count: polodb_count(DB, "contacts")? }}route GET /contacts ? { min_age: int = 0 } = polodb_find(DB, "contacts", { age: { "$gte": min_age } }, 100)? | map((c) => { name: c.name, city: c.city, age: c.age })route GET /contacts/birthday ? { name: str } { let changed = polodb_update(DB, "contacts", { name: name }, { "$inc": { age: 1 } })? { changed, now: polodb_find(DB, "contacts", { name: name }, 1)?[0].age }}$ quillo run polodb.qwe$ curl 'localhost:8080/contacts/seed'$ curl 'localhost:8080/contacts?min_age=30'$ curl 'localhost:8080/contacts/birthday?name=Ada'Source
//! An embedded document database in one file, with a MongoDB-like API: collections of JSON documents, found and changed with `{ age: { "$gt": 30 } }` filters, no server to run.//! version: 1.0.0//!//! [dependencies]//! polodb_core = "3.5"//! bson = "2"use bson::{Bson, Document};use polodb_core::Database;use serde_json::Value;use std::collections::HashMap;use std::sync::{Arc, Mutex, OnceLock};const MAX_DOCS: usize = 10_000;/// One open database per file, kept for the life of the server (PoloDB locks its file).fn db(path: &str) -> Result<Arc<Database>, String> { static DBS: OnceLock<Mutex<HashMap<String, Arc<Database>>>> = OnceLock::new(); let mut dbs = DBS.get_or_init(|| Mutex::new(HashMap::new())).lock().unwrap_or_else(|e| e.into_inner()); if let Some(d) = dbs.get(path) { return Ok(d.clone()); } if let Some(dir) = std::path::Path::new(path).parent() { if !dir.as_os_str().is_empty() { std::fs::create_dir_all(dir).map_err(|e| e.to_string())?; } } let d = Arc::new(Database::open_file(path).map_err(|e| format!("{}: {}", path, e))?); dbs.insert(path.to_string(), d.clone()); Ok(d)}/// A JSON map as a BSON document; an `_id` of 24 hex digits becomes an ObjectId again.fn to_doc(v: &Value, what: &str) -> Result<Document, String> { if !v.is_object() { return Err(format!("the {} is a map, not {}", what, v)); } let mut d = bson::to_document(v).map_err(|e| e.to_string())?; if let Some(Bson::String(s)) = d.get("_id") { if let Ok(oid) = bson::oid::ObjectId::parse_str(s) { d.insert("_id", oid); } } Ok(d)}/// BSON back as plain JSON: ObjectIds are their hex text, dates their ISO text.fn to_json(b: Bson) -> Value { match b { Bson::ObjectId(o) => Value::String(o.to_hex()), Bson::DateTime(d) => Value::String(d.try_to_rfc3339_string().unwrap_or_default()), Bson::Int32(i) => Value::from(i), Bson::Int64(i) => Value::from(i), Bson::Document(d) => Value::Object(d.into_iter().map(|(k, v)| (k, to_json(v))).collect()), Bson::Array(xs) => Value::Array(xs.into_iter().map(to_json).collect()), other => other.into_relaxed_extjson(), }}/// Adds a document (a map) to `collection` in the database file at `db_path`, made if missing, and gives its new `_id` as text.pub fn polodb_insert(db_path: &str, collection: &str, doc: serde_json::Value) -> Result<String, String> { let d = db(db_path)?; let r = d.collection::<Document>(collection).insert_one(to_doc(&doc, "document")?).map_err(|e| e.to_string())?; Ok(match r.inserted_id { Bson::ObjectId(o) => o.to_hex(), other => to_json(other).to_string(), })}/// The documents matching `filter`, as maps with `_id`: `{}` for all, `{ city: "Oslo" }`, `{ age: { "$gte": 18 } }`, `$in`, `$ne`, `$lt`.../// At most `limit` of them (1 to 10,000), in the order they were added.pub fn polodb_find(db_path: &str, collection: &str, filter: serde_json::Value, limit: i64) -> Result<Vec<Value>, String> { let d = db(db_path)?; let f = to_doc(&filter, "filter")?; let docs = d.collection::<Document>(collection).find_many(if f.is_empty() { None } else { Some(f) }).map_err(|e| e.to_string())?; Ok(docs.into_iter().take(limit.clamp(1, MAX_DOCS as i64) as usize).map(|d| to_json(Bson::Document(d))).collect())}/// Changes every document matching `filter` with a MongoDB update (`{ "$set": { paid: true } }`, `{ "$inc": { stock: -1 } }`), and gives how many changed.pub fn polodb_update(db_path: &str, collection: &str, filter: serde_json::Value, update: serde_json::Value) -> Result<i64, String> { let d = db(db_path)?; let r = d.collection::<Document>(collection).update_many(to_doc(&filter, "filter")?, to_doc(&update, "update")?).map_err(|e| e.to_string())?; Ok(r.modified_count as i64)}/// Removes every document matching `filter` (`{}` removes them all), and gives how many went.pub fn polodb_delete(db_path: &str, collection: &str, filter: serde_json::Value) -> Result<i64, String> { let d = db(db_path)?; let r = d.collection::<Document>(collection).delete_many(to_doc(&filter, "filter")?).map_err(|e| e.to_string())?; Ok(r.deleted_count as i64)}/// How many documents `collection` holds (0 for one never used).pub fn polodb_count(db_path: &str, collection: &str) -> Result<i64, String> { let d = db(db_path)?; d.collection::<Document>(collection).count_documents().map(|n| n as i64).map_err(|e| e.to_string())}Related
cache_disk.qwer · 1.0.0A disk cache for slow answers: text under a key with a lifetime, stored by content hash and checked when read.8 views · 8 downloadsKey-value store kv.qwer · 1.0.0A fast key-value store in one file: values with an optional lifetime, atomic counters, and keys listed by prefix, kept across restarts.7 views · 6 downloadsApache Cassandra cassandra.qwer · 1.0.0Talk to Cassandra or ScyllaDB by the CQL binary protocol v4, by hand: password login, plain and prepared statements with an Unprepared answer prepared again, batches, rows read by type down to UDTs and decimals, paging state followed, and server errors with their codes.3 views · 7 downloadsBackblaze B2 backblaze_b2.qwer · 1.0.0Keep files on Backblaze B2 through its native API: uploads with their SHA-1, large files in parts, upload URLs replaced when a pod is busy, download links and checked downloads, paged listings, and an expired token renewed without the program noticing.4 views · 6 downloadsContent-addressed files blob_store.qwer · 1.0.0Uploads kept in a folder by their SHA-256, as Git keeps objects: the same file stored once, names pointing at contents, atomic writes, every file verified against its hash, and contents nothing names swept away.6 views · 7 downloadsCouchDB couchdb.qwer · 1.0.0CouchDB over HTTP: documents with revisions, write conflicts reported, a merge that retries them, replication conflicts listed and resolved, bulk writes read document by document, Mango queries paged by bookmark, and the changes feed picked up from a saved sequence.3 views · 7 downloadsThread 0 posts
On PoloDB: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.