Documetation for the entity module

This commit is contained in:
Charles Chege 2021-10-29 10:39:16 +03:00
parent 817e9bdd23
commit db098d1a03
10 changed files with 148 additions and 0 deletions

View File

@ -5,6 +5,7 @@ use async_trait::async_trait;
use sea_query::{Nullable, ValueTuple};
use std::fmt::Debug;
/// Defines a value from an ActiveModel and its state
#[derive(Clone, Debug, Default)]
pub struct ActiveValue<V>
where
@ -14,6 +15,7 @@ where
state: ActiveValueState,
}
/// Defines a set operation on an [ActiveValue]
#[allow(non_snake_case)]
pub fn Set<V>(v: V) -> ActiveValue<V>
where
@ -22,6 +24,7 @@ where
ActiveValue::set(v)
}
/// Defines an unset operation on an [ActiveValue]
#[allow(non_snake_case)]
pub fn Unset<V>(_: Option<bool>) -> ActiveValue<V>
where
@ -30,6 +33,7 @@ where
ActiveValue::unset()
}
// Defines the state of an [ActiveValue]
#[derive(Clone, Debug)]
enum ActiveValueState {
Set,
@ -51,22 +55,31 @@ where
ActiveValue::unchanged(value)
}
/// Enforces a set of constraints on any type performing an Create, Update or Delete operation
#[async_trait]
pub trait ActiveModelTrait: Clone + Debug {
/// Enforce the type to the constraints of the [EntityTrait]
type Entity: EntityTrait;
/// Get a mutable [ActiveValue] from an ActiveModel
fn take(&mut self, c: <Self::Entity as EntityTrait>::Column) -> ActiveValue<Value>;
/// Get a immutable [ActiveValue] from an ActiveModel
fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> ActiveValue<Value>;
/// Set the Value into an ActiveModel
fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value);
/// Set the state of an [ActiveValue] to the Unset state
fn unset(&mut self, c: <Self::Entity as EntityTrait>::Column);
/// Check the state of a [ActiveValue]
fn is_unset(&self, c: <Self::Entity as EntityTrait>::Column) -> bool;
/// The default implementation of the ActiveModel
fn default() -> Self;
/// Get the primary key of the ActiveModel
#[allow(clippy::question_mark)]
fn get_primary_key_value(&self) -> Option<ValueTuple> {
let mut cols = <Self::Entity as EntityTrait>::PrimaryKey::iter();
@ -103,6 +116,7 @@ pub trait ActiveModelTrait: Clone + Debug {
}
}
/// Perform an `INSERT` operation on the ActiveModel
async fn insert<'a, C>(self, db: &'a C) -> Result<Self, DbErr>
where
<Self::Entity as EntityTrait>::Model: IntoActiveModel<Self>,
@ -121,6 +135,7 @@ pub trait ActiveModelTrait: Clone + Debug {
ActiveModelBehavior::after_save(am, true)
}
/// Perform the `UPDATE` operation on an ActiveModel
async fn update<'a, C>(self, db: &'a C) -> Result<Self, DbErr>
where
Self: ActiveModelBehavior + 'a,
@ -170,6 +185,7 @@ pub trait ActiveModelTrait: Clone + Debug {
}
}
/// Enforce a set of constraints to a override the ActiveModel behavior
/// Behaviors for users to override
#[allow(unused_variables)]
pub trait ActiveModelBehavior: ActiveModelTrait {
@ -199,10 +215,12 @@ pub trait ActiveModelBehavior: ActiveModelTrait {
}
}
/// Enforce constraints for conversion to an ActiveModel
pub trait IntoActiveModel<A>
where
A: ActiveModelTrait,
{
/// Method to call to perform the conversion
fn into_active_model(self) -> A;
}
@ -215,10 +233,12 @@ where
}
}
/// Constraints to perform the conversion of a type into an [ActiveValue]
pub trait IntoActiveValue<V>
where
V: Into<Value>,
{
/// Method to perform the conversion
fn into_active_value(self) -> ActiveValue<V>;
}
@ -296,6 +316,7 @@ impl<V> ActiveValue<V>
where
V: Into<Value>,
{
/// Set the value of an [ActiveValue] and also set its state to `ActiveValueState::Set`
pub fn set(value: V) -> Self {
Self {
value: Some(value),
@ -303,6 +324,7 @@ where
}
}
/// Check if the state of an [ActiveValue] is `ActiveValueState::Set` which returns true
pub fn is_set(&self) -> bool {
matches!(self.state, ActiveValueState::Set)
}
@ -314,10 +336,14 @@ where
}
}
/// Check if the status of the [ActiveValue] is `ActiveValueState::Unchanged`
/// which returns `true` if it is
pub fn is_unchanged(&self) -> bool {
matches!(self.state, ActiveValueState::Unchanged)
}
/// Set the `value` field of the ActiveModel to [Option::None] and the
/// `state` field to `ActiveValueState::Unset`
pub fn unset() -> Self {
Self {
value: None,
@ -325,23 +351,30 @@ where
}
}
/// Check if the state of an [ActiveValue] is `ActiveValueState::Unset`
/// which returns true if it is
pub fn is_unset(&self) -> bool {
matches!(self.state, ActiveValueState::Unset)
}
/// Get the mutable value of the `value` field of an [ActiveValue]
/// also setting it's state to `ActiveValueState::Unset`
pub fn take(&mut self) -> Option<V> {
self.state = ActiveValueState::Unset;
self.value.take()
}
/// Get an owned value of the `value` field of the [ActiveValue]
pub fn unwrap(self) -> V {
self.value.unwrap()
}
/// Check is a [Value] exists or not
pub fn into_value(self) -> Option<Value> {
self.value.map(Into::into)
}
/// Wrap the [Value] into a `ActiveValue<Value>`
pub fn into_wrapped_value(self) -> ActiveValue<Value> {
match self.state {
ActiveValueState::Set => ActiveValue::set(self.into_value().unwrap()),

View File

@ -7,21 +7,28 @@ use sea_query::{Alias, Iden, IntoIden, IntoTableRef, IntoValueTuple, TableRef};
pub use sea_strum::IntoEnumIterator as Iterable;
use std::fmt::Debug;
/// Ensure the identifier for an Entity can be converted to a static str
pub trait IdenStatic: Iden + Copy + Debug + 'static {
/// Method to call to get the static string identity
fn as_str(&self) -> &str;
}
/// Enforces the naming of an entity to a set of constraints
pub trait EntityName: IdenStatic + Default {
/// Method to get the name for the schema, defaults to [Option::None] if not set
fn schema_name(&self) -> Option<&str> {
None
}
/// Get the name of the table
fn table_name(&self) -> &str;
/// Get the name of the module from the invoking `self.table_name()`
fn module_name(&self) -> &str {
self.table_name()
}
/// Get the [TableRef] from invoking the `self.schema_name()`
fn table_ref(&self) -> TableRef {
match self.schema_name() {
Some(schema) => (Alias::new(schema).into_iden(), self.into_iden()).into_table_ref(),
@ -43,14 +50,19 @@ pub trait EntityName: IdenStatic + Default {
/// - Update: `update`, `update_*`
/// - Delete: `delete`, `delete_*`
pub trait EntityTrait: EntityName {
#[allow(missing_docs)]
type Model: ModelTrait<Entity = Self> + FromQueryResult;
#[allow(missing_docs)]
type Column: ColumnTrait;
#[allow(missing_docs)]
type Relation: RelationTrait;
#[allow(missing_docs)]
type PrimaryKey: PrimaryKeyTrait + PrimaryKeyToColumn<Column = Self::Column>;
/// Check if the relation belongs to an Entity
fn belongs_to<R>(related: R) -> RelationBuilder<Self, R>
where
R: EntityTrait,
@ -58,6 +70,7 @@ pub trait EntityTrait: EntityName {
RelationBuilder::new(RelationType::HasOne, Self::default(), related, false)
}
/// Check if the entity has at least one relation
fn has_one<R>(_: R) -> RelationBuilder<Self, R>
where
R: EntityTrait + Related<Self>,
@ -65,6 +78,7 @@ pub trait EntityTrait: EntityName {
RelationBuilder::from_rel(RelationType::HasOne, R::to().rev(), true)
}
/// Chech if the Entity has many relations
fn has_many<R>(_: R) -> RelationBuilder<Self, R>
where
R: EntityTrait + Related<Self>,

View File

@ -2,6 +2,7 @@ use crate::{EntityName, IdenStatic, Iterable};
use sea_query::{DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value};
use std::str::FromStr;
/// Defines a Column for an Entity
#[derive(Debug, Clone, PartialEq)]
pub struct ColumnDef {
pub(crate) col_type: ColumnType,
@ -10,34 +11,62 @@ pub struct ColumnDef {
pub(crate) indexed: bool,
}
/// The type of column as defined in the SQL format
#[derive(Debug, Clone, PartialEq)]
pub enum ColumnType {
/// `CHAR` type of specified fixed length
Char(Option<u32>),
/// `STRING` type for variable string length
String(Option<u32>),
/// `TEXT` type used for large pieces of string data and stored out of row in case size is too big
Text,
/// `TINYINT` useful for storing one byte of data (range of 0-255)
TinyInteger,
/// `SMALLINT` data type stores small whole numbers that range from 32,767 to 32,767
SmallInteger,
/// `INTEGER` data types hold numbers that are whole, or without a decimal point
Integer,
/// `BIGINT` is a 64-bit representation of an integer taking up 8 bytes of storage and
/// ranging from -2^63 (-9,223,372,036,854,775,808) to 2^63 (9,223,372,036,854,775,807).
BigInteger,
/// `FLOAT` an approximate-number data type, where values range cannot be represented exactly.
Float,
/// `DOUBLE` is a normal-size floating point number where the
/// total number of digits is specified in size.
Double,
/// `DECIMAL` type store numbers that have fixed precision and scale
Decimal(Option<(u32, u32)>),
/// `DATETIME` type is used for values that contain both date and time parts.
DateTime,
/// `TIMESTAMP` is a temporal data type that holds the combination of date and time.
Timestamp,
/// `TIMESTAMP WITH TIME ZONE` (or `TIMESTAMPTZ`) data type stores 8-byte
/// date values that include timestamp and time zone information in UTC format.
TimestampWithTimeZone,
/// `TIME` data type defines a time of a day based on 24-hour clock
Time,
/// `DATE` data type stores the calendar date
Date,
/// `BINARY` data types contain byte strings—a sequence of octets or bytes.
Binary,
/// `BOOLEAN` is the result of a comparison operator
Boolean,
/// `MONEY` data type handles monetary data
Money(Option<(u32, u32)>),
/// `JSON` represents the JavaScript Object Notation type
Json,
/// JSON binary format is structured in the way that permits the server to search for
/// values within the JSON document directly by key or array index, which is very fast.
JsonBinary,
/// A custom implementation of a data type
Custom(String),
/// A Universally Unique IDentifier that is specified in RFC 4122
Uuid,
}
macro_rules! bind_oper {
( $op: ident ) => {
#[allow(missing_docs)]
fn $op<V>(&self, v: V) -> SimpleExpr
where
V: Into<Value>,
@ -58,6 +87,7 @@ macro_rules! bind_func_no_params {
macro_rules! bind_vec_func {
( $func: ident ) => {
#[allow(missing_docs)]
#[allow(clippy::wrong_self_convention)]
fn $func<V, I>(&self, v: I) -> SimpleExpr
where
@ -72,6 +102,7 @@ macro_rules! bind_vec_func {
macro_rules! bind_subquery_func {
( $func: ident ) => {
#[allow(clippy::wrong_self_convention)]
#[allow(missing_docs)]
fn $func(&self, s: SelectStatement) -> SimpleExpr {
Expr::tbl(self.entity_name(), *self).$func(s)
}
@ -81,14 +112,18 @@ macro_rules! bind_subquery_func {
// LINT: when the operand value does not match column type
/// Wrapper of the identically named method in [`sea_query::Expr`]
pub trait ColumnTrait: IdenStatic + Iterable + FromStr {
#[allow(missing_docs)]
type EntityName: EntityName;
/// Define a column for an Entity
fn def(&self) -> ColumnDef;
/// Get the name of the entity the column belongs to
fn entity_name(&self) -> DynIden {
SeaRc::new(Self::EntityName::default()) as DynIden
}
/// get the name of the entity the column belongs to
fn as_column_ref(&self) -> (DynIden, DynIden) {
(self.entity_name(), SeaRc::new(*self) as DynIden)
}
@ -221,6 +256,7 @@ pub trait ColumnTrait: IdenStatic + Iterable + FromStr {
bind_func_no_params!(is_null);
bind_func_no_params!(is_not_null);
/// Perform an operation if the column is null
fn if_null<V>(&self, v: V) -> SimpleExpr
where
V: Into<Value>,
@ -236,6 +272,7 @@ pub trait ColumnTrait: IdenStatic + Iterable + FromStr {
}
impl ColumnType {
/// instantiate a new [ColumnDef]
pub fn def(self) -> ColumnDef {
ColumnDef {
col_type: self,
@ -247,20 +284,24 @@ impl ColumnType {
}
impl ColumnDef {
/// Marks the column as `UNIQUE`
pub fn unique(mut self) -> Self {
self.unique = true;
self
}
/// Mark the column as nullable
pub fn null(self) -> Self {
self.nullable()
}
/// Mark the column as nullable
pub fn nullable(mut self) -> Self {
self.null = true;
self
}
/// Set the `indexed` field to `true`
pub fn indexed(mut self) -> Self {
self.indexed = true;
self

View File

@ -2,10 +2,14 @@ use crate::{ColumnTrait, EntityTrait, IdenStatic};
use sea_query::{Alias, DynIden, Iden, IntoIden, SeaRc};
use std::fmt;
/// Defines an operation for an Entity
#[derive(Debug, Clone)]
pub enum Identity {
/// Performs one operation
Unary(DynIden),
/// Performs two operations
Binary(DynIden, DynIden),
/// Performs three operations
Ternary(DynIden, DynIden, DynIden),
}
@ -28,14 +32,18 @@ impl Iden for Identity {
}
}
/// Performs a conversion into an [Identity]
pub trait IntoIdentity {
/// Method to perform the conversion
fn into_identity(self) -> Identity;
}
/// Check the [Identity] of an Entity
pub trait IdentityOf<E>
where
E: EntityTrait,
{
/// Method to call to perform this check
fn identity_of(self) -> Identity;
}

View File

@ -3,15 +3,21 @@ use crate::{
};
use sea_query::{Alias, IntoIden, JoinType, SeaRc};
/// Same as [RelationDef]
pub type LinkDef = RelationDef;
/// A set of constraints for links between Entities
pub trait Linked {
#[allow(missing_docs)]
type FromEntity: EntityTrait;
#[allow(missing_docs)]
type ToEntity: EntityTrait;
/// Link for an Entity
fn link(&self) -> Vec<LinkDef>;
/// Find all the Entities that are linked to the Entity
fn find_linked(&self) -> Select<Self::ToEntity> {
let mut select = Select::new();
for (i, rel) in self.link().into_iter().rev().enumerate() {

View File

@ -4,6 +4,7 @@ mod column;
mod identity;
mod link;
mod model;
/// Re-export common types from the entity
pub mod prelude;
mod primary_key;
mod relation;

View File

@ -5,13 +5,18 @@ use crate::{
pub use sea_query::Value;
use std::fmt::Debug;
/// A set of constraints for a Model
pub trait ModelTrait: Clone + Send + Debug {
#[allow(missing_docs)]
type Entity: EntityTrait;
/// Get the [Value] of a column from an Entity
fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> Value;
/// Set the [Value] of a column in an Entity
fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value);
/// Find related Models
fn find_related<R>(&self, _: R) -> Select<R>
where
R: EntityTrait,
@ -20,6 +25,7 @@ pub trait ModelTrait: Clone + Send + Debug {
<Self::Entity as Related<R>>::find_related().belongs_to(self)
}
/// Find linked Models
fn find_linked<L>(&self, l: L) -> Select<L::ToEntity>
where
L: Linked<FromEntity = Self::Entity>,
@ -29,9 +35,13 @@ pub trait ModelTrait: Clone + Send + Debug {
}
}
/// A set of constraints for implementing a [QueryResult]
pub trait FromQueryResult: Sized {
/// Instantiate a Model from a [QueryResult]
fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr>;
/// Transform the error from instantiating a Model from a [QueryResult]
/// and converting it to an [Option]
fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
Ok(Self::from_query_result(res, pre).ok())
}

View File

@ -23,6 +23,7 @@ pub use chrono::NaiveTime as Time;
#[cfg(feature = "with-chrono")]
pub use chrono::NaiveDateTime as DateTime;
/// Handles the time and dates
#[cfg(feature = "with-chrono")]
pub type DateTimeWithTimeZone = chrono::DateTime<chrono::FixedOffset>;

View File

@ -4,7 +4,9 @@ use sea_query::{FromValueTuple, IntoValueTuple};
use std::fmt::Debug;
//LINT: composite primary key cannot auto increment
/// A set of constraints to be used to define a Primary Key
pub trait PrimaryKeyTrait: IdenStatic + Iterable {
#[allow(missing_docs)]
type ValueType: Sized
+ Send
+ Debug
@ -14,14 +16,19 @@ pub trait PrimaryKeyTrait: IdenStatic + Iterable {
+ TryGetableMany
+ TryFromU64;
/// Method to call to perform `AUTOINCREMENT` operation on a Primary Kay
fn auto_increment() -> bool;
}
/// How to map a Primary Key to a column
pub trait PrimaryKeyToColumn {
#[allow(missing_docs)]
type Column: ColumnTrait;
/// Method to map a primary key to a column in an Entity
fn into_column(self) -> Self::Column;
/// Method to map a primary key from a column in an Entity
fn from_column(col: Self::Column) -> Option<Self>
where
Self: Sized;

View File

@ -3,45 +3,68 @@ use core::marker::PhantomData;
use sea_query::{JoinType, TableRef};
use std::fmt::Debug;
/// Defines the type of relationship
#[derive(Clone, Debug)]
pub enum RelationType {
/// An Entity has one relationship
HasOne,
/// An Entity has many relationships
HasMany,
}
/// Action to perform on a foreign key whenever there are changes
/// to an ActiveModel
pub type ForeignKeyAction = sea_query::ForeignKeyAction;
/// Constraints a type to implement the trait to create a relationship
pub trait RelationTrait: Iterable + Debug + 'static {
/// The method to call
fn def(&self) -> RelationDef;
}
/// Checks if Entities are related
pub trait Related<R>
where
R: EntityTrait,
{
/// Check if an entity is related to another entity
fn to() -> RelationDef;
/// Check if an entity is related through another entity
fn via() -> Option<RelationDef> {
None
}
/// Find related Entities
fn find_related() -> Select<R> {
Select::<R>::new().join_join_rev(JoinType::InnerJoin, Self::to(), Self::via())
}
}
/// Defines a relationship
#[derive(Debug)]
pub struct RelationDef {
/// The type of relationship defined in [RelationType]
pub rel_type: RelationType,
/// Reference from another Entity
pub from_tbl: TableRef,
/// Reference to another ENtity
pub to_tbl: TableRef,
/// Reference to from a Column
pub from_col: Identity,
/// Reference to another column
pub to_col: Identity,
/// Defines the owner of the Relation
pub is_owner: bool,
/// Defines an operation to be performed on a Foreign Key when a
/// `DELETE` Operation is performed
pub on_delete: Option<ForeignKeyAction>,
/// Defines an operation to be performed on a Foreign Key when a
/// `UPDATE` Operation is performed
pub on_update: Option<ForeignKeyAction>,
}
/// Defines a helper to build a relation
#[derive(Debug)]
pub struct RelationBuilder<E, R>
where
@ -108,6 +131,7 @@ where
}
}
/// Build a relationship from an Entity
pub fn from<T>(mut self, identifier: T) -> Self
where
T: IdentityOf<E>,
@ -116,6 +140,7 @@ where
self
}
/// Build a relationship to an Entity
pub fn to<T>(mut self, identifier: T) -> Self
where
T: IdentityOf<R>,
@ -124,11 +149,13 @@ where
self
}
/// An operation to perform on a foreign key when a delete operation occurs
pub fn on_delete(mut self, action: ForeignKeyAction) -> Self {
self.on_delete = Some(action);
self
}
/// An operation to perform on a foreign key when an update operation occurs
pub fn on_update(mut self, action: ForeignKeyAction) -> Self {
self.on_update = Some(action);
self