Merge branch 'master' into ss/actix-example
This commit is contained in:
commit
1a4392dd0b
@ -81,9 +81,7 @@ let pear = fruit::ActiveModel {
|
||||
};
|
||||
|
||||
// insert one
|
||||
let res = Fruit::insert(pear).exec(db).await?;
|
||||
|
||||
println!("InsertResult: {}", res.last_insert_id);
|
||||
let pear = pear.insert(db).await?;
|
||||
|
||||
// insert many
|
||||
Fruit::insert_many(vec![apple, pear]).exec(db).await?;
|
||||
|
@ -26,7 +26,7 @@ type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T
|
||||
mod post;
|
||||
pub use post::Entity as Post;
|
||||
|
||||
const DEFAULT_POSTS_PER_PAGE: usize = 25;
|
||||
const DEFAULT_POSTS_PER_PAGE: usize = 5;
|
||||
|
||||
#[get("/new")]
|
||||
async fn new() -> Template {
|
||||
@ -97,6 +97,7 @@ async fn list(
|
||||
posts: posts,
|
||||
flash: flash,
|
||||
page: page,
|
||||
posts_per_page: posts_per_page,
|
||||
num_pages: num_pages,
|
||||
},
|
||||
)
|
||||
|
@ -27,9 +27,9 @@
|
||||
<td></td>
|
||||
<td>
|
||||
{% if page == 0 %} Previous {% else %}
|
||||
<a href="/?page={{ page - 1 }}">Previous</a>
|
||||
<a href="/?page={{ page - 1 }}&posts_per_page={{ posts_per_page }}">Previous</a>
|
||||
{% endif %} | {% if page == num_pages - 1 %} Next {% else %}
|
||||
<a href="/?page={{ page + 1 }}">Next</a>
|
||||
<a href="/?page={{ page + 1 }}&posts_per_page={{ posts_per_page }}">Next</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td></td>
|
||||
|
@ -10,3 +10,5 @@ publish = false
|
||||
[dependencies]
|
||||
sea-orm = { path = "../../", features = [ "sqlx-all", "runtime-tokio-native-tls" ] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
env_logger = { version = "^0.9" }
|
||||
log = { version = "^0.4" }
|
||||
|
@ -3,7 +3,12 @@ use sea_orm::*;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
let db = Database::connect("sql://sea:sea@localhost/bakery")
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.is_test(true)
|
||||
.init();
|
||||
|
||||
let db = Database::connect("mysql://sea:sea@localhost/bakery")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -21,15 +21,17 @@ path = "src/main.rs"
|
||||
clap = { version = "^2.33.3" }
|
||||
dotenv = { version = "^0.15" }
|
||||
async-std = { version = "^1.9", features = [ "attributes" ] }
|
||||
sea-orm = { version = "^0.1.2", features = [ "sqlx-all" ] }
|
||||
sea-orm-codegen = { version = "^0.2.0" }
|
||||
sea-schema = { version = "^0.2.7", default-features = false, features = [
|
||||
sea-orm-codegen = { version = "^0.2.0", path = "../sea-orm-codegen" }
|
||||
sea-schema = { version = "^0.2.8", git = "https://github.com/SeaQL/sea-schema.git", default-features = false, features = [
|
||||
"debug-print",
|
||||
"sqlx-mysql",
|
||||
"sqlx-postgres",
|
||||
"discovery",
|
||||
"writer",
|
||||
] }
|
||||
sqlx = { version = "^0.5", default-features = false, features = [ "mysql", "postgres" ] }
|
||||
env_logger = { version = "^0.9" }
|
||||
log = { version = "^0.4" }
|
||||
|
||||
[features]
|
||||
default = [ "runtime-async-std-native-tls" ]
|
||||
|
@ -40,6 +40,20 @@ pub fn build_cli() -> App<'static, 'static> {
|
||||
.long("include-hidden-tables")
|
||||
.help("Generate entity file for hidden tables (i.e. table name starts with an underscore)")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("EXPANDED_FORMAT")
|
||||
.long("expanded-format")
|
||||
.help("Generate entity file of expanded format")
|
||||
.takes_value(false)
|
||||
.conflicts_with("COMPACT_FORMAT"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("COMPACT_FORMAT")
|
||||
.long("compact-format")
|
||||
.help("Generate entity file of compact format")
|
||||
.takes_value(false)
|
||||
.conflicts_with("EXPANDED_FORMAT"),
|
||||
),
|
||||
)
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp);
|
||||
@ -48,5 +62,13 @@ pub fn build_cli() -> App<'static, 'static> {
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.subcommand(entity_subcommand)
|
||||
.arg(
|
||||
Arg::with_name("VERBOSE")
|
||||
.long("verbose")
|
||||
.short("v")
|
||||
.help("Show debug messages")
|
||||
.takes_value(false)
|
||||
.global(true),
|
||||
)
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use clap::ArgMatches;
|
||||
use dotenv::dotenv;
|
||||
use log::LevelFilter;
|
||||
use sea_orm_codegen::{EntityTransformer, OutputFile};
|
||||
use std::{error::Error, fmt::Display, fs, io::Write, path::Path, process::Command};
|
||||
|
||||
@ -25,6 +26,7 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
|
||||
let url = args.value_of("DATABASE_URL").unwrap();
|
||||
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
|
||||
let include_hidden_tables = args.is_present("INCLUDE_HIDDEN_TABLES");
|
||||
let expanded_format = args.is_present("EXPANDED_FORMAT");
|
||||
let filter_hidden_tables = |table: &str| -> bool {
|
||||
if include_hidden_tables {
|
||||
true
|
||||
@ -32,6 +34,12 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
|
||||
!table.starts_with("_")
|
||||
}
|
||||
};
|
||||
if args.is_present("VERBOSE") {
|
||||
let _ = ::env_logger::builder()
|
||||
.filter_level(LevelFilter::Debug)
|
||||
.is_test(true)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
let table_stmts = if url.starts_with("mysql://") {
|
||||
use sea_schema::mysql::discovery::SchemaDiscovery;
|
||||
@ -66,7 +74,7 @@ async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Er
|
||||
panic!("This database is not supported ({})", url)
|
||||
};
|
||||
|
||||
let output = EntityTransformer::transform(table_stmts)?.generate();
|
||||
let output = EntityTransformer::transform(table_stmts)?.generate(expanded_format);
|
||||
|
||||
let dir = Path::new(output_dir);
|
||||
fs::create_dir_all(dir)?;
|
||||
|
@ -15,7 +15,7 @@ name = "sea_orm_codegen"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
sea-query = { version = "^0.15" }
|
||||
sea-query = { version = "^0.16.2" }
|
||||
syn = { version = "^1", default-features = false, features = [
|
||||
"derive",
|
||||
"parsing",
|
||||
@ -25,3 +25,6 @@ syn = { version = "^1", default-features = false, features = [
|
||||
quote = "^1"
|
||||
heck = "^0.3"
|
||||
proc-macro2 = "^1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { version = "^0.7" }
|
||||
|
@ -1,3 +1,4 @@
|
||||
ignore = [
|
||||
"tests/entity/*.rs",
|
||||
"tests/compact/*.rs",
|
||||
"tests/expanded/*.rs",
|
||||
]
|
@ -91,6 +91,10 @@ impl Entity {
|
||||
self.relations.iter().map(|rel| rel.get_def()).collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_attrs(&self) -> Vec<TokenStream> {
|
||||
self.relations.iter().map(|rel| rel.get_attrs()).collect()
|
||||
}
|
||||
|
||||
pub fn get_relation_rel_types(&self) -> Vec<Ident> {
|
||||
self.relations
|
||||
.iter()
|
||||
@ -168,7 +172,7 @@ impl Entity {
|
||||
mod tests {
|
||||
use crate::{Column, Entity, PrimaryKey, Relation, RelationType};
|
||||
use quote::format_ident;
|
||||
use sea_query::ColumnType;
|
||||
use sea_query::{ColumnType, ForeignKeyAction};
|
||||
|
||||
fn setup() -> Entity {
|
||||
Entity {
|
||||
@ -195,12 +199,16 @@ mod tests {
|
||||
columns: vec!["id".to_owned()],
|
||||
ref_columns: vec!["cake_id".to_owned()],
|
||||
rel_type: RelationType::HasOne,
|
||||
on_delete: Some(ForeignKeyAction::Cascade),
|
||||
on_update: Some(ForeignKeyAction::Cascade),
|
||||
},
|
||||
Relation {
|
||||
ref_table: "filling".to_owned(),
|
||||
columns: vec!["id".to_owned()],
|
||||
ref_columns: vec!["cake_id".to_owned()],
|
||||
rel_type: RelationType::HasOne,
|
||||
on_delete: Some(ForeignKeyAction::Cascade),
|
||||
on_update: Some(ForeignKeyAction::Cascade),
|
||||
},
|
||||
],
|
||||
conjunct_relations: vec![],
|
||||
@ -347,6 +355,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_relation_attrs() {
|
||||
let entity = setup();
|
||||
|
||||
for (i, elem) in entity.get_relation_attrs().into_iter().enumerate() {
|
||||
assert_eq!(
|
||||
elem.to_string(),
|
||||
entity.relations[i].get_attrs().to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_relation_rel_types() {
|
||||
let entity = setup();
|
||||
|
@ -52,6 +52,21 @@ impl Column {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_col_type_attrs(&self) -> Option<TokenStream> {
|
||||
let col_type = match &self.col_type {
|
||||
ColumnType::Float(Some(l)) => Some(format!("Float(Some({}))", l)),
|
||||
ColumnType::Double(Some(l)) => Some(format!("Double(Some({}))", l)),
|
||||
ColumnType::Decimal(Some((p, s))) => Some(format!("Decimal(Some(({}, {})))", p, s)),
|
||||
ColumnType::Money(Some((p, s))) => Some(format!("Money(Some({}, {}))", p, s)),
|
||||
ColumnType::Text => Some("Text".to_owned()),
|
||||
ColumnType::Custom(iden) => {
|
||||
Some(format!("Custom(\"{}\".to_owned())", iden.to_string()))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
col_type.map(|ty| quote! { column_type = #ty })
|
||||
}
|
||||
|
||||
pub fn get_def(&self) -> TokenStream {
|
||||
let mut col_def = match &self.col_type {
|
||||
ColumnType::Char(s) => match s {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use heck::{CamelCase, SnakeCase};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use sea_query::TableForeignKey;
|
||||
use sea_query::{ForeignKeyAction, TableForeignKey};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RelationType {
|
||||
@ -16,6 +16,8 @@ pub struct Relation {
|
||||
pub(crate) columns: Vec<String>,
|
||||
pub(crate) ref_columns: Vec<String>,
|
||||
pub(crate) rel_type: RelationType,
|
||||
pub(crate) on_update: Option<ForeignKeyAction>,
|
||||
pub(crate) on_delete: Option<ForeignKeyAction>,
|
||||
}
|
||||
|
||||
impl Relation {
|
||||
@ -49,6 +51,53 @@ impl Relation {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_attrs(&self) -> TokenStream {
|
||||
let rel_type = self.get_rel_type();
|
||||
let ref_table_snake_case = self.get_ref_table_snake_case();
|
||||
let ref_entity = format!("super::{}::Entity", ref_table_snake_case);
|
||||
match self.rel_type {
|
||||
RelationType::HasOne | RelationType::HasMany => {
|
||||
quote! {
|
||||
#[sea_orm(#rel_type = #ref_entity)]
|
||||
}
|
||||
}
|
||||
RelationType::BelongsTo => {
|
||||
let column_camel_case = self.get_column_camel_case();
|
||||
let ref_column_camel_case = self.get_ref_column_camel_case();
|
||||
let from = format!("Column::{}", column_camel_case);
|
||||
let to = format!(
|
||||
"super::{}::Column::{}",
|
||||
ref_table_snake_case, ref_column_camel_case
|
||||
);
|
||||
let on_update = if let Some(action) = &self.on_update {
|
||||
let action = Self::get_foreign_key_action(action);
|
||||
quote! {
|
||||
on_update = #action,
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
};
|
||||
let on_delete = if let Some(action) = &self.on_delete {
|
||||
let action = Self::get_foreign_key_action(action);
|
||||
quote! {
|
||||
on_delete = #action,
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
};
|
||||
quote! {
|
||||
#[sea_orm(
|
||||
#rel_type = #ref_entity,
|
||||
from = #from,
|
||||
to = #to,
|
||||
#on_update
|
||||
#on_delete
|
||||
)]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rel_type(&self) -> Ident {
|
||||
match self.rel_type {
|
||||
RelationType::HasOne => format_ident!("has_one"),
|
||||
@ -64,6 +113,17 @@ impl Relation {
|
||||
pub fn get_ref_column_camel_case(&self) -> Ident {
|
||||
format_ident!("{}", self.ref_columns[0].to_camel_case())
|
||||
}
|
||||
|
||||
pub fn get_foreign_key_action(action: &ForeignKeyAction) -> String {
|
||||
match action {
|
||||
ForeignKeyAction::Restrict => "Restrict",
|
||||
ForeignKeyAction::Cascade => "Cascade",
|
||||
ForeignKeyAction::SetNull => "SetNull",
|
||||
ForeignKeyAction::NoAction => "NoAction",
|
||||
ForeignKeyAction::SetDefault => "SetDefault",
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TableForeignKey> for Relation {
|
||||
@ -75,11 +135,15 @@ impl From<&TableForeignKey> for Relation {
|
||||
let columns = tbl_fk.get_columns();
|
||||
let ref_columns = tbl_fk.get_ref_columns();
|
||||
let rel_type = RelationType::BelongsTo;
|
||||
let on_delete = tbl_fk.get_on_delete();
|
||||
let on_update = tbl_fk.get_on_update();
|
||||
Self {
|
||||
ref_table,
|
||||
columns,
|
||||
ref_columns,
|
||||
rel_type,
|
||||
on_delete,
|
||||
on_update,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -88,6 +152,7 @@ impl From<&TableForeignKey> for Relation {
|
||||
mod tests {
|
||||
use crate::{Relation, RelationType};
|
||||
use proc_macro2::TokenStream;
|
||||
use sea_query::ForeignKeyAction;
|
||||
|
||||
fn setup() -> Vec<Relation> {
|
||||
vec![
|
||||
@ -96,18 +161,24 @@ mod tests {
|
||||
columns: vec!["id".to_owned()],
|
||||
ref_columns: vec!["cake_id".to_owned()],
|
||||
rel_type: RelationType::HasOne,
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
},
|
||||
Relation {
|
||||
ref_table: "filling".to_owned(),
|
||||
columns: vec!["filling_id".to_owned()],
|
||||
ref_columns: vec!["id".to_owned()],
|
||||
rel_type: RelationType::BelongsTo,
|
||||
on_delete: Some(ForeignKeyAction::Cascade),
|
||||
on_update: Some(ForeignKeyAction::Cascade),
|
||||
},
|
||||
Relation {
|
||||
ref_table: "filling".to_owned(),
|
||||
columns: vec!["filling_id".to_owned()],
|
||||
ref_columns: vec!["id".to_owned()],
|
||||
rel_type: RelationType::HasMany,
|
||||
on_delete: Some(ForeignKeyAction::Cascade),
|
||||
on_update: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::Entity;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{punctuated::Punctuated, token::Comma};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EntityWriter {
|
||||
@ -17,21 +18,25 @@ pub struct OutputFile {
|
||||
}
|
||||
|
||||
impl EntityWriter {
|
||||
pub fn generate(self) -> WriterOutput {
|
||||
pub fn generate(self, expanded_format: bool) -> WriterOutput {
|
||||
let mut files = Vec::new();
|
||||
files.extend(self.write_entities());
|
||||
files.extend(self.write_entities(expanded_format));
|
||||
files.push(self.write_mod());
|
||||
files.push(self.write_prelude());
|
||||
WriterOutput { files }
|
||||
}
|
||||
|
||||
pub fn write_entities(&self) -> Vec<OutputFile> {
|
||||
pub fn write_entities(&self, expanded_format: bool) -> Vec<OutputFile> {
|
||||
self.entities
|
||||
.iter()
|
||||
.map(|entity| {
|
||||
let mut lines = Vec::new();
|
||||
Self::write_doc_comment(&mut lines);
|
||||
let code_blocks = Self::gen_code_blocks(entity);
|
||||
let code_blocks = if expanded_format {
|
||||
Self::gen_expanded_code_blocks(entity)
|
||||
} else {
|
||||
Self::gen_compact_code_blocks(entity)
|
||||
};
|
||||
Self::write(&mut lines, code_blocks);
|
||||
OutputFile {
|
||||
name: format!("{}.rs", entity.get_table_name_snake_case()),
|
||||
@ -97,7 +102,7 @@ impl EntityWriter {
|
||||
lines.push("".to_owned());
|
||||
}
|
||||
|
||||
pub fn gen_code_blocks(entity: &Entity) -> Vec<TokenStream> {
|
||||
pub fn gen_expanded_code_blocks(entity: &Entity) -> Vec<TokenStream> {
|
||||
let mut code_blocks = vec![
|
||||
Self::gen_import(),
|
||||
Self::gen_entity_struct(),
|
||||
@ -116,6 +121,23 @@ impl EntityWriter {
|
||||
code_blocks
|
||||
}
|
||||
|
||||
pub fn gen_compact_code_blocks(entity: &Entity) -> Vec<TokenStream> {
|
||||
let mut code_blocks = vec![Self::gen_import(), Self::gen_compact_model_struct(entity)];
|
||||
let relation_defs = if entity.get_relation_ref_tables_camel_case().is_empty() {
|
||||
vec![
|
||||
Self::gen_relation_enum(entity),
|
||||
Self::gen_impl_relation_trait(entity),
|
||||
]
|
||||
} else {
|
||||
vec![Self::gen_compact_relation_enum(entity)]
|
||||
};
|
||||
code_blocks.extend(relation_defs);
|
||||
code_blocks.extend(Self::gen_impl_related(entity));
|
||||
code_blocks.extend(Self::gen_impl_conjunct_related(entity));
|
||||
code_blocks.extend(vec![Self::gen_impl_active_model_behavior()]);
|
||||
code_blocks
|
||||
}
|
||||
|
||||
pub fn gen_import() -> TokenStream {
|
||||
quote! {
|
||||
use sea_orm::entity::prelude::*;
|
||||
@ -297,6 +319,74 @@ impl EntityWriter {
|
||||
pub use super::#table_name_snake_case_ident::Entity as #table_name_camel_case_ident;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_compact_model_struct(entity: &Entity) -> TokenStream {
|
||||
let table_name = entity.table_name.as_str();
|
||||
let column_names_snake_case = entity.get_column_names_snake_case();
|
||||
let column_rs_types = entity.get_column_rs_types();
|
||||
let primary_keys: Vec<String> = entity
|
||||
.primary_keys
|
||||
.iter()
|
||||
.map(|pk| pk.name.clone())
|
||||
.collect();
|
||||
let attrs: Vec<TokenStream> = entity
|
||||
.columns
|
||||
.iter()
|
||||
.map(|col| {
|
||||
let mut attrs: Punctuated<_, Comma> = Punctuated::new();
|
||||
if primary_keys.contains(&col.name) {
|
||||
attrs.push(quote! { primary_key });
|
||||
if !col.auto_increment {
|
||||
attrs.push(quote! { auto_increment = false });
|
||||
}
|
||||
}
|
||||
if let Some(ts) = col.get_col_type_attrs() {
|
||||
attrs.extend(vec![ts]);
|
||||
if !col.not_null {
|
||||
attrs.push(quote! { nullable });
|
||||
}
|
||||
};
|
||||
if !attrs.is_empty() {
|
||||
let mut ts = TokenStream::new();
|
||||
for (i, attr) in attrs.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
ts = quote! { #ts, };
|
||||
}
|
||||
ts = quote! { #ts #attr };
|
||||
}
|
||||
quote! {
|
||||
#[sea_orm(#ts)]
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
quote! {
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = #table_name)]
|
||||
pub struct Model {
|
||||
#(
|
||||
#attrs
|
||||
pub #column_names_snake_case: #column_rs_types,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_compact_relation_enum(entity: &Entity) -> TokenStream {
|
||||
let relation_ref_tables_camel_case = entity.get_relation_ref_tables_camel_case();
|
||||
let attrs = entity.get_relation_attrs();
|
||||
quote! {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#(
|
||||
#attrs
|
||||
#relation_ref_tables_camel_case,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -304,18 +394,11 @@ mod tests {
|
||||
use crate::{
|
||||
Column, ConjunctRelation, Entity, EntityWriter, PrimaryKey, Relation, RelationType,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
use proc_macro2::TokenStream;
|
||||
use sea_query::ColumnType;
|
||||
use sea_query::{ColumnType, ForeignKeyAction};
|
||||
use std::io::{self, BufRead, BufReader};
|
||||
|
||||
const ENTITY_FILES: [&str; 5] = [
|
||||
include_str!("../../tests/entity/cake.rs"),
|
||||
include_str!("../../tests/entity/cake_filling.rs"),
|
||||
include_str!("../../tests/entity/filling.rs"),
|
||||
include_str!("../../tests/entity/fruit.rs"),
|
||||
include_str!("../../tests/entity/vendor.rs"),
|
||||
];
|
||||
|
||||
fn setup() -> Vec<Entity> {
|
||||
vec![
|
||||
Entity {
|
||||
@ -330,9 +413,9 @@ mod tests {
|
||||
},
|
||||
Column {
|
||||
name: "name".to_owned(),
|
||||
col_type: ColumnType::String(Some(255)),
|
||||
col_type: ColumnType::Text,
|
||||
auto_increment: false,
|
||||
not_null: true,
|
||||
not_null: false,
|
||||
unique: false,
|
||||
},
|
||||
],
|
||||
@ -341,6 +424,8 @@ mod tests {
|
||||
columns: vec![],
|
||||
ref_columns: vec![],
|
||||
rel_type: RelationType::HasMany,
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
}],
|
||||
conjunct_relations: vec![ConjunctRelation {
|
||||
via: "cake_filling".to_owned(),
|
||||
@ -374,12 +459,16 @@ mod tests {
|
||||
columns: vec!["cake_id".to_owned()],
|
||||
ref_columns: vec!["id".to_owned()],
|
||||
rel_type: RelationType::BelongsTo,
|
||||
on_delete: Some(ForeignKeyAction::Cascade),
|
||||
on_update: Some(ForeignKeyAction::Cascade),
|
||||
},
|
||||
Relation {
|
||||
ref_table: "filling".to_owned(),
|
||||
columns: vec!["filling_id".to_owned()],
|
||||
ref_columns: vec!["id".to_owned()],
|
||||
rel_type: RelationType::BelongsTo,
|
||||
on_delete: Some(ForeignKeyAction::Cascade),
|
||||
on_update: Some(ForeignKeyAction::Cascade),
|
||||
},
|
||||
],
|
||||
conjunct_relations: vec![],
|
||||
@ -450,12 +539,16 @@ mod tests {
|
||||
columns: vec!["cake_id".to_owned()],
|
||||
ref_columns: vec!["id".to_owned()],
|
||||
rel_type: RelationType::BelongsTo,
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
},
|
||||
Relation {
|
||||
ref_table: "vendor".to_owned(),
|
||||
columns: vec![],
|
||||
ref_columns: vec![],
|
||||
rel_type: RelationType::HasMany,
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
},
|
||||
],
|
||||
conjunct_relations: vec![],
|
||||
@ -493,6 +586,8 @@ mod tests {
|
||||
columns: vec!["fruit_id".to_owned()],
|
||||
ref_columns: vec!["id".to_owned()],
|
||||
rel_type: RelationType::BelongsTo,
|
||||
on_delete: None,
|
||||
on_update: None,
|
||||
}],
|
||||
conjunct_relations: vec![],
|
||||
primary_keys: vec![PrimaryKey {
|
||||
@ -503,8 +598,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gen_code_blocks() -> io::Result<()> {
|
||||
fn test_gen_expanded_code_blocks() -> io::Result<()> {
|
||||
let entities = setup();
|
||||
const ENTITY_FILES: [&str; 5] = [
|
||||
include_str!("../../tests/expanded/cake.rs"),
|
||||
include_str!("../../tests/expanded/cake_filling.rs"),
|
||||
include_str!("../../tests/expanded/filling.rs"),
|
||||
include_str!("../../tests/expanded/fruit.rs"),
|
||||
include_str!("../../tests/expanded/vendor.rs"),
|
||||
];
|
||||
|
||||
assert_eq!(entities.len(), ENTITY_FILES.len());
|
||||
|
||||
@ -521,7 +623,46 @@ mod tests {
|
||||
}
|
||||
let content = lines.join("");
|
||||
let expected: TokenStream = content.parse().unwrap();
|
||||
let generated = EntityWriter::gen_code_blocks(entity)
|
||||
let generated = EntityWriter::gen_expanded_code_blocks(entity)
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.fold(TokenStream::new(), |mut acc, tok| {
|
||||
acc.extend(tok);
|
||||
acc
|
||||
});
|
||||
assert_eq!(expected.to_string(), generated.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gen_compact_code_blocks() -> io::Result<()> {
|
||||
let entities = setup();
|
||||
const ENTITY_FILES: [&str; 5] = [
|
||||
include_str!("../../tests/compact/cake.rs"),
|
||||
include_str!("../../tests/compact/cake_filling.rs"),
|
||||
include_str!("../../tests/compact/filling.rs"),
|
||||
include_str!("../../tests/compact/fruit.rs"),
|
||||
include_str!("../../tests/compact/vendor.rs"),
|
||||
];
|
||||
|
||||
assert_eq!(entities.len(), ENTITY_FILES.len());
|
||||
|
||||
for (i, entity) in entities.iter().enumerate() {
|
||||
let mut reader = BufReader::new(ENTITY_FILES[i].as_bytes());
|
||||
let mut lines: Vec<String> = Vec::new();
|
||||
|
||||
reader.read_until(b';', &mut Vec::new())?;
|
||||
|
||||
let mut line = String::new();
|
||||
while reader.read_line(&mut line)? > 0 {
|
||||
lines.push(line.to_owned());
|
||||
line.clear();
|
||||
}
|
||||
let content = lines.join("");
|
||||
let expected: TokenStream = content.parse().unwrap();
|
||||
let generated = EntityWriter::gen_compact_code_blocks(entity)
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.fold(TokenStream::new(), |mut acc, tok| {
|
||||
|
35
sea-orm-codegen/tests/compact/cake.rs
Normal file
35
sea-orm-codegen/tests/compact/cake.rs
Normal file
@ -0,0 +1,35 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "cake")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub name: Option<String> ,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::fruit::Entity")]
|
||||
Fruit,
|
||||
}
|
||||
|
||||
impl Related<super::fruit::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Fruit.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::filling::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
super::cake_filling::Relation::Filling.def()
|
||||
}
|
||||
fn via() -> Option<RelationDef> {
|
||||
Some(super::cake_filling::Relation::Cake.def().rev())
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
46
sea-orm-codegen/tests/compact/cake_filling.rs
Normal file
46
sea-orm-codegen/tests/compact/cake_filling.rs
Normal file
@ -0,0 +1,46 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "_cake_filling_")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub cake_id: i32,
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub filling_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::cake::Entity",
|
||||
from = "Column::CakeId",
|
||||
to = "super::cake::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade",
|
||||
)]
|
||||
Cake,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::filling::Entity",
|
||||
from = "Column::FillingId",
|
||||
to = "super::filling::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade",
|
||||
)]
|
||||
Filling,
|
||||
}
|
||||
|
||||
impl Related<super::cake::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Cake.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::filling::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Filling.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
33
sea-orm-codegen/tests/compact/filling.rs
Normal file
33
sea-orm-codegen/tests/compact/filling.rs
Normal file
@ -0,0 +1,33 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "filling")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
_ => panic!("No RelationDef"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::cake::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
super::cake_filling::Relation::Cake.def()
|
||||
}
|
||||
fn via() -> Option<RelationDef> {
|
||||
Some(super::cake_filling::Relation::Filling.def().rev())
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
38
sea-orm-codegen/tests/compact/fruit.rs
Normal file
38
sea-orm-codegen/tests/compact/fruit.rs
Normal file
@ -0,0 +1,38 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "fruit")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub cake_id: Option<i32> ,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::cake::Entity",
|
||||
from = "Column::CakeId",
|
||||
to = "super::cake::Column::Id",
|
||||
)]
|
||||
Cake,
|
||||
#[sea_orm(has_many = "super::vendor::Entity")]
|
||||
Vendor,
|
||||
}
|
||||
|
||||
impl Related<super::cake::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Cake.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::vendor::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Vendor.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
30
sea-orm-codegen/tests/compact/vendor.rs
Normal file
30
sea-orm-codegen/tests/compact/vendor.rs
Normal file
@ -0,0 +1,30 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "vendor")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub fruit_id: Option<i32> ,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::fruit::Entity",
|
||||
from = "Column::FruitId",
|
||||
to = "super::fruit::Column::Id",
|
||||
)]
|
||||
Fruit,
|
||||
}
|
||||
|
||||
impl Related<super::fruit::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Fruit.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
@ -14,7 +14,7 @@ impl EntityName for Entity {
|
||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||
pub struct Model {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub name: Option<String> ,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
@ -46,7 +46,7 @@ impl ColumnTrait for Column {
|
||||
fn def(&self) -> ColumnDef {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer.def(),
|
||||
Self::Name => ColumnType::String(Some(255u32)).def(),
|
||||
Self::Name => ColumnType::Text.def().null(),
|
||||
}
|
||||
}
|
||||
}
|
9
sea-orm-codegen/tests/expanded/mod.rs
Normal file
9
sea-orm-codegen/tests/expanded/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod cake;
|
||||
pub mod cake_filling;
|
||||
pub mod filling;
|
||||
pub mod fruit;
|
||||
pub mod vendor;
|
7
sea-orm-codegen/tests/expanded/prelude.rs
Normal file
7
sea-orm-codegen/tests/expanded/prelude.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
|
||||
|
||||
pub use super::cake::Entity as Cake;
|
||||
pub use super::cake_filling::Entity as CakeFilling;
|
||||
pub use super::filling::Entity as Filling;
|
||||
pub use super::fruit::Entity as Fruit;
|
||||
pub use super::vendor::Entity as Vendor;
|
@ -1,3 +1,4 @@
|
||||
use crate::util::field_not_ignored;
|
||||
use heck::CamelCase;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
@ -14,7 +15,9 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
|
||||
ident.span() => compile_error!("you can only derive DeriveActiveModel on structs");
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
.into_iter()
|
||||
.filter(field_not_ignored);
|
||||
|
||||
let field: Vec<Ident> = fields
|
||||
.clone()
|
||||
|
@ -68,6 +68,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
|
||||
let mut default_value = None;
|
||||
let mut default_expr = None;
|
||||
let mut indexed = false;
|
||||
let mut ignore = false;
|
||||
let mut unique = false;
|
||||
let mut sql_type = None;
|
||||
// search for #[sea_orm(primary_key, auto_increment = false, column_type = "String(Some(255))", default_value = "new user", default_expr = "gen_random_uuid()", nullable, indexed, unique)]
|
||||
@ -120,7 +121,10 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
|
||||
}
|
||||
Meta::Path(p) => {
|
||||
if let Some(name) = p.get_ident() {
|
||||
if name == "primary_key" {
|
||||
if name == "ignore" {
|
||||
ignore = true;
|
||||
break;
|
||||
} else if name == "primary_key" {
|
||||
primary_keys.push(quote! { #field_name });
|
||||
primary_key_types.push(field.ty.clone());
|
||||
} else if name == "nullable" {
|
||||
@ -138,6 +142,11 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
|
||||
}
|
||||
}
|
||||
|
||||
if ignore {
|
||||
columns_enum.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
let field_type = match sql_type {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
@ -163,7 +172,9 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
|
||||
"bool" => quote! { Boolean },
|
||||
"NaiveDate" => quote! { Date },
|
||||
"NaiveTime" => quote! { Time },
|
||||
"DateTime" | "NaiveDateTime" => quote! { DateTime },
|
||||
"DateTime" | "NaiveDateTime" | "DateTimeWithTimeZone" => {
|
||||
quote! { DateTime }
|
||||
}
|
||||
"Uuid" => quote! { Uuid },
|
||||
"Json" => quote! { Json },
|
||||
"Decimal" => quote! { Decimal },
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::attributes::derive_attr;
|
||||
use crate::{attributes::derive_attr, util::field_not_ignored};
|
||||
use heck::CamelCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use std::iter::FromIterator;
|
||||
use syn::Ident;
|
||||
|
||||
enum Error {
|
||||
InputNotStruct,
|
||||
@ -14,6 +15,7 @@ struct DeriveModel {
|
||||
entity_ident: syn::Ident,
|
||||
field_idents: Vec<syn::Ident>,
|
||||
ident: syn::Ident,
|
||||
ignore_attrs: Vec<bool>,
|
||||
}
|
||||
|
||||
impl DeriveModel {
|
||||
@ -48,11 +50,17 @@ impl DeriveModel {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let ignore_attrs = fields
|
||||
.iter()
|
||||
.map(|field| !field_not_ignored(field))
|
||||
.collect();
|
||||
|
||||
Ok(DeriveModel {
|
||||
column_idents,
|
||||
entity_ident,
|
||||
field_idents,
|
||||
ident,
|
||||
ignore_attrs,
|
||||
})
|
||||
}
|
||||
|
||||
@ -70,23 +78,56 @@ impl DeriveModel {
|
||||
let ident = &self.ident;
|
||||
let field_idents = &self.field_idents;
|
||||
let column_idents = &self.column_idents;
|
||||
let field_values: Vec<TokenStream> = column_idents
|
||||
.iter()
|
||||
.zip(&self.ignore_attrs)
|
||||
.map(|(column_ident, ignore)| {
|
||||
if *ignore {
|
||||
quote! {
|
||||
Default::default()
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
row.try_get(pre, sea_orm::IdenStatic::as_str(&<<Self as sea_orm::ModelTrait>::Entity as sea_orm::entity::EntityTrait>::Column::#column_ident).into())?
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote!(
|
||||
impl sea_orm::FromQueryResult for #ident {
|
||||
fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> Result<Self, sea_orm::DbErr> {
|
||||
Ok(Self {
|
||||
#(#field_idents: row.try_get(pre, sea_orm::IdenStatic::as_str(&<<Self as sea_orm::ModelTrait>::Entity as sea_orm::entity::EntityTrait>::Column::#column_idents).into())?),*
|
||||
#(#field_idents: #field_values),*
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn impl_model_trait(&self) -> TokenStream {
|
||||
fn impl_model_trait<'a>(&'a self) -> TokenStream {
|
||||
let ident = &self.ident;
|
||||
let entity_ident = &self.entity_ident;
|
||||
let field_idents = &self.field_idents;
|
||||
let column_idents = &self.column_idents;
|
||||
let ignore_attrs = &self.ignore_attrs;
|
||||
let ignore = |(ident, ignore): (&'a Ident, &bool)| -> Option<&'a Ident> {
|
||||
if *ignore {
|
||||
None
|
||||
} else {
|
||||
Some(ident)
|
||||
}
|
||||
};
|
||||
let field_idents: Vec<&Ident> = self
|
||||
.field_idents
|
||||
.iter()
|
||||
.zip(ignore_attrs)
|
||||
.filter_map(ignore)
|
||||
.collect();
|
||||
let column_idents: Vec<&Ident> = self
|
||||
.column_idents
|
||||
.iter()
|
||||
.zip(ignore_attrs)
|
||||
.filter_map(ignore)
|
||||
.collect();
|
||||
|
||||
let missing_field_msg = format!("field does not exist on {}", ident);
|
||||
|
||||
|
@ -5,6 +5,7 @@ use syn::{parse_macro_input, DeriveInput, Error};
|
||||
|
||||
mod attributes;
|
||||
mod derives;
|
||||
mod util;
|
||||
|
||||
#[proc_macro_derive(DeriveEntity, attributes(sea_orm))]
|
||||
pub fn derive_entity(input: TokenStream) -> TokenStream {
|
||||
@ -73,7 +74,7 @@ pub fn derive_model(input: TokenStream) -> TokenStream {
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(DeriveActiveModel)]
|
||||
#[proc_macro_derive(DeriveActiveModel, attributes(sea_orm))]
|
||||
pub fn derive_active_model(input: TokenStream) -> TokenStream {
|
||||
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
|
||||
|
||||
|
26
sea-orm-macros/src/util.rs
Normal file
26
sea-orm-macros/src/util.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use syn::{punctuated::Punctuated, token::Comma, Field, Meta};
|
||||
|
||||
pub(crate) fn field_not_ignored(field: &Field) -> bool {
|
||||
for attr in field.attrs.iter() {
|
||||
if let Some(ident) = attr.path.get_ident() {
|
||||
if ident != "sea_orm" {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) {
|
||||
for meta in list.iter() {
|
||||
if let Meta::Path(path) = meta {
|
||||
if let Some(name) = path.get_ident() {
|
||||
if name == "ignore" {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
20
src/entity/link.rs
Normal file
20
src/entity/link.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use crate::{EntityTrait, QuerySelect, RelationDef, Select};
|
||||
use sea_query::JoinType;
|
||||
|
||||
pub type LinkDef = RelationDef;
|
||||
|
||||
pub trait Linked {
|
||||
type FromEntity: EntityTrait;
|
||||
|
||||
type ToEntity: EntityTrait;
|
||||
|
||||
fn link(&self) -> Vec<LinkDef>;
|
||||
|
||||
fn find_linked(&self) -> Select<Self::ToEntity> {
|
||||
let mut select = Select::new();
|
||||
for rel in self.link().into_iter().rev() {
|
||||
select = select.join_rev(JoinType::InnerJoin, rel);
|
||||
}
|
||||
select
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ mod active_model;
|
||||
mod base_entity;
|
||||
mod column;
|
||||
mod identity;
|
||||
mod link;
|
||||
mod model;
|
||||
pub mod prelude;
|
||||
mod primary_key;
|
||||
@ -11,6 +12,7 @@ pub use active_model::*;
|
||||
pub use base_entity::*;
|
||||
pub use column::*;
|
||||
pub use identity::*;
|
||||
pub use link::*;
|
||||
pub use model::*;
|
||||
// pub use prelude::*;
|
||||
pub use primary_key::*;
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::{DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Select};
|
||||
use crate::{
|
||||
DbErr, EntityTrait, Linked, QueryFilter, QueryResult, Related, Select, SelectModel,
|
||||
SelectorRaw, Statement,
|
||||
};
|
||||
pub use sea_query::Value;
|
||||
use std::fmt::Debug;
|
||||
|
||||
@ -25,15 +28,75 @@ pub trait ModelTrait: Clone + Send + Debug {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromQueryResult {
|
||||
fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr>
|
||||
where
|
||||
Self: Sized;
|
||||
pub trait FromQueryResult: Sized {
|
||||
fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr>;
|
||||
|
||||
fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
|
||||
Ok(Self::from_query_result(res, pre).ok())
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// # #[cfg(feature = "mock")]
|
||||
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
|
||||
/// #
|
||||
/// # let db = MockDatabase::new(DbBackend::Postgres)
|
||||
/// # .append_query_results(vec![vec![
|
||||
/// # maplit::btreemap! {
|
||||
/// # "name" => Into::<Value>::into("Chocolate Forest"),
|
||||
/// # "num_of_cakes" => Into::<Value>::into(1),
|
||||
/// # },
|
||||
/// # maplit::btreemap! {
|
||||
/// # "name" => Into::<Value>::into("New York Cheese"),
|
||||
/// # "num_of_cakes" => Into::<Value>::into(1),
|
||||
/// # },
|
||||
/// # ]])
|
||||
/// # .into_connection();
|
||||
/// #
|
||||
/// use sea_orm::{entity::*, query::*, tests_cfg::cake, FromQueryResult};
|
||||
///
|
||||
/// #[derive(Debug, PartialEq, FromQueryResult)]
|
||||
/// struct SelectResult {
|
||||
/// name: String,
|
||||
/// num_of_cakes: i32,
|
||||
/// }
|
||||
///
|
||||
/// # let _: Result<(), DbErr> = smol::block_on(async {
|
||||
/// #
|
||||
/// let res: Vec<SelectResult> = SelectResult::find_by_statement(Statement::from_sql_and_values(
|
||||
/// DbBackend::Postgres,
|
||||
/// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#,
|
||||
/// vec![],
|
||||
/// ))
|
||||
/// .all(&db)
|
||||
/// .await?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// res,
|
||||
/// vec![
|
||||
/// SelectResult {
|
||||
/// name: "Chocolate Forest".to_owned(),
|
||||
/// num_of_cakes: 1,
|
||||
/// },
|
||||
/// SelectResult {
|
||||
/// name: "New York Cheese".to_owned(),
|
||||
/// num_of_cakes: 1,
|
||||
/// },
|
||||
/// ]
|
||||
/// );
|
||||
/// #
|
||||
/// # Ok(())
|
||||
/// # });
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// db.into_transaction_log(),
|
||||
/// vec![Transaction::from_sql_and_values(
|
||||
/// DbBackend::Postgres,
|
||||
/// r#"SELECT "cake"."name", count("cake"."id") AS "num_of_cakes" FROM "cake""#,
|
||||
/// vec![]
|
||||
/// ),]
|
||||
/// );
|
||||
/// ```
|
||||
fn find_by_statement(stmt: Statement) -> SelectorRaw<SelectModel<Self>> {
|
||||
SelectorRaw::<SelectModel<Self>>::from_statement(stmt)
|
||||
}
|
||||
}
|
||||
|
@ -30,22 +30,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Linked {
|
||||
type FromEntity: EntityTrait;
|
||||
|
||||
type ToEntity: EntityTrait;
|
||||
|
||||
fn link(&self) -> Vec<RelationDef>;
|
||||
|
||||
fn find_linked(&self) -> Select<Self::ToEntity> {
|
||||
let mut select = Select::new();
|
||||
for rel in self.link().into_iter().rev() {
|
||||
select = select.join_rev(JoinType::InnerJoin, rel);
|
||||
}
|
||||
select
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RelationDef {
|
||||
pub rel_type: RelationType,
|
||||
|
@ -34,6 +34,7 @@ where
|
||||
where
|
||||
A: 'a,
|
||||
{
|
||||
// TODO: extract primary key's value from query
|
||||
// so that self is dropped before entering await
|
||||
let mut query = self.query;
|
||||
#[cfg(feature = "sqlx-postgres")]
|
||||
@ -48,6 +49,7 @@ where
|
||||
}
|
||||
}
|
||||
Inserter::<A>::new(query).exec(db)
|
||||
// TODO: return primary key if extracted before, otherwise use InsertResult
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::{debug_print, DbErr};
|
||||
#[cfg(feature = "mock")]
|
||||
use crate::debug_print;
|
||||
use crate::DbErr;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -263,6 +263,18 @@ impl<S> SelectorRaw<S>
|
||||
where
|
||||
S: SelectorTrait,
|
||||
{
|
||||
/// Create `SelectorRaw` from Statment. Executing this `SelectorRaw` will
|
||||
/// return a type `M` which implement `FromQueryResult`.
|
||||
pub fn from_statement<M>(stmt: Statement) -> SelectorRaw<SelectModel<M>>
|
||||
where
|
||||
M: FromQueryResult,
|
||||
{
|
||||
SelectorRaw {
|
||||
stmt,
|
||||
selector: SelectModel { model: PhantomData },
|
||||
}
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// # #[cfg(feature = "mock")]
|
||||
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
|
||||
|
@ -94,9 +94,7 @@
|
||||
//! };
|
||||
//!
|
||||
//! // insert one
|
||||
//! let res = Fruit::insert(pear).exec(db).await?;
|
||||
//!
|
||||
//! println!("InsertResult: {}", res.last_insert_id);
|
||||
//! let pear = pear.insert(db).await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! # async fn function2(db: &DbConn) -> Result<(), DbErr> {
|
||||
|
94
src/tests_cfg/cake_expanded.rs
Normal file
94
src/tests_cfg/cake_expanded.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use crate as sea_orm;
|
||||
use crate::entity::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"cake"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
|
||||
pub struct Model {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
Name,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
type ValueType = i32;
|
||||
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
Fruit,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
|
||||
fn def(&self) -> ColumnDef {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer.def(),
|
||||
Self::Name => ColumnType::String(None).def(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::Fruit => Entity::has_many(super::fruit::Entity).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::fruit::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Fruit.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::filling::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
super::cake_filling::Relation::Filling.def()
|
||||
}
|
||||
|
||||
fn via() -> Option<RelationDef> {
|
||||
Some(super::cake_filling::Relation::Cake.def().rev())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CakeToFilling;
|
||||
|
||||
impl Linked for CakeToFilling {
|
||||
type FromEntity = Entity;
|
||||
|
||||
type ToEntity = super::filling::Entity;
|
||||
|
||||
fn link(&self) -> Vec<RelationDef> {
|
||||
vec![
|
||||
super::cake_filling::Relation::Cake.def().rev(),
|
||||
super::cake_filling::Relation::Filling.def(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
@ -19,6 +19,8 @@ pub struct Model {
|
||||
pub cake_id: i32,
|
||||
pub filling_id: i32,
|
||||
pub price: Decimal,
|
||||
#[sea_orm(ignore)]
|
||||
pub ignored_attr: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
|
@ -9,6 +9,8 @@ pub struct Entity;
|
||||
pub struct Model {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
#[sea_orm(ignore)]
|
||||
pub ignored_attr: i32,
|
||||
}
|
||||
|
||||
// If your column names are not in snake-case, derive `DeriveCustomColumn` here.
|
||||
|
@ -18,6 +18,12 @@ pub enum Relation {
|
||||
to = "super::cake::Column::Id"
|
||||
)]
|
||||
Cake,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::cake_expanded::Entity",
|
||||
from = "Column::CakeId",
|
||||
to = "super::cake_expanded::Column::Id"
|
||||
)]
|
||||
CakeExpanded,
|
||||
}
|
||||
|
||||
impl Related<super::cake::Entity> for Entity {
|
||||
@ -26,4 +32,10 @@ impl Related<super::cake::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::cake_expanded::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::CakeExpanded.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
@ -1,12 +1,14 @@
|
||||
//! Configurations for test cases and examples. Not intended for actual use.
|
||||
|
||||
pub mod cake;
|
||||
pub mod cake_expanded;
|
||||
pub mod cake_filling;
|
||||
pub mod cake_filling_price;
|
||||
pub mod filling;
|
||||
pub mod fruit;
|
||||
|
||||
pub use cake::Entity as Cake;
|
||||
pub use cake_expanded::Entity as CakeExpanded;
|
||||
pub use cake_filling::Entity as CakeFilling;
|
||||
pub use cake_filling_price::Entity as CakeFillingPrice;
|
||||
pub use filling::Entity as Filling;
|
||||
|
Loading…
x
Reference in New Issue
Block a user