22 stable releases

3.2.1 Oct 23, 2023
3.2.0 Oct 22, 2023
2.0.2 Oct 16, 2023
1.4.1 Dec 4, 2022
1.3.2 Sep 19, 2022

#292 in Database interfaces

MIT and GPL-3.0 licenses

37KB
791 lines

Siena

Siena is data provider agnostic ORM for Rust, enabling you to easily use custom data stores for your application with all the niceties of a quering engine.

Siena comes built-in with a flat-file data provider, LocalProvider, supporting YAML and FrontMatter files, but you can easily create your own data provider by implementing the StoreProvider trait.

Install

Add the following to your Cargo.toml file:

siena = "3.0.0"

Changelog

To see what's changed, check the changelog.

Usage

Create store

The first thing you need to do when using Siena is creating the store. A store is an instance of Siena with the data provider set. A data provider is anything that implements the StoreProvider trait (so you can create your own!). Siena comes with the LocalProvider provider, which works on the local file system.

Example:

use siena::providers::local::LocalProvider;
use siena::siena::siena;

fn main() {
    let provider = LocalProvider { directory: "./path".to_string() };
    let store = siena(provider);
}

Fetching Records

Records are placed in collections. A collection is a directory in your store. So let's say that you have a collection called "blog-posts", you could fetch them like this:

let posts = store.collection("blog-posts").get_all();

You can also just get the first record via get_first() or the last one via .get_last().

Filtering Records

You can filter records using numerous when_* methods. And yes, you can chain them as much as you want.

when_is

To filter records by a record key that equals a given value, you can use the when_is method, like so:

let posts = store
    .collection("blog-posts")
    .when_is("status", "published")
    .get_all();

when_is_not

Similarly, to filter records the opposite way, by a record key that does not equal a given value, you can use the when_isnt method:

let posts = store
    .collection("blog-posts")
    .when_is_not("status", "published")
    .get_all();

when_has

To filter records by the presence of a record key, you can use the when_has method, like so:

let posts = store
    .collection("blog-posts")
    .when_has("status")
    .get_all();

when_has_not

Similarly, to filter records the opposite way, by the lack of a presence of a record key, you can use the when_hasnt method:

let posts = store
    .collection("blog-posts")
    .when_has_not("status")
    .get_all();

when_matches

To filter records by a record key that matches a value according to a Regex pattern, you can use the when_matches method, like so:

let posts = store
    .collection("blog-posts")
    .when_matches("date", r"2022\-09")
    .get_all();

There is no opposite method for when_matches, because regex gives you the ability to do that yourself.

Sorting Records

You can sort records with the sort method, like so:

use siena::siena::{RecordSortOrder};

let posts = store
    .collection("blog-posts")
    .sort("date", RecordSortOrder::Desc)
    .get_all();

The available ways to sort are:

  • RecordSortOrder::Desc
  • RecordSortOrder::Asc

Limiting Records

To limit the result, use the limit method:

let posts = store
    .collection("blog-posts")
    .limit(10)
    .get_all();

Offsetting Records

To offset the result, use the offset method:

let posts = store
    .collection("blog-posts")
    .offset(10)
    .get_all();

Pagination

With the combination of limit and offset method, you can create easy pagination, for example:

let page = 2;
let posts_per_page = 10;

let posts = store
    .collection("blog-posts")
    .offset((page - 1) * posts_per_page)
    .limit(posts_per_page)
    .get_all();

Or, simply use the paginate method which does this work for you, like this:

let posts = store
    .collection("blog-posts")
    .paginate(2, 10)
    .get_all();

Updating Records

You can update the result of your query via the set method. It doesn't matter if you have one record or multiple records, it will update anything that you have matching your query.

For example:

let posts = store
    .collection("blog-posts")
    .set(Vec::from([("status", "private")]));

This will update all the records in the blog-post collection by updating the status to private.

Whereas this example:

let posts = store
    .collection("blog-posts")
    .when_is("status", "public")
    .set(Vec::from([("status", "private")]));

Will only update all the records that have status as public to private.

Creating Records

The create method is what you use for creating a new record. Note however that the record is not persisted until you use the set method to add some data. The set method is the only method which writes data. The create method only creates the record in-memory so that the set method would know where to write data.

An example:

store
    .create("blog-posts", "hello-world")
    .set(Vec::from([("title", "Hello, World.")]));

The create method takes two arguments, the collection name, and the ID of the record, which has to be unique to that collection or it will overwrite an existing record.

Deleting Records

The delete method is what you use for deleting all the records matching a query, so for example if you want to delete all records matching the status "draft", you'd run this:

store
    .collection("blog-posts")
    .when_is("status", "draft")
    .delete();

Providers

LocalProvider

The LocalProvider is a provider that works on the local file system. It supports YAML and Markdown (FrontMatter) files. In the case of Markdown files, the Record's returned will have content and content_raw String entries, one for the rendered HTML and one for the raw Markdown, respectively.

Supported data types are:

  • String
  • usize
  • bool
  • HashMap<String, RecordData>
  • Vec<RecordData>

Custom Providers

You can create your own provider by implementing the StoreProvider trait. The trait has three methods that you need to implement:

pub trait StoreProvider {
    fn retrieve(&self, name: &str) -> Vec<Record>;
    fn set(&self, records: Vec<Record>, data: Vec<(&str, &RecordData)>) -> Vec<Record>;
    fn delete(&self, records: Vec<Record>);
}

The retrieve function

This function should take in a name of a data collection, e.g posts and return all Record's for that.

The set function

This function should take in a Vec<Record> and a Vec<(&str, &RecordData)> and return a Vec<Record>. The Vec<Record> is the records that you want to update, and the Vec<(&str, &RecordData)> is the data that you want to update them with. The &str is the key of the data, and the &RecordData is the value.

The delete function

This function should take in a Vec<Record> and delete them.

Dependencies

~7–17MB
~240K SLoC