#tokio-postgres #orm #postgresql #sql #refinery

yanked oxidizer

Oxidizer helps you reduce the boiler plate of writing entities, tables & migrations when using tokio-postgres and refinery

4 releases

0.2.1 Aug 30, 2020
0.2.0 Aug 29, 2020
0.1.1 Aug 16, 2020
0.1.0 Aug 9, 2020

#6 in #refinery

MIT license

43KB
897 lines

Oxidizer

A simple orm based on tokio-postgres and refinery

#[async_trait]
pub trait Entity: Sized {
    async fn save(&mut self, db: &DB) -> DBResult<bool>;
    async fn delete(&mut self, db: &DB) -> DBResult<bool>;

    fn from_row(row: &Row) -> Self;
    fn create_migration() -> DBResult<Migration>;
    fn get_table_name() -> String;

    async fn find(db: &DB, query: &str, params: &'_ [&'_ (dyn ToSql + Sync)]) -> DBResult<Vec<Self>>;
    async fn first(db: &DB, query: &str, params: &'_ [&'_ (dyn ToSql + Sync)]) -> DBResult<Option<Self>>;
}
use oxidizer::*;
use chrono::{DateTime, Utc};

#[derive(Entity)]
#[derive(Default)]
pub struct MyEntity {
    #[primary_key]
    id: i32,

    name: String,

    #[indexed]
    integer: i32,
    integer64: i64,

    float: f32,
    double: f64,

    boolean: bool,

    datetime: Option<DateTime<Utc>>,
}

#[tokio::test]
async fn test_my_entity() {
    let uri = "postgres://postgres:alkje2lkaj2e@db/postgres";
    let max_open = 50; // mobc
    let ca_file: Option<&str> = None;
    let db = DB::connect(&uri, max_open, ca_file).await.unwrap();

    db.migrate_tables(&[MyEntity::create_migration().unwrap()]).await.unwrap();

    let mut entity = MyEntity::default();
    let creating = entity.save(&db).await.unwrap();
    assert_eq!(creating, true);
}

Attributes

Derive attributes can be used to create indexes, change the default table name and create reverse relation accessors

#[primary_key]

Required Field attribute used to mark the field as the primary key, this will make the field autoincrement

use oxidizer::*;
#[derive(Entity)]
struct Entity {
    #[primary_key]
    id: i32
}

#[indexed]

Make the specified field indexed in the db

use oxidizer::*;
#[derive(Entity)]
struct Entity {
    #[primary_key]
    id: i32,
    #[indexed]
    name: String,
}

#[relation]

See Relations

#[has_many]

See Relations

#[entity]

General settings for the entity struct

table_name: String;

Allows one to change the table name of the entity

use oxidizer::*;
#[derive(Entity)]
#[entity(table_name="custom_table_name")]
struct Entity {
    #[primary_key]
    id: i32
}

#[index]

Creates a custom index/constraint on one or more column

use oxidizer::*;
#[derive(Default, Entity)]
#[index(name="myindex", columns="name, email", unique)]
struct MyEntity {
    #[primary_key]
    id: i32,

    name: String,
    email: String,
}

#[field_ignore]

Ignores the specified field. The field type must implement the Default trait.

use oxidizer::*;
#[derive(Default, Entity)]
struct MyEntity {
    #[primary_key]
    id: i32,

    name: String,
    #[field_ignore]
    email: String,
}

#[custom_type]

The custom type attribute lets you override the default type provided by oxidizer.

use oxidizer::*;
pub enum MyEnum {
    Item1,
    Item2,
}

pub enum ConvertError {
    Error
}

impl std::fmt::Display for ConvertError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Error trying to convert")
    }
}

impl TryFrom<&MyEnum> for i32 {
    type Error = ConvertError;

    fn try_from(v: &MyEnum) -> Result<Self, Self::Error> {
        match v {
            MyEnum::Item1 => Ok(0),
            MyEnum::Item2 => Ok(1),
        }
    }
}

impl TryFrom<i32> for MyEnum {
    type Error = ConvertError;

    fn try_from(v: i32) -> Result<Self, Self::Error> {
        match v {
            0 => Ok(MyEnum::Item1),
            1 => Ok(MyEnum::Item2),
            _ => Err(ConvertError::Error),
        }
    }
}

#[derive(Entity)]
pub struct TestCustomType {
    #[primary_key]
    id: i32,

    #[custom_type(ty = "i32")]
    my_enum: MyEnum,
}

The custom type requires you to explicity implement the related TryFrom trait functions to convert between the actual type and the overriden type. The error type from the TryFrom trait must implement the std::fmt::Display trait

Relations

#[relation]

Relations can be created using the relation attribute as in the example:

use oxidizer::*;
#[derive(Entity)]
struct Entity {
    #[primary_key]
    id: i32,
}

#[derive(Entity)]
struct TestRelation {
    #[primary_key]
    id: i32,
    device_id: String,

    #[relation(model="Entity", key="id")]
    entity_id: i32,
}

This will implement for TestRelation the following generated trait:

#[oxidizer::async_trait]
pub trait __AccessorTestRelationToEntity {
    async fn get_test_entity(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult<Entity>;
    async fn set_test_entity(&mut self, db: &oxidizer::db::DB, v: &Entity) -> oxidizer::db::DBResult<()>;
}

#[has_many] 1-to-many or many-to-many relations can be achieved using the has_many attribute

basic (1-to-many)

use oxidizer::*;

#[derive(Entity)]
#[derive(Default)]
#[has_many(model="TargetEntity", field="entity_id")]
pub struct Entity {
    #[primary_key]
    id: i32,
    name: String
}

#[derive(Default, Entity)]
pub struct TargetEntity {
    #[primary_key]
    id: i32,
    #[relation(model="Entity", key="id")]
    entity_id: i32
}

This will create helper functions to access all the TargetEntity that Entity has. This is what the generated trait and implementation looks like (implementaion is also generated).

#[oxidizer::async_trait]
pub trait __AccessorHasManyTargetEntityToEntity {
    async fn get_all_test_entity(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult<Vec<Entity>>;
}

With a through table (many-to-many)

use oxidizer::*;

#[derive(Entity)]
#[derive(Default)]
pub struct Entity {
    #[primary_key]
    id: i32,
    name: String
}

#[derive(Default, Entity)]
#[has_many(model="Entity", field="entity_id", through="TestManyToMany")]
pub struct TargetEntity {
    #[primary_key]
    id: i32,
}

#[derive(Default, Entity)]
pub struct TestManyToMany {
    #[primary_key]
    id: i32,

    #[relation(model="TargetEntity", key="id")]
    target_id: i32,

    #[relation(model="Entity", key="id")]
    entity_id: i32,
}

This will create helper functions to access the related entities. This is what the generated trait looks like (implementaion is also generated):

#[oxidizer::async_trait]
pub trait __AccessorHasManyTargetEntityToEntity {
    async fn get_all_test_entity(&self, db: &oxidizer::db::DB) -> oxidizer::db::DBResult<Vec<TestManyToMany>>;
}

Dependencies

~18–31MB
~494K SLoC