Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More compact entity format - DeriveModel, DeriveModelColumn, DeriveModelPrimaryKey, DeriveEntity, DeriveRelation #122

Merged
merged 33 commits into from Sep 7, 2021

Conversation

tqwewe
Copy link
Contributor

@tqwewe tqwewe commented Sep 2, 2021

This PR aims to resolve #105.


It adds features to the existing:

  • DeriveModel
  • DeriveEntity

And adds new derives:

  • DeriveModelColumn
  • DeriveModelPrimaryKey
  • DeriveRelation

DeriveModel changes

Derive attributes:

#[sea(
    entity = Entity, // Specifies the entity struct for implementing ModelTrait. Default: Entity
)]

Field attributes: none

Summary:
DeriveModel still works the same, but includes an additional attribute entity which allows you to specify the Entity struct incase you've named it something other than Entity.

Example:

#[derive(DeriveModel)]
#[sea(entity = UserEntity)]
pub struct Model {
    pub id: Uuid,
    pub name: String,
}

// Generates
impl sea_orm::FromQueryResult for Model { ... }
impl sea_orm::ModelTrait for Model { ... }

DeriveEntity changes

Derive attributes:

#[sea(
    column = Column, // Specifies the column struct. Default: Column
    model = Model, // Specifies the model struct. Default: Model
    primary_key = PrimaryKey, // Specifies the primary key struct. Default: PrimaryKey
    relation = Relation, // Specifies the relation struct. Default: Relation
    schema_name = "public", // Define the entity's shema name. Optional
    table_name = "users", // Define the table name. If omitted, EntityName will not be implemented and should be done manually
)]

Field attributes: none

Summary:
DeriveEntity still works the same, but also implements EntityName if the table_name is set.

Example:

#[derive(DeriveEntity)]
#[sea(schema_name = "public", table_name = "users")]
pub struct Entity;

// Generates
impl sea_orm::entity::EntityName for Entity {
    fn schema_name(&self) -> Option<&str> { Some("public") }
    fn table_name(&self) -> &str { "users" }
}
impl sea_orm::entity::EntityTrait for Entity { ... }
impl sea_orm::Iden for Entity { ... }
impl sea_orm::IdenStatic for Entity { ... }

DeriveModelColumn added

Derive attributes:

#[sea(
    column = Column, // Specifies the column struct. Default: Column
    entity = Entity, // Specifies the entity struct. Default: Entity
)]

Field attributes:

#[sea(
    column_type = "String(Some(255))", // Specifies the field's ColumnType. Default: detected by the field's type
    column_type_raw = "ColumnDef::String(Some(255)).def()", // Specifies the field's ColumnType with raw code. Optional
    indexed, // Adds .indexed() to the column def. Optional
    null, // Adds .null() to the column def. Optional
    unique, // Adds .unique() to the column def. Optional
)]

Summary:
Generate's the column enum based on the field names.
The column enum implements ColumnTrait, FromStr, Iden & IdenStatic. A method .as_str() is also added to it.

ColumnTrait is implemented on the column enum, and attempts to detect the type from field's Rust type. If the column_type attribute is added, it will use this type instead.

Example:

#[derive(DeriveModelColumn)]
#[sea(entity = UserEntity)]
pub struct Model {
    pub id: Uuid,
    pub name: String,
    #[sea(column_type = "String(Some(255))", null, unique)]
    pub email: Email,
    #[sea(column_type_raw = "ColumnDef::String(None).def()")]
    pub password: String,
}

// Generates
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Column {
    Id,
    Name,
    Email,
    Password,
}
impl Column { fn as_str(&self) -> &str { ... } }
impl sea_orm::entity::ColumnTrait for Column { ... }
impl std::str::FromStr for Column { ... }
impl sea_orm::Iden for Column { ... }
impl sea_orm::IdenStatic for Column { ... }

Mapping for rust types into ColumnDef types can be found here: https://github.com/SeaQL/sea-orm/pull/122/files#r702886781


DeriveModelPrimaryKey added

Derive attributes:

#[sea(
    column = Column, // Specifies the column struct. Default: Column
    primary_key = PrimaryKey, // Specifies the primary key struct. Default: PrimaryKey
)]

Field attributes:

#[sea(
    auto_increment = true, // Indicated the primary key is auto incrementing. Default: true
    primary_key, // Marks the field as a primary key.
)]

Summary:
Generates the PrimaryKey enum based on the fields marked with primary_key. Implements Iden, IdenStatic, PrimaryKeyToColumn & PrimaryKeyTrait on the enum. Additionally adds a .as_str() method.

PrimaryKeyTrait is implemented and uses the first field marked as primary_key for the ValueType.
If there are multiple primary keys, auto_increment will be false, otherwise true by default with the ability to change this with the auto_increment attribute.

Example:

#[derive(DeriveModelPrimaryKey)]
pub struct Model {
    #[sea(primary_key)]
    pub id: Uuid,
    pub name: String,
}

// Generates
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum PrimaryKey {
    Id,
}
impl sea_orm::entity::PrimaryKeyTrait for PrimaryKey {
    type ValueType = Uuid;
    fn auto_increment() -> bool { true }
}
impl PrimaryKey { fn as_str(&self) -> &str { ... } }
impl sea_orm::Iden for PrimaryKey { ... }
impl sea_orm::IdenStatic for PrimaryKey { ... }
impl sea_orm::PrimaryKeyToColumn for PrimaryKey { ... }

DeriveRelation added

Derive attributes:

#[sea(
    entity = Entity, // Specifies the entity struct. Default: Entity
)]

Field attributes:

#[sea(
    belongs_to = "cake::Entity", // Specifies the entity the field belonds to. Required
    from = "Column::CakeId", // Specifies the from column. Required
    to = "cake::Column::Id", // Specifies the to column. Required
)]

Summary:
Implements RelationTrait on the enum.
Each variant is required to have the field attributes listed above, and generates the RelationTrait based on the variants.

Example:

#[derive(DeriveRelation)]
#[sea(entity = Entity)]
pub enum Relation {
    #[sea(belongs_to = "cake::Entity", from = "Column::CakeId", to = "cake::Column::Id")]
    Cake,
}

// Generates
impl RelationTrait for Relation {
    fn def(&self) -> RelationDef {
        match self {
            Self::Cake => Entity::belongs_to(cake::Entity)
                .from(Column::CakeId)
                .to(cake::Column::Id)
                .into(),
        }
    }
}

Example of all derives

#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
#[sea(schema_name = "public", table_name = "users")]
pub struct Entity;

#[derive(
    Clone,
    Debug,
    DeriveActiveModel,
    DeriveActiveModelBehavior,
    DeriveModel,
    DeriveModelColumn,
    DeriveModelPrimaryKey,
)]
pub struct Model {
    #[sea(primary_key)]
    pub id: Uuid,
    pub name: String,
    pub age: u64,
    pub parent: Uuid,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
  #[sea_orm(belongs_to = "cake::Entity", from = "Column::CakeId", to = "cake::Column::Id")]
  Parent,
}

@tqwewe tqwewe changed the title SimpleModel derive for simplified models SimpleModel derive for simplified models Sep 2, 2021
@tqwewe tqwewe mentioned this pull request Sep 2, 2021
@tqwewe
Copy link
Contributor Author

tqwewe commented Sep 2, 2021

I'm thinking, instead of having SimpleModel generate a struct ModelInput, perhaps a new derive can be made SimpleInput which references the Model, and requires that all fields on the SimpleInput implement Into<T> of the matching field of the Model.

For example:

#[derive(Clone, Debug, SimpleModel)]
#[table(name = "users")]
pub struct User {
    #[primary_key]
    pub id: Uuid,
    pub email: String,
    pub password: String,
    pub full_name: Option<String>,
    pub age: Option<u64>,
}

#[derive(Clone, Debug, SimpleInput)]
#[input(model = "User")]
pub struct NewUser<'a> {
    pub email: &'a str,
    pub full_name: Option<&'a str>,
    pub age: Option<&'a str>, // Compiler would fail because `u64` does not implement `From<&str>`
}

The benefit of this, is that a check could be done at compile time to make sure that the types and fields in the NewUser input model are compatible with the original Model. And it would reduce bloating the api of the SimpleObject.

Removes the need for prelude when using `SimpleModel`
@tyt2y3
Copy link
Member

tyt2y3 commented Sep 2, 2021

Yes it makes sense. It's cleaner and more intuitive to separate the SimpleInput

@tyt2y3 tyt2y3 force-pushed the master branch 7 times, most recently from 71f7f40 to 9a1b771 Compare September 3, 2021 15:15
@tqwewe
Copy link
Contributor Author

tqwewe commented Sep 4, 2021

I've added the SimpleInput feature.

It's usage is dependant on a struct derived with SimpleModel being in the same module, and it checks the types are compatible with the Into trait only at compile time, and ensures there are no unknown fields in the SimpleInput which are not in the SimpleModel.

The following example will not compile:

#[derive(SimpleModel)]
#[table(name = "users")]
pub(crate) struct User {
    #[primary_key]
    pub id: Uuid,
    pub name: String,
}

#[derive(SimpleInput)]
#[input(model = User)]
pub(crate) struct NewSeo<'a> {
    pub name: u64, // Error: the trait bound `String: From<u64>` is not satisfied
}

The checking between types does not cause a compile error if one type is in an Option and the other is not.


I initially started implementing ActiveModelTrait for the SimpleInput, but realised that inserting only uses the ActiveModelTrait::take method, and does not use any others.. and instead of putting a panic! or unimplemented! method for the other methods, I decided to create a new trait called Insertable.

@tqwewe tqwewe changed the title SimpleModel derive for simplified models SimpleModel and SimpleInput derive for simplified models Sep 4, 2021
@tqwewe tqwewe changed the title SimpleModel and SimpleInput derive for simplified models More compact entity format - DeriveModel, DeriveModelColumn, DeriveModelPrimaryKey, DeriveRelation Sep 6, 2021
Comment on lines 243 to 269
fn type_to_column_type(ty: syn::Type) -> TokenStream {
let ty_string = ty.into_token_stream().to_string();
match ty_string.as_str() {
"std::string::String" | "string::String" | "String" => {
quote!(ColumnType::String(None))
}
"char" => quote!(ColumnType::Char(None)),
"i8" => quote!(ColumnType::TinyInteger),
"i16" => quote!(ColumnType::SmallInteger),
"i32" => quote!(ColumnType::Integer),
"i64" => quote!(ColumnType::BigInteger),
"f32" => quote!(ColumnType::Float),
"f64" => quote!(ColumnType::Double),
"Json" => quote!(ColumnType::Json),
"DateTime" => quote!(ColumnType::DateTime),
"DateTimeWithTimeZone" => quote!(ColumnType::DateTimeWithTimeZone),
"Decimal" => quote!(ColumnType::Decimal(None)),
"Uuid" => quote!(ColumnType::Uuid),
"Vec < u8 >" => quote!(ColumnType::Binary),
"bool" => quote!(ColumnType::Boolean),
_ => {
let ty_name = ty_string.replace(' ', "").to_snake_case();
quote!(ColumnType::Custom(#ty_name.to_owned()))
}
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust types converting into ColumnType can be found here.

@tqwewe tqwewe changed the title More compact entity format - DeriveModel, DeriveModelColumn, DeriveModelPrimaryKey, DeriveRelation More compact entity format - DeriveModel, DeriveModelColumn, DeriveModelPrimaryKey, DeriveEntity, DeriveRelation Sep 6, 2021
@tqwewe
Copy link
Contributor Author

tqwewe commented Sep 6, 2021

@tyt2y3 @billy1624 I think the PR is done if you want to review it.

@tyt2y3
Copy link
Member

tyt2y3 commented Sep 7, 2021

Thank you so much for the big contribution. I am going through it.

@tyt2y3 tyt2y3 changed the base branch from master to more-macros September 7, 2021 08:07
@tyt2y3
Copy link
Member

tyt2y3 commented Sep 7, 2021

Thank you so much for the contribution.

I really liked your coding style, especially for using a struct to hold all the parameters.

DeriveModel, DeriveEntity, DeriveRelation these are immensely useful.

As for DeriveModelColumn & DerivePrimaryKey, I prefer #128 's implementation strategy, where instead of impl the traits in-place, we expand it to the existing 'long format', and let the old macros continue expanding it.
This 'layered approach' frees us from having to maintain two sets of impl.

I will merge this to a separate branch, cherry-pick the bits, make some tweaks and integrate things together.
Hopefully after this we will have something desired by everybody.

@tyt2y3 tyt2y3 merged commit a9088fe into SeaQL:more-macros Sep 7, 2021
arpancodes pushed a commit to arpancodes/sea-orm that referenced this pull request Apr 8, 2022
Postgres chained concatenate expression
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compact Entity format
2 participants