Stores

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

#documents#stores#nosql#files

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 error

Adds 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 error

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.

polodb_update(db_path: str, collection: str, filter: any, update: any) → intor an error

Changes 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 error

Removes every document matching filter ({} removes them all), and gives how many went.

polodb_count(db_path: str, collection: str) → intor an error

How 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.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/polodb.qwer$ curl -O https://quillolang.org/plugins/files/polodb.qwe

Then, in your program, all of it or only what you call:

main.qwe
use polodb.qweruse polodb_insert, polodb_find from polodb      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
polodb_core = "3.5"bson = "2"

Example

polodb.qwe
// 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

polodb.qwer
//! 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())}
Disk cache 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.cacheperformancefilesstores8 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.storescachecounterssettingsfiles7 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.databasenosqlcqlcluster3 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.storagefilesbackupapi4 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.filesstorageuploadsdedupeintegrity6 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.documentscouchdbreplicationchanges3 views · 7 downloads
Thread
78Useful? Like it, or add to its thread below.

Thread 0 posts

On PoloDB: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.