From e86172cca39ef4963cf0b3d845136125f2744c27 Mon Sep 17 00:00:00 2001 From: Heikel Bouzayene Date: Wed, 11 Sep 2024 17:26:02 +0100 Subject: [PATCH 01/22] first commit --- Cargo.toml | 3 +- examples/mysql/Cargo.toml | 5 +- .../tests/offset_pagination_query_tests.rs | 137 +++++++++++ examples/postgres/Cargo.toml | 5 +- .../tests/offset_pagination_query_tests.rs | 137 +++++++++++ examples/sqlite/Cargo.toml | 5 +- .../tests/offset_pagination_query_tests.rs | 135 +++++++++++ generator/src/templates/actix_cargo.toml | 5 +- generator/src/templates/axum_cargo.toml | 5 +- generator/src/templates/poem_cargo.toml | 5 +- src/builder.rs | 36 +-- src/query/entity_object_relation.rs | 172 +++++++++----- src/query/entity_object_via_relation.rs | 223 ++++++++++++------ src/query/entity_query_field.rs | 122 ++++++++-- src/query/pagination.rs | 48 ++++ 15 files changed, 870 insertions(+), 173 deletions(-) create mode 100644 examples/mysql/tests/offset_pagination_query_tests.rs create mode 100644 examples/postgres/tests/offset_pagination_query_tests.rs create mode 100644 examples/sqlite/tests/offset_pagination_query_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 782c24e1..a400f252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,5 +32,6 @@ with-uuid = ["sea-orm/with-uuid"] with-decimal = ["sea-orm/with-rust_decimal", "async-graphql/decimal"] with-bigdecimal = ["sea-orm/with-bigdecimal", "async-graphql/bigdecimal"] with-postgres-array = ["sea-orm/postgres-array"] +offset-pagination = [] # with-ipnetwork = ["sea-orm/with-ipnetwork"] -# with-mac_address = ["sea-orm/with-mac_address"] +# with-mac_address = ["sea-orm/with-mac_address"] \ No newline at end of file diff --git a/examples/mysql/Cargo.toml b/examples/mysql/Cargo.toml index ab2acc05..bc90cfeb 100644 --- a/examples/mysql/Cargo.toml +++ b/examples/mysql/Cargo.toml @@ -22,5 +22,8 @@ features = ["with-decimal", "with-chrono"] [dev-dependencies] serde_json = { version = "1.0.103" } +[features] +offset-pagination = ["seaography/offset-pagination"] + [workspace] -members = [] +members = [] \ No newline at end of file diff --git a/examples/mysql/tests/offset_pagination_query_tests.rs b/examples/mysql/tests/offset_pagination_query_tests.rs new file mode 100644 index 00000000..d966d07e --- /dev/null +++ b/examples/mysql/tests/offset_pagination_query_tests.rs @@ -0,0 +1,137 @@ +use async_graphql::{dynamic::*, Response}; +use sea_orm::Database; + +pub async fn get_schema() -> Schema { + let database = Database::connect("mysql://sea:sea@127.0.0.1/sakila") + .await + .unwrap(); + let schema = seaography_mysql_example::query_root::schema(database, None, None).unwrap(); + + schema +} + +pub fn assert_eq(a: Response, b: &str) { + assert_eq!( + a.data.into_json().unwrap(), + serde_json::from_str::(b).unwrap() + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_simple_query() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + store { + storeId + staff { + firstName + lastName + } + } + } + "#, + ) + .await, + r#" + { + "store": [ + { + "storeId": 1, + "staff": { + "firstName": "Mike", + "lastName": "Hillyer" + } + }, + { + "storeId": 2, + "staff": { + "firstName": "Jon", + "lastName": "Stephens" + } + } + ] + } + "#, + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_simple_query_with_filter() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + store(filters: {storeId:{eq: 1}}) { + storeId + staff { + firstName + lastName + } + } + } + "#, + ) + .await, + r#" + { + "store": [ + { + "storeId": 1, + "staff": { + "firstName": "Mike", + "lastName": "Hillyer" + } + } + ] + } + "#, + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_filter_with_pagination() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + customer( + filters: { active: { eq: 0 } } + pagination: { page: { page: 2, limit: 3 } } + ) { + customerId + } + } + "#, + ) + .await, + r#" + { + "customer": [ + { + "customerId": 315 + }, + { + "customerId": 368 + }, + { + "customerId": 406 + } + ] + } + "#, + ) +} \ No newline at end of file diff --git a/examples/postgres/Cargo.toml b/examples/postgres/Cargo.toml index b79ac2ca..ffb254a2 100644 --- a/examples/postgres/Cargo.toml +++ b/examples/postgres/Cargo.toml @@ -22,5 +22,8 @@ features = ["with-decimal", "with-chrono", "with-postgres-array"] [dev-dependencies] serde_json = { version = "1.0.103" } +[features] +offset-pagination = ["seaography/offset-pagination"] + [workspace] -members = [] +members = [] \ No newline at end of file diff --git a/examples/postgres/tests/offset_pagination_query_tests.rs b/examples/postgres/tests/offset_pagination_query_tests.rs new file mode 100644 index 00000000..5285ec02 --- /dev/null +++ b/examples/postgres/tests/offset_pagination_query_tests.rs @@ -0,0 +1,137 @@ +use async_graphql::{dynamic::*, Response}; +use sea_orm::Database; + +pub async fn get_schema() -> Schema { + let database = Database::connect("postgres://sea:sea@127.0.0.1/sakila") + .await + .unwrap(); + let schema = seaography_postgres_example::query_root::schema(database, None, None).unwrap(); + + schema +} + +pub fn assert_eq(a: Response, b: &str) { + assert_eq!( + a.data.into_json().unwrap(), + serde_json::from_str::(b).unwrap() + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_simple_query() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + store { + storeId + staff { + firstName + lastName + } + } + } + "#, + ) + .await, + r#" + { + "store": [ + { + "storeId": 1, + "staff": { + "firstName": "Mike", + "lastName": "Hillyer" + } + }, + { + "storeId": 2, + "staff": { + "firstName": "Jon", + "lastName": "Stephens" + } + } + ] + } + "#, + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_simple_query_with_filter() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + store(filters: {storeId:{eq: 1}}) { + storeId + staff { + firstName + lastName + } + } + } + "#, + ) + .await, + r#" + { + "store": [ + { + "storeId": 1, + "staff": { + "firstName": "Mike", + "lastName": "Hillyer" + } + } + ] + } + "#, + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_filter_with_pagination() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + customer( + filters: { active: { eq: 0 } } + pagination: { page: { page: 2, limit: 3 } } + ) { + customerId + } + } + "#, + ) + .await, + r#" + { + "customer": [ + { + "customerId": 315 + }, + { + "customerId": 368 + }, + { + "customerId": 406 + } + ] + } + "#, + ) +} \ No newline at end of file diff --git a/examples/sqlite/Cargo.toml b/examples/sqlite/Cargo.toml index f75d44ea..99897494 100644 --- a/examples/sqlite/Cargo.toml +++ b/examples/sqlite/Cargo.toml @@ -22,5 +22,8 @@ features = ["with-decimal", "with-chrono"] [dev-dependencies] serde_json = { version = "1.0.103" } +[features] +offset-pagination = ["seaography/offset-pagination"] + [workspace] -members = [] +members = [] \ No newline at end of file diff --git a/examples/sqlite/tests/offset_pagination_query_tests.rs b/examples/sqlite/tests/offset_pagination_query_tests.rs new file mode 100644 index 00000000..bf4ac006 --- /dev/null +++ b/examples/sqlite/tests/offset_pagination_query_tests.rs @@ -0,0 +1,135 @@ +use async_graphql::{dynamic::*, Response}; +use sea_orm::Database; + +pub async fn get_schema() -> Schema { + let database = Database::connect("sqlite://sakila.db").await.unwrap(); + let schema = seaography_sqlite_example::query_root::schema(database, None, None).unwrap(); + + schema +} + +pub fn assert_eq(a: Response, b: &str) { + assert_eq!( + a.data.into_json().unwrap(), + serde_json::from_str::(b).unwrap() + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_simple_query() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + store { + storeId + staff { + firstName + lastName + } + } + } + "#, + ) + .await, + r#" + { + "store": [ + { + "storeId": 1, + "staff": { + "firstName": "Mike", + "lastName": "Hillyer" + } + }, + { + "storeId": 2, + "staff": { + "firstName": "Jon", + "lastName": "Stephens" + } + } + ] + } + "#, + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_simple_query_with_filter() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + store(filters: {storeId:{eq: 1}}) { + storeId + staff { + firstName + lastName + } + } + } + "#, + ) + .await, + r#" + { + "store": [ + { + "storeId": 1, + "staff": { + "firstName": "Mike", + "lastName": "Hillyer" + } + } + ] + } + "#, + ) +} + +#[cfg(feature = "offset-pagination")] +#[tokio::test] +async fn test_filter_with_pagination() { + let schema = get_schema().await; + + assert_eq( + schema + .execute( + r#" + { + customer( + filters: { active: { eq: 0 } } + pagination: { page: { page: 2, limit: 3 } } + ) { + customerId + } + } + "#, + ) + .await, + r#" + { + "customer": [ + { + "customerId": 315 + }, + { + "customerId": 368 + }, + { + "customerId": 406 + } + ] + } + "#, + ) +} \ No newline at end of file diff --git a/generator/src/templates/actix_cargo.toml b/generator/src/templates/actix_cargo.toml index 9ee2914d..48ab0e61 100644 --- a/generator/src/templates/actix_cargo.toml +++ b/generator/src/templates/actix_cargo.toml @@ -21,5 +21,8 @@ features = ["with-decimal", "with-chrono"] [dev-dependencies] serde_json = { version = "1.0.103" } +[features] +offset-pagination = ["seaography/offset-pagination"] + [workspace] -members = [] +members = [] \ No newline at end of file diff --git a/generator/src/templates/axum_cargo.toml b/generator/src/templates/axum_cargo.toml index 59529b0b..909d154f 100644 --- a/generator/src/templates/axum_cargo.toml +++ b/generator/src/templates/axum_cargo.toml @@ -21,5 +21,8 @@ features = ["with-decimal", "with-chrono"] [dev-dependencies] serde_json = { version = "1.0.103" } +[features] +offset-pagination = ["seaography/offset-pagination"] + [workspace] -members = [] +members = [] \ No newline at end of file diff --git a/generator/src/templates/poem_cargo.toml b/generator/src/templates/poem_cargo.toml index ec21acd7..624e4d86 100644 --- a/generator/src/templates/poem_cargo.toml +++ b/generator/src/templates/poem_cargo.toml @@ -21,5 +21,8 @@ features = ["with-decimal", "with-chrono"] [dev-dependencies] serde_json = { version = "1.0.103" } +[features] +offset-pagination = ["seaography/offset-pagination"] + [workspace] -members = [] +members = [] \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index 2de75389..44dea5fc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -83,17 +83,23 @@ impl Builder { |entity_object, field| entity_object.field(field), ); - let edge_object_builder = EdgeObjectBuilder { - context: self.context, - }; - let edge = edge_object_builder.to_object::(); + if cfg!(feature = "offset-pagination") { + self.outputs.extend(vec![entity_object]); + } else { + let edge_object_builder = EdgeObjectBuilder { + context: self.context, + }; - let connection_object_builder = ConnectionObjectBuilder { - context: self.context, - }; - let connection = connection_object_builder.to_object::(); + let edge = edge_object_builder.to_object::(); - self.outputs.extend(vec![entity_object, edge, connection]); + let connection_object_builder = ConnectionObjectBuilder { + context: self.context, + }; + + let connection = connection_object_builder.to_object::(); + + self.outputs.extend(vec![entity_object, edge, connection]); + } let filter_input_builder = FilterInputBuilder { context: self.context, @@ -271,12 +277,8 @@ impl Builder { } .enumeration(), ) - .register( - CursorInputBuilder { - context: self.context, - } - .input_object(), - ) + .register(query) + .register(mutation) .register( CursorInputBuilder { context: self.context, @@ -313,8 +315,6 @@ impl Builder { } .to_object(), ) - .register(query) - .register(mutation) } } @@ -360,4 +360,4 @@ macro_rules! register_entities_without_relation { ($builder:expr, [$($module_paths:ident),+ $(,)?]) => { $(seaography::register_entity_without_relation!($builder, $module_paths);)* }; -} +} \ No newline at end of file diff --git a/src/query/entity_object_relation.rs b/src/query/entity_object_relation.rs index a813c1da..36233028 100644 --- a/src/query/entity_object_relation.rs +++ b/src/query/entity_object_relation.rs @@ -7,8 +7,8 @@ use heck::ToSnakeCase; use sea_orm::{EntityTrait, Iden, ModelTrait, RelationDef}; use crate::{ - apply_memory_pagination, get_filter_conditions, BuilderContext, Connection, - ConnectionObjectBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, + apply_memory_offset_pagination, apply_memory_pagination, get_filter_conditions, BuilderContext, + Connection, ConnectionObjectBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, HashableGroupKey, KeyComplex, OneToManyLoader, OneToOneLoader, OrderInputBuilder, PaginationInputBuilder, }; @@ -108,63 +108,127 @@ impl EntityObjectRelationBuilder { } }) }), - true => Field::new( - name, - TypeRef::named_nn(connection_object_builder.type_name(&object_name)), - move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow - }; - - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => { - Err::, async_graphql::Error>(Error::new(reason)) - } - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), + true => { + if cfg!(feature = "offset-pagination") { + Field::new(name, TypeRef::named_list(&object_name), move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow }; - } - - let parent: &T::Model = ctx - .parent_value - .try_downcast_ref::() - .expect("Parent should exist"); - - let loader = ctx.data_unchecked::>>(); - - let stmt = R::find(); - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); - let key = KeyComplex:: { - key: vec![parent.get(from_col)], - meta: HashableGroupKey:: { - stmt, - columns: vec![to_col], - filters: Some(filters), - order_by, - }, - }; - let values = loader.load_one(key).await?; + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => { + Err::, async_graphql::Error>(Error::new(reason)) + } + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } - let pagination = ctx.args.get(&context.entity_query_field.pagination); - let pagination = - PaginationInputBuilder { context }.parse_object(pagination); + let parent: &T::Model = ctx + .parent_value + .try_downcast_ref::() + .expect("Parent should exist"); + + let loader = ctx.data_unchecked::>>(); + + let stmt = R::find(); + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = + OrderInputBuilder { context }.parse_object::(order_by); + let key = KeyComplex:: { + key: vec![parent.get(from_col)], + meta: HashableGroupKey:: { + stmt, + columns: vec![to_col], + filters: Some(filters), + order_by, + }, + }; - let connection: Connection = apply_memory_pagination(values, pagination); + let values = loader.load_one(key).await?; + let pagination = ctx.args.get(&context.entity_query_field.pagination); + let pagination = + PaginationInputBuilder { context }.parse_object(pagination); - Ok(Some(FieldValue::owned_any(connection))) + let connection = + apply_memory_offset_pagination::(values, pagination); + + Ok(Some(FieldValue::list( + connection.into_iter().map(FieldValue::owned_any), + ))) + }) }) - }, - ), + } else { + Field::new( + name, + TypeRef::named_nn(connection_object_builder.type_name(&object_name)), + move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; + + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => Err::, async_graphql::Error>( + Error::new(reason), + ), + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } + + let parent: &T::Model = ctx + .parent_value + .try_downcast_ref::() + .expect("Parent should exist"); + + let loader = ctx.data_unchecked::>>(); + + let stmt = R::find(); + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = + OrderInputBuilder { context }.parse_object::(order_by); + let key = KeyComplex:: { + key: vec![parent.get(from_col)], + meta: HashableGroupKey:: { + stmt, + columns: vec![to_col], + filters: Some(filters), + order_by, + }, + }; + + let values = loader.load_one(key).await?; + + let pagination = + ctx.args.get(&context.entity_query_field.pagination); + let pagination = + PaginationInputBuilder { context }.parse_object(pagination); + + let connection: Connection = + apply_memory_pagination(values, pagination); + + Ok(Some(FieldValue::owned_any(connection))) + }) + }, + ) + } + } }; match relation_definition.is_owner { @@ -184,4 +248,4 @@ impl EntityObjectRelationBuilder { )), } } -} +} \ No newline at end of file diff --git a/src/query/entity_object_via_relation.rs b/src/query/entity_object_via_relation.rs index db84ad23..16dc82a1 100644 --- a/src/query/entity_object_via_relation.rs +++ b/src/query/entity_object_via_relation.rs @@ -9,10 +9,10 @@ use sea_orm::{ }; use crate::{ - apply_memory_pagination, apply_order, apply_pagination, get_filter_conditions, BuilderContext, - ConnectionObjectBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, - HashableGroupKey, KeyComplex, OneToManyLoader, OneToOneLoader, OrderInputBuilder, - PaginationInputBuilder, + apply_memory_offset_pagination, apply_memory_pagination, apply_offset_pagination, apply_order, + apply_pagination, get_filter_conditions, BuilderContext, ConnectionObjectBuilder, + EntityObjectBuilder, FilterInputBuilder, GuardAction, HashableGroupKey, KeyComplex, + OneToManyLoader, OneToOneLoader, OrderInputBuilder, PaginationInputBuilder, }; /// This builder produces a GraphQL field for an SeaORM entity related trait @@ -122,83 +122,168 @@ impl EntityObjectViaRelationBuilder { } }) }), - true => Field::new( - name, - TypeRef::named_nn(connection_object_builder.type_name(&object_name)), - move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow - }; + true => { + if cfg!(feature = "offset-pagination") { + Field::new(name, TypeRef::named_list(&object_name), move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => { - Err::, async_graphql::Error>(Error::new(reason)) - } - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => { + Err::, async_graphql::Error>(Error::new(reason)) + } + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } + + // FIXME: optimize union queries + // NOTE: each has unique query in order to apply pagination... + let parent: &T::Model = ctx + .parent_value + .try_downcast_ref::() + .expect("Parent should exist"); + + let stmt = if >::via().is_some() { + >::find_related() + } else { + R::find() }; - } - - // FIXME: optimize union queries - // NOTE: each has unique query in order to apply pagination... - let parent: &T::Model = ctx - .parent_value - .try_downcast_ref::() - .expect("Parent should exist"); - - let stmt = if >::via().is_some() { - >::find_related() - } else { - R::find() - }; - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = + OrderInputBuilder { context }.parse_object::(order_by); - let pagination = ctx.args.get(&context.entity_query_field.pagination); - let pagination = - PaginationInputBuilder { context }.parse_object(pagination); + let pagination = ctx.args.get(&context.entity_query_field.pagination); + let pagination = + PaginationInputBuilder { context }.parse_object(pagination); - let db = ctx.data::()?; + let db = ctx.data::()?; - let connection = if is_via_relation { - // TODO optimize query - let condition = Condition::all().add(from_col.eq(parent.get(from_col))); + let connection = if is_via_relation { + // TODO optimize query + let condition = + Condition::all().add(from_col.eq(parent.get(from_col))); - let stmt = stmt.filter(condition.add(filters)); - let stmt = apply_order(stmt, order_by); - apply_pagination::(db, stmt, pagination).await? - } else { - let loader = ctx.data_unchecked::>>(); + let stmt = stmt.filter(condition.add(filters)); + let stmt = apply_order(stmt, order_by); + apply_offset_pagination::(db, stmt, pagination).await? + } else { + let loader = ctx.data_unchecked::>>(); - let key = KeyComplex:: { - key: vec![parent.get(from_col)], - meta: HashableGroupKey:: { - stmt, - columns: vec![to_col], - filters: Some(filters), - order_by, - }, + let key = KeyComplex:: { + key: vec![parent.get(from_col)], + meta: HashableGroupKey:: { + stmt, + columns: vec![to_col], + filters: Some(filters), + order_by, + }, + }; + + let values = loader.load_one(key).await?; + apply_memory_offset_pagination::(values, pagination) }; - let values = loader.load_one(key).await?; + Ok(Some(FieldValue::list( + connection.into_iter().map(FieldValue::owned_any), + ))) + }) + }) + } else { + Field::new( + name, + TypeRef::named_nn(connection_object_builder.type_name(&object_name)), + move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; - apply_memory_pagination(values, pagination) - }; + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => Err::, async_graphql::Error>( + Error::new(reason), + ), + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } - Ok(Some(FieldValue::owned_any(connection))) - }) - }, - ), + // FIXME: optimize union queries + // NOTE: each has unique query in order to apply pagination... + let parent: &T::Model = ctx + .parent_value + .try_downcast_ref::() + .expect("Parent should exist"); + + let stmt = if >::via().is_some() { + >::find_related() + } else { + R::find() + }; + + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); + + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = + OrderInputBuilder { context }.parse_object::(order_by); + + let pagination = + ctx.args.get(&context.entity_query_field.pagination); + let pagination = + PaginationInputBuilder { context }.parse_object(pagination); + + let db = ctx.data::()?; + + let connection = if is_via_relation { + // TODO optimize query + let condition = + Condition::all().add(from_col.eq(parent.get(from_col))); + + let stmt = stmt.filter(condition.add(filters)); + let stmt = apply_order(stmt, order_by); + apply_pagination::(db, stmt, pagination).await? + } else { + let loader = + ctx.data_unchecked::>>(); + + let key = KeyComplex:: { + key: vec![parent.get(from_col)], + meta: HashableGroupKey:: { + stmt, + columns: vec![to_col], + filters: Some(filters), + order_by, + }, + }; + + let values = loader.load_one(key).await?; + + apply_memory_pagination(values, pagination) + }; + + Ok(Some(FieldValue::owned_any(connection))) + }) + }, + ) + } + } }; match via_relation_definition.is_owner { @@ -218,4 +303,4 @@ impl EntityObjectViaRelationBuilder { )), } } -} +} \ No newline at end of file diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index 168b9da7..d2eed06d 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -1,3 +1,5 @@ +use std::os::unix::process; + use async_graphql::{ dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef}, Error, @@ -6,9 +8,9 @@ use heck::ToLowerCamelCase; use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter}; use crate::{ - apply_order, apply_pagination, get_filter_conditions, BuilderContext, ConnectionObjectBuilder, - EntityObjectBuilder, FilterInputBuilder, GuardAction, OrderInputBuilder, - PaginationInputBuilder, + apply_offset_pagination, apply_order, apply_pagination, get_filter_conditions, BuilderContext, + ConnectionObjectBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, + OrderInputBuilder, PaginationInputBuilder, }; /// The configuration structure for EntityQueryFieldBuilder @@ -78,15 +80,85 @@ impl EntityQueryFieldBuilder { }; let object_name = entity_object.type_name::(); - let type_name = connection_object_builder.type_name(&object_name); - + let type_name = if cfg!(feature = "offset-pagination") { + object_name.clone() + } else { + connection_object_builder.type_name(&object_name) + }; + #[cfg(feature = "offset-pagination")] + let process_fn = Box::new(|connection| FieldValue::owned_any(connection)); + #[cfg(not(feature = "offset-pagination"))] + let process_fn = Box::new(|connection: Vec| { + FieldValue::list(connection.into_iter().map(FieldValue::owned_any)) + }); let guard = self.context.guards.entity_guards.get(&object_name); let context: &'static BuilderContext = self.context; - Field::new( - self.type_name::(), - TypeRef::named_nn(type_name), - move |ctx| { + + #[cfg(feature = "offset-pagination")] + let ttype: Box TypeRef> = + Box::new(|param: String| TypeRef::named_list(param)); + #[cfg(not(feature = "offset-pagination"))] + let ttype = Box::new(|param: String| TypeRef::named_nn(param)); + + if cfg!(feature = "offset-pagination") { + Field::new(self.type_name::(), ttype(type_name), move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; + + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => { + Err::, async_graphql::Error>(Error::new(reason)) + } + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } + + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let pagination = ctx.args.get(&context.entity_query_field.pagination); + let pagination = PaginationInputBuilder { context }.parse_object(pagination); + + let stmt = T::find(); + let stmt = stmt.filter(filters); + let stmt = apply_order(stmt, order_by); + + let db = ctx.data::()?; + + let connection = apply_offset_pagination::(db, stmt, pagination).await?; + + Ok(Some(process_fn(connection))) + }) + }) + .argument(InputValue::new( + &self.context.entity_query_field.filters, + TypeRef::named(filter_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &self.context.entity_query_field.order_by, + TypeRef::named(order_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &self.context.entity_query_field.pagination, + TypeRef::named(pagination_input_builder.type_name()), + )) + } else { + let connection_object_builder = ConnectionObjectBuilder { + context: self.context, + }; + + let type_name = connection_object_builder.type_name(&object_name); + Field::new(self.type_name::(), ttype(type_name), move |ctx| { let context: &'static BuilderContext = context; FieldFuture::new(async move { let guard_flag = if let Some(guard) = guard { @@ -121,21 +193,21 @@ impl EntityQueryFieldBuilder { let connection = apply_pagination::(db, stmt, pagination).await?; - Ok(Some(FieldValue::owned_any(connection))) + Ok(Some(process_fn(connection))) }) - }, - ) - .argument(InputValue::new( - &self.context.entity_query_field.filters, - TypeRef::named(filter_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &self.context.entity_query_field.order_by, - TypeRef::named(order_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &self.context.entity_query_field.pagination, - TypeRef::named(pagination_input_builder.type_name()), - )) + }) + .argument(InputValue::new( + &self.context.entity_query_field.filters, + TypeRef::named(filter_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &self.context.entity_query_field.order_by, + TypeRef::named(order_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &self.context.entity_query_field.pagination, + TypeRef::named(pagination_input_builder.type_name()), + )) + } } -} +} \ No newline at end of file diff --git a/src/query/pagination.rs b/src/query/pagination.rs index e2c854e3..2b4de4a2 100644 --- a/src/query/pagination.rs +++ b/src/query/pagination.rs @@ -263,6 +263,29 @@ where } } +pub async fn apply_offset_pagination( + db: &DatabaseConnection, + stmt: Select, + pagination: PaginationInput, +) -> Result::Model>, sea_orm::error::DbErr> +where + T: EntityTrait, + ::Model: Sync, +{ + if let Some(page_object) = pagination.page { + let paginator = stmt.paginate(db, page_object.limit); + + Ok(paginator.fetch_page(page_object.page).await?) + } else if let Some(offset_object) = pagination.offset { + let offset = offset_object.offset; + let limit = offset_object.limit; + + Ok(stmt.offset(offset).limit(limit).all(db).await?) + } else { + Ok(stmt.all(db).await?) + } +} + pub fn apply_memory_pagination( values: Option>, pagination: PaginationInput, @@ -411,3 +434,28 @@ where } } } + +pub fn apply_memory_offset_pagination( + values: Option>, + pagination: PaginationInput, +) -> Vec +where + T: EntityTrait, + T::Model: Sync, +{ + let data: Vec<::Model> = values.unwrap_or_default(); + + if let Some(page_object) = pagination.page { + data.into_iter() + .skip((page_object.page * page_object.limit).try_into().unwrap()) + .take(page_object.limit.try_into().unwrap()) + .collect() + } else if let Some(offset_object) = pagination.offset { + data.into_iter() + .skip((offset_object.offset).try_into().unwrap()) + .take(offset_object.limit.try_into().unwrap()) + .collect() + } else { + data + } +} \ No newline at end of file From 5a856ee33d1248fbd59744e87f79df9b3f7ba22c Mon Sep 17 00:00:00 2001 From: Heikel Bouzayene Date: Thu, 12 Sep 2024 08:58:01 +0100 Subject: [PATCH 02/22] fix unitests and code style --- examples/mysql/tests/mutation_tests.rs | 3 +- examples/mysql/tests/query_tests.rs | 13 +- examples/postgres/tests/mutation_tests.rs | 3 +- examples/postgres/tests/query_tests.rs | 13 +- examples/sqlite/tests/guard_mutation_tests.rs | 7 +- examples/sqlite/tests/guard_tests.rs | 4 +- examples/sqlite/tests/mutation_tests.rs | 8 +- examples/sqlite/tests/query_tests.rs | 12 +- src/query/entity_object_relation.rs | 186 +++++--------- src/query/entity_object_via_relation.rs | 226 ++++++------------ src/query/entity_query_field.rs | 174 ++++---------- src/query/pagination.rs | 20 +- 12 files changed, 256 insertions(+), 413 deletions(-) diff --git a/examples/mysql/tests/mutation_tests.rs b/examples/mysql/tests/mutation_tests.rs index 44a1948d..f638ddba 100644 --- a/examples/mysql/tests/mutation_tests.rs +++ b/examples/mysql/tests/mutation_tests.rs @@ -1,6 +1,7 @@ use async_graphql::{dynamic::*, Response}; use sea_orm::Database; +#[cfg(not(feature = "offset-pagination"))] async fn main() { test_simple_insert_one().await; test_complex_insert_one().await; @@ -620,4 +621,4 @@ async fn test_delete_mutation() { } "#, ); -} +} \ No newline at end of file diff --git a/examples/mysql/tests/query_tests.rs b/examples/mysql/tests/query_tests.rs index 97ca5a8c..d6b5ffbd 100644 --- a/examples/mysql/tests/query_tests.rs +++ b/examples/mysql/tests/query_tests.rs @@ -17,6 +17,7 @@ pub fn assert_eq(a: Response, b: &str) { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_simple_query() { let schema = get_schema().await; @@ -64,6 +65,7 @@ async fn test_simple_query() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_simple_query_with_filter() { let schema = get_schema().await; @@ -104,6 +106,7 @@ async fn test_simple_query_with_filter() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_filter_with_pagination() { let schema = get_schema().await; @@ -153,6 +156,7 @@ async fn test_filter_with_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_complex_filter_with_pagination() { let schema = get_schema().await; @@ -202,6 +206,7 @@ async fn test_complex_filter_with_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination() { let schema = get_schema().await; @@ -297,6 +302,7 @@ async fn test_cursor_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination_prev() { let schema = get_schema().await; @@ -374,6 +380,7 @@ async fn test_cursor_pagination_prev() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination_no_next() { let schema = get_schema().await; @@ -442,6 +449,7 @@ async fn test_cursor_pagination_no_next() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_self_ref() { let schema = get_schema().await; @@ -506,6 +514,7 @@ async fn test_self_ref() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn related_queries_filters() { let schema = get_schema().await; @@ -696,6 +705,7 @@ async fn related_queries_filters() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn related_queries_pagination() { let schema = get_schema().await; @@ -833,6 +843,7 @@ async fn related_queries_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn enumeration_filter() { let schema = get_schema().await; @@ -884,4 +895,4 @@ async fn enumeration_filter() { } "#, ) -} +} \ No newline at end of file diff --git a/examples/postgres/tests/mutation_tests.rs b/examples/postgres/tests/mutation_tests.rs index 1fa66e91..2cff0963 100644 --- a/examples/postgres/tests/mutation_tests.rs +++ b/examples/postgres/tests/mutation_tests.rs @@ -1,6 +1,7 @@ use async_graphql::{dynamic::*, Response}; use sea_orm::Database; +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn main() { test_simple_insert_one().await; @@ -630,4 +631,4 @@ async fn test_delete_mutation() { } "#, ); -} +} \ No newline at end of file diff --git a/examples/postgres/tests/query_tests.rs b/examples/postgres/tests/query_tests.rs index 7c3d340d..c69ecb93 100644 --- a/examples/postgres/tests/query_tests.rs +++ b/examples/postgres/tests/query_tests.rs @@ -17,6 +17,7 @@ pub fn assert_eq(a: Response, b: &str) { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_simple_query() { let schema = get_schema().await; @@ -64,6 +65,7 @@ async fn test_simple_query() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_simple_query_with_filter() { let schema = get_schema().await; @@ -104,6 +106,7 @@ async fn test_simple_query_with_filter() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_filter_with_pagination() { let schema = get_schema().await; @@ -153,6 +156,7 @@ async fn test_filter_with_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_complex_filter_with_pagination() { let schema = get_schema().await; @@ -202,6 +206,7 @@ async fn test_complex_filter_with_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination() { let schema = get_schema().await; @@ -297,6 +302,7 @@ async fn test_cursor_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination_prev() { let schema = get_schema().await; @@ -374,6 +380,7 @@ async fn test_cursor_pagination_prev() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination_no_next() { let schema = get_schema().await; @@ -507,6 +514,7 @@ async fn test_cursor_pagination_no_next() { // ) // } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn related_queries_filters() { let schema = get_schema().await; @@ -694,6 +702,7 @@ async fn related_queries_filters() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn related_queries_pagination() { let schema = get_schema().await; @@ -831,6 +840,7 @@ async fn related_queries_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn enumeration_filter() { let schema = get_schema().await; @@ -884,6 +894,7 @@ async fn enumeration_filter() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_boolean_field() { let schema = get_schema().await; @@ -916,4 +927,4 @@ async fn test_boolean_field() { } "#, ) -} +} \ No newline at end of file diff --git a/examples/sqlite/tests/guard_mutation_tests.rs b/examples/sqlite/tests/guard_mutation_tests.rs index ced4915a..c1eee029 100644 --- a/examples/sqlite/tests/guard_mutation_tests.rs +++ b/examples/sqlite/tests/guard_mutation_tests.rs @@ -80,7 +80,7 @@ pub fn assert_eq(a: Response, b: &str) { serde_json::from_str::(b).unwrap() ) } - +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn entity_guard_mutation() { let schema = get_schema().await; @@ -130,6 +130,7 @@ async fn entity_guard_mutation() { assert_eq!(response.errors[0].message, "Entity guard triggered."); } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn field_guard_mutation() { let schema = get_schema().await; @@ -141,7 +142,7 @@ async fn field_guard_mutation() { languageUpdate(data: { name: "Cantonese" }, filter: { languageId: { eq: 6 } }) { languageId } - } + } "#, ) .await; @@ -149,4 +150,4 @@ async fn field_guard_mutation() { assert_eq!(response.errors.len(), 1); assert_eq!(response.errors[0].message, "Field guard triggered."); -} +} \ No newline at end of file diff --git a/examples/sqlite/tests/guard_tests.rs b/examples/sqlite/tests/guard_tests.rs index 9e9340a6..0d75ff02 100644 --- a/examples/sqlite/tests/guard_tests.rs +++ b/examples/sqlite/tests/guard_tests.rs @@ -285,6 +285,7 @@ pub fn assert_eq(a: Response, b: &str) { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn entity_guard() { let schema = get_schema().await; @@ -357,6 +358,7 @@ async fn entity_guard() { assert_eq!(response.errors[0].message, "Entity guard triggered."); } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn field_guard() { let schema = get_schema().await; @@ -380,4 +382,4 @@ async fn field_guard() { assert_eq!(response.errors.len(), 1); assert_eq!(response.errors[0].message, "Field guard triggered."); -} +} \ No newline at end of file diff --git a/examples/sqlite/tests/mutation_tests.rs b/examples/sqlite/tests/mutation_tests.rs index c3f9dec5..53be711d 100644 --- a/examples/sqlite/tests/mutation_tests.rs +++ b/examples/sqlite/tests/mutation_tests.rs @@ -1,6 +1,7 @@ use async_graphql::{dynamic::*, Response}; use sea_orm::Database; +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn main() { test_simple_insert_one().await; @@ -24,6 +25,7 @@ pub fn assert_eq(a: Response, b: &str) { ) } +#[cfg(not(feature = "offset-pagination"))] async fn test_simple_insert_one() { let schema = get_schema().await; @@ -106,6 +108,7 @@ async fn test_simple_insert_one() { ); } +#[cfg(not(feature = "offset-pagination"))] async fn test_complex_insert_one() { let schema = get_schema().await; @@ -218,6 +221,7 @@ async fn test_complex_insert_one() { ); } +#[cfg(not(feature = "offset-pagination"))] async fn test_create_batch_mutation() { let schema = get_schema().await; @@ -320,6 +324,7 @@ async fn test_create_batch_mutation() { ); } +#[cfg(not(feature = "offset-pagination"))] async fn test_update_mutation() { let schema = get_schema().await; @@ -498,6 +503,7 @@ async fn test_update_mutation() { ); } +#[cfg(not(feature = "offset-pagination"))] async fn test_delete_mutation() { let schema = get_schema().await; @@ -601,4 +607,4 @@ async fn test_delete_mutation() { } "#, ); -} +} \ No newline at end of file diff --git a/examples/sqlite/tests/query_tests.rs b/examples/sqlite/tests/query_tests.rs index 0f04935e..4104de1e 100644 --- a/examples/sqlite/tests/query_tests.rs +++ b/examples/sqlite/tests/query_tests.rs @@ -15,6 +15,7 @@ pub fn assert_eq(a: Response, b: &str) { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_simple_query() { let schema = get_schema().await; @@ -62,6 +63,7 @@ async fn test_simple_query() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_simple_query_with_filter() { let schema = get_schema().await; @@ -102,6 +104,7 @@ async fn test_simple_query_with_filter() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_filter_with_pagination() { let schema = get_schema().await; @@ -151,6 +154,7 @@ async fn test_filter_with_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_complex_filter_with_pagination() { let schema = get_schema().await; @@ -200,6 +204,7 @@ async fn test_complex_filter_with_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination() { let schema = get_schema().await; @@ -295,6 +300,7 @@ async fn test_cursor_pagination() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination_prev() { let schema = get_schema().await; @@ -372,6 +378,7 @@ async fn test_cursor_pagination_prev() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_cursor_pagination_no_next() { let schema = get_schema().await; @@ -440,6 +447,7 @@ async fn test_cursor_pagination_no_next() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn test_self_ref() { let schema = get_schema().await; @@ -504,6 +512,7 @@ async fn test_self_ref() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn related_queries_filters() { let schema = get_schema().await; @@ -706,6 +715,7 @@ async fn related_queries_filters() { ) } +#[cfg(not(feature = "offset-pagination"))] #[tokio::test] async fn related_queries_pagination() { let schema = get_schema().await; @@ -841,4 +851,4 @@ async fn related_queries_pagination() { } "#, ) -} +} \ No newline at end of file diff --git a/src/query/entity_object_relation.rs b/src/query/entity_object_relation.rs index 36233028..ca500be2 100644 --- a/src/query/entity_object_relation.rs +++ b/src/query/entity_object_relation.rs @@ -6,11 +6,12 @@ use async_graphql::{ use heck::ToSnakeCase; use sea_orm::{EntityTrait, Iden, ModelTrait, RelationDef}; +#[cfg(not(feature = "offset-pagination"))] +use crate::ConnectionObjectBuilder; use crate::{ - apply_memory_offset_pagination, apply_memory_pagination, get_filter_conditions, BuilderContext, - Connection, ConnectionObjectBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, - HashableGroupKey, KeyComplex, OneToManyLoader, OneToOneLoader, OrderInputBuilder, - PaginationInputBuilder, + apply_memory_pagination, get_filter_conditions, BuilderContext, EntityObjectBuilder, + FilterInputBuilder, GuardAction, HashableGroupKey, KeyComplex, OneToManyLoader, OneToOneLoader, + OrderInputBuilder, PaginationInputBuilder, }; /// This builder produces a GraphQL field for an SeaORM entity relationship @@ -32,11 +33,22 @@ impl EntityObjectRelationBuilder { { let context: &'static BuilderContext = self.context; let entity_object_builder = EntityObjectBuilder { context }; + #[cfg(not(feature = "offset-pagination"))] let connection_object_builder = ConnectionObjectBuilder { context }; let filter_input_builder = FilterInputBuilder { context }; let order_input_builder = OrderInputBuilder { context }; let object_name: String = entity_object_builder.type_name::(); + #[cfg(feature = "offset-pagination")] + let type_ref = TypeRef::named_list(&object_name); + #[cfg(not(feature = "offset-pagination"))] + let type_ref = TypeRef::named_nn(connection_object_builder.type_name(&object_name)); + + #[cfg(feature = "offset-pagination")] + let resolver_fn = + |object: Vec| FieldValue::list(object.into_iter().map(FieldValue::owned_any)); + #[cfg(not(feature = "offset-pagination"))] + let resolver_fn = |object: crate::Connection| FieldValue::owned_any(object); let guard = self.context.guards.entity_guards.get(&object_name); let from_col = ::from_str( @@ -108,127 +120,57 @@ impl EntityObjectRelationBuilder { } }) }), - true => { - if cfg!(feature = "offset-pagination") { - Field::new(name, TypeRef::named_list(&object_name), move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow - }; - - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => { - Err::, async_graphql::Error>(Error::new(reason)) - } - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), - }; + true => Field::new(name, type_ref, move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; + + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => { + Err::, async_graphql::Error>(Error::new(reason)) } + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } + + let parent: &T::Model = ctx + .parent_value + .try_downcast_ref::() + .expect("Parent should exist"); + + let loader = ctx.data_unchecked::>>(); - let parent: &T::Model = ctx - .parent_value - .try_downcast_ref::() - .expect("Parent should exist"); - - let loader = ctx.data_unchecked::>>(); - - let stmt = R::find(); - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = - OrderInputBuilder { context }.parse_object::(order_by); - let key = KeyComplex:: { - key: vec![parent.get(from_col)], - meta: HashableGroupKey:: { - stmt, - columns: vec![to_col], - filters: Some(filters), - order_by, - }, - }; - - let values = loader.load_one(key).await?; - let pagination = ctx.args.get(&context.entity_query_field.pagination); - let pagination = - PaginationInputBuilder { context }.parse_object(pagination); - - let connection = - apply_memory_offset_pagination::(values, pagination); - - Ok(Some(FieldValue::list( - connection.into_iter().map(FieldValue::owned_any), - ))) - }) - }) - } else { - Field::new( - name, - TypeRef::named_nn(connection_object_builder.type_name(&object_name)), - move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow - }; - - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => Err::, async_graphql::Error>( - Error::new(reason), - ), - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), - }; - } - - let parent: &T::Model = ctx - .parent_value - .try_downcast_ref::() - .expect("Parent should exist"); - - let loader = ctx.data_unchecked::>>(); - - let stmt = R::find(); - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = - OrderInputBuilder { context }.parse_object::(order_by); - let key = KeyComplex:: { - key: vec![parent.get(from_col)], - meta: HashableGroupKey:: { - stmt, - columns: vec![to_col], - filters: Some(filters), - order_by, - }, - }; - - let values = loader.load_one(key).await?; - - let pagination = - ctx.args.get(&context.entity_query_field.pagination); - let pagination = - PaginationInputBuilder { context }.parse_object(pagination); - - let connection: Connection = - apply_memory_pagination(values, pagination); - - Ok(Some(FieldValue::owned_any(connection))) - }) + let stmt = R::find(); + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let key = KeyComplex:: { + key: vec![parent.get(from_col)], + meta: HashableGroupKey:: { + stmt, + columns: vec![to_col], + filters: Some(filters), + order_by, }, - ) - } - } + }; + + let values = loader.load_one(key).await?; + let pagination = ctx.args.get(&context.entity_query_field.pagination); + let pagination = PaginationInputBuilder { context }.parse_object(pagination); + + let object = apply_memory_pagination::(values, pagination); + + Ok(Some(resolver_fn(object))) + }) + }), }; match relation_definition.is_owner { diff --git a/src/query/entity_object_via_relation.rs b/src/query/entity_object_via_relation.rs index 16dc82a1..8bf1c280 100644 --- a/src/query/entity_object_via_relation.rs +++ b/src/query/entity_object_via_relation.rs @@ -8,9 +8,10 @@ use sea_orm::{ ColumnTrait, Condition, DatabaseConnection, EntityTrait, Iden, ModelTrait, QueryFilter, Related, }; +#[cfg(not(feature = "offset-pagination"))] +use crate::ConnectionObjectBuilder; use crate::{ - apply_memory_offset_pagination, apply_memory_pagination, apply_offset_pagination, apply_order, - apply_pagination, get_filter_conditions, BuilderContext, ConnectionObjectBuilder, + apply_memory_pagination, apply_order, apply_pagination, get_filter_conditions, BuilderContext, EntityObjectBuilder, FilterInputBuilder, GuardAction, HashableGroupKey, KeyComplex, OneToManyLoader, OneToOneLoader, OrderInputBuilder, PaginationInputBuilder, }; @@ -41,11 +42,21 @@ impl EntityObjectViaRelationBuilder { }; let entity_object_builder = EntityObjectBuilder { context }; + #[cfg(not(feature = "offset-pagination"))] let connection_object_builder = ConnectionObjectBuilder { context }; let filter_input_builder = FilterInputBuilder { context }; let order_input_builder = OrderInputBuilder { context }; - let object_name: String = entity_object_builder.type_name::(); + #[cfg(feature = "offset-pagination")] + let type_ref = TypeRef::named_list(&object_name); + #[cfg(not(feature = "offset-pagination"))] + let type_ref = TypeRef::named_nn(connection_object_builder.type_name(&object_name)); + + #[cfg(feature = "offset-pagination")] + let resolver_fn = + |object: Vec| FieldValue::list(object.into_iter().map(FieldValue::owned_any)); + #[cfg(not(feature = "offset-pagination"))] + let resolver_fn = |object: crate::Connection| FieldValue::owned_any(object); let guard = self.context.guards.entity_guards.get(&object_name); let from_col = ::from_str( @@ -122,168 +133,77 @@ impl EntityObjectViaRelationBuilder { } }) }), - true => { - if cfg!(feature = "offset-pagination") { - Field::new(name, TypeRef::named_list(&object_name), move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow - }; + true => Field::new(name, type_ref, move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => { - Err::, async_graphql::Error>(Error::new(reason)) - } - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), - }; + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => { + Err::, async_graphql::Error>(Error::new(reason)) } + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } - // FIXME: optimize union queries - // NOTE: each has unique query in order to apply pagination... - let parent: &T::Model = ctx - .parent_value - .try_downcast_ref::() - .expect("Parent should exist"); - - let stmt = if >::via().is_some() { - >::find_related() - } else { - R::find() - }; - - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); - - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = - OrderInputBuilder { context }.parse_object::(order_by); - - let pagination = ctx.args.get(&context.entity_query_field.pagination); - let pagination = - PaginationInputBuilder { context }.parse_object(pagination); - - let db = ctx.data::()?; - - let connection = if is_via_relation { - // TODO optimize query - let condition = - Condition::all().add(from_col.eq(parent.get(from_col))); - - let stmt = stmt.filter(condition.add(filters)); - let stmt = apply_order(stmt, order_by); - apply_offset_pagination::(db, stmt, pagination).await? - } else { - let loader = ctx.data_unchecked::>>(); - - let key = KeyComplex:: { - key: vec![parent.get(from_col)], - meta: HashableGroupKey:: { - stmt, - columns: vec![to_col], - filters: Some(filters), - order_by, - }, - }; - - let values = loader.load_one(key).await?; - apply_memory_offset_pagination::(values, pagination) - }; - - Ok(Some(FieldValue::list( - connection.into_iter().map(FieldValue::owned_any), - ))) - }) - }) - } else { - Field::new( - name, - TypeRef::named_nn(connection_object_builder.type_name(&object_name)), - move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow - }; - - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => Err::, async_graphql::Error>( - Error::new(reason), - ), - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), - }; - } - - // FIXME: optimize union queries - // NOTE: each has unique query in order to apply pagination... - let parent: &T::Model = ctx - .parent_value - .try_downcast_ref::() - .expect("Parent should exist"); - - let stmt = if >::via().is_some() { - >::find_related() - } else { - R::find() - }; - - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); + // FIXME: optimize union queries + // NOTE: each has unique query in order to apply pagination... + let parent: &T::Model = ctx + .parent_value + .try_downcast_ref::() + .expect("Parent should exist"); - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = - OrderInputBuilder { context }.parse_object::(order_by); + let stmt = if >::via().is_some() { + >::find_related() + } else { + R::find() + }; - let pagination = - ctx.args.get(&context.entity_query_field.pagination); - let pagination = - PaginationInputBuilder { context }.parse_object(pagination); + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); - let db = ctx.data::()?; + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = OrderInputBuilder { context }.parse_object::(order_by); - let connection = if is_via_relation { - // TODO optimize query - let condition = - Condition::all().add(from_col.eq(parent.get(from_col))); + let pagination = ctx.args.get(&context.entity_query_field.pagination); + let pagination = PaginationInputBuilder { context }.parse_object(pagination); - let stmt = stmt.filter(condition.add(filters)); - let stmt = apply_order(stmt, order_by); - apply_pagination::(db, stmt, pagination).await? - } else { - let loader = - ctx.data_unchecked::>>(); + let db = ctx.data::()?; - let key = KeyComplex:: { - key: vec![parent.get(from_col)], - meta: HashableGroupKey:: { - stmt, - columns: vec![to_col], - filters: Some(filters), - order_by, - }, - }; + let object = if is_via_relation { + // TODO optimize query + let condition = Condition::all().add(from_col.eq(parent.get(from_col))); - let values = loader.load_one(key).await?; + let stmt = stmt.filter(condition.add(filters)); + let stmt = apply_order(stmt, order_by); + apply_pagination::(db, stmt, pagination).await? + } else { + let loader = ctx.data_unchecked::>>(); + + let key = KeyComplex:: { + key: vec![parent.get(from_col)], + meta: HashableGroupKey:: { + stmt, + columns: vec![to_col], + filters: Some(filters), + order_by, + }, + }; - apply_memory_pagination(values, pagination) - }; + let values = loader.load_one(key).await?; + apply_memory_pagination::(values, pagination) + }; - Ok(Some(FieldValue::owned_any(connection))) - }) - }, - ) - } - } + Ok(Some(resolver_fn(object))) + }) + }), }; match via_relation_definition.is_owner { diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index d2eed06d..184306cc 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -1,5 +1,3 @@ -use std::os::unix::process; - use async_graphql::{ dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef}, Error, @@ -7,10 +5,11 @@ use async_graphql::{ use heck::ToLowerCamelCase; use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter}; +#[cfg(not(feature = "offset-pagination"))] +use crate::ConnectionObjectBuilder; use crate::{ - apply_offset_pagination, apply_order, apply_pagination, get_filter_conditions, BuilderContext, - ConnectionObjectBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, - OrderInputBuilder, PaginationInputBuilder, + apply_order, apply_pagination, get_filter_conditions, BuilderContext, EntityObjectBuilder, + FilterInputBuilder, GuardAction, OrderInputBuilder, PaginationInputBuilder, }; /// The configuration structure for EntityQueryFieldBuilder @@ -63,6 +62,7 @@ impl EntityQueryFieldBuilder { T: EntityTrait, ::Model: Sync, { + #[cfg(not(feature = "offset-pagination"))] let connection_object_builder = ConnectionObjectBuilder { context: self.context, }; @@ -80,134 +80,66 @@ impl EntityQueryFieldBuilder { }; let object_name = entity_object.type_name::(); - let type_name = if cfg!(feature = "offset-pagination") { - object_name.clone() - } else { - connection_object_builder.type_name(&object_name) - }; #[cfg(feature = "offset-pagination")] - let process_fn = Box::new(|connection| FieldValue::owned_any(connection)); + let type_ref = TypeRef::named_list(&object_name); #[cfg(not(feature = "offset-pagination"))] - let process_fn = Box::new(|connection: Vec| { - FieldValue::list(connection.into_iter().map(FieldValue::owned_any)) - }); - let guard = self.context.guards.entity_guards.get(&object_name); - - let context: &'static BuilderContext = self.context; - + let type_ref = TypeRef::named_nn(connection_object_builder.type_name(&object_name)); #[cfg(feature = "offset-pagination")] - let ttype: Box TypeRef> = - Box::new(|param: String| TypeRef::named_list(param)); + let resolver_fn = + |object: Vec| FieldValue::list(object.into_iter().map(FieldValue::owned_any)); #[cfg(not(feature = "offset-pagination"))] - let ttype = Box::new(|param: String| TypeRef::named_nn(param)); - - if cfg!(feature = "offset-pagination") { - Field::new(self.type_name::(), ttype(type_name), move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow - }; - - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => { - Err::, async_graphql::Error>(Error::new(reason)) - } - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), - }; - } - - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); - let pagination = ctx.args.get(&context.entity_query_field.pagination); - let pagination = PaginationInputBuilder { context }.parse_object(pagination); - - let stmt = T::find(); - let stmt = stmt.filter(filters); - let stmt = apply_order(stmt, order_by); - - let db = ctx.data::()?; - - let connection = apply_offset_pagination::(db, stmt, pagination).await?; + let resolver_fn = |object: crate::Connection| FieldValue::owned_any(object); + let guard = self.context.guards.entity_guards.get(&object_name); - Ok(Some(process_fn(connection))) - }) - }) - .argument(InputValue::new( - &self.context.entity_query_field.filters, - TypeRef::named(filter_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &self.context.entity_query_field.order_by, - TypeRef::named(order_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &self.context.entity_query_field.pagination, - TypeRef::named(pagination_input_builder.type_name()), - )) - } else { - let connection_object_builder = ConnectionObjectBuilder { - context: self.context, - }; + let context: &'static BuilderContext = self.context; - let type_name = connection_object_builder.type_name(&object_name); - Field::new(self.type_name::(), ttype(type_name), move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow + Field::new(self.type_name::(), type_ref, move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; + + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => Err::, async_graphql::Error>(Error::new(reason)), + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), }; + } - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => { - Err::, async_graphql::Error>(Error::new(reason)) - } - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), - }; - } + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let pagination = ctx.args.get(&context.entity_query_field.pagination); + let pagination = PaginationInputBuilder { context }.parse_object(pagination); - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); - let pagination = ctx.args.get(&context.entity_query_field.pagination); - let pagination = PaginationInputBuilder { context }.parse_object(pagination); + let stmt = T::find(); + let stmt = stmt.filter(filters); + let stmt = apply_order(stmt, order_by); - let stmt = T::find(); - let stmt = stmt.filter(filters); - let stmt = apply_order(stmt, order_by); + let db = ctx.data::()?; - let db = ctx.data::()?; + let object = apply_pagination::(db, stmt, pagination).await?; - let connection = apply_pagination::(db, stmt, pagination).await?; - - Ok(Some(process_fn(connection))) - }) + Ok(Some(resolver_fn(object))) }) - .argument(InputValue::new( - &self.context.entity_query_field.filters, - TypeRef::named(filter_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &self.context.entity_query_field.order_by, - TypeRef::named(order_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &self.context.entity_query_field.pagination, - TypeRef::named(pagination_input_builder.type_name()), - )) - } + }) + .argument(InputValue::new( + &self.context.entity_query_field.filters, + TypeRef::named(filter_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &self.context.entity_query_field.order_by, + TypeRef::named(order_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &self.context.entity_query_field.pagination, + TypeRef::named(pagination_input_builder.type_name()), + )) } } \ No newline at end of file diff --git a/src/query/pagination.rs b/src/query/pagination.rs index 2b4de4a2..48212401 100644 --- a/src/query/pagination.rs +++ b/src/query/pagination.rs @@ -1,17 +1,20 @@ +#[allow(unused_imports)] +use crate::{ + decode_cursor, encode_cursor, map_cursor_values, Connection, Edge, PageInfo, PaginationInfo, + PaginationInput, +}; +#[cfg(not(feature = "offset-pagination"))] use itertools::Itertools; #[allow(unused_imports)] use sea_orm::CursorTrait; +#[allow(unused_imports)] use sea_orm::{ ConnectionTrait, DatabaseConnection, EntityTrait, Iterable, ModelTrait, PaginatorTrait, PrimaryKeyToColumn, QuerySelect, QueryTrait, Select, }; -use crate::{ - decode_cursor, encode_cursor, map_cursor_values, Connection, Edge, PageInfo, PaginationInfo, - PaginationInput, -}; - /// used to parse pagination input object and apply it to statement +#[cfg(not(feature = "offset-pagination"))] pub async fn apply_pagination( db: &DatabaseConnection, stmt: Select, @@ -263,7 +266,8 @@ where } } -pub async fn apply_offset_pagination( +#[cfg(feature = "offset-pagination")] +pub async fn apply_pagination( db: &DatabaseConnection, stmt: Select, pagination: PaginationInput, @@ -286,6 +290,7 @@ where } } +#[cfg(not(feature = "offset-pagination"))] pub fn apply_memory_pagination( values: Option>, pagination: PaginationInput, @@ -435,7 +440,8 @@ where } } -pub fn apply_memory_offset_pagination( +#[cfg(feature = "offset-pagination")] +pub fn apply_memory_pagination( values: Option>, pagination: PaginationInput, ) -> Vec From d46567bf829b8585f594768157e9e77ad55f4f6a Mon Sep 17 00:00:00 2001 From: Heikel Date: Tue, 15 Oct 2024 10:54:52 +0100 Subject: [PATCH 03/22] use convenient sea-orm version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a400f252..918d48a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ categories = ["database"] [dependencies] async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] } -sea-orm = { version = "~1.1.0-rc.1", default-features = false, features = ["seaography"] } +sea-orm = { version = "1.0.*", default-features = false, features = ["seaography"] } itertools = { version = "0.12.0" } heck = { version = "0.4.1" } thiserror = { version = "1.0.44" } From 249ddf1b810ad90abe4a626919bbe6ff8310f2c3 Mon Sep 17 00:00:00 2001 From: Heikel Date: Wed, 16 Oct 2024 22:20:35 +0100 Subject: [PATCH 04/22] add cascade --- src/query/cascading.rs | 14 +++++ src/query/entity_query_field.rs | 92 ++++++++++++++++++++++----------- src/query/mod.rs | 3 ++ 3 files changed, 78 insertions(+), 31 deletions(-) create mode 100644 src/query/cascading.rs diff --git a/src/query/cascading.rs b/src/query/cascading.rs new file mode 100644 index 00000000..e5dd2e6f --- /dev/null +++ b/src/query/cascading.rs @@ -0,0 +1,14 @@ +use async_graphql::dynamic::ValueAccessor; + +pub fn get_cascade_conditions(cascades: Option) -> Vec { + if let Some(cascades) = cascades { + cascades + .list() + .unwrap() + .iter() + .map(|field| field.string().unwrap().to_string()) + .collect::>() + } else { + Vec::new() + } +} \ No newline at end of file diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index 184306cc..d53a3405 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -3,7 +3,9 @@ use async_graphql::{ Error, }; use heck::ToLowerCamelCase; -use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter}; +use sea_orm::{ + DatabaseConnection, EntityTrait, Iterable, JoinType, QueryFilter, QuerySelect, RelationTrait, +}; #[cfg(not(feature = "offset-pagination"))] use crate::ConnectionObjectBuilder; @@ -12,6 +14,8 @@ use crate::{ FilterInputBuilder, GuardAction, OrderInputBuilder, PaginationInputBuilder, }; +use super::get_cascade_conditions; + /// The configuration structure for EntityQueryFieldBuilder pub struct EntityQueryFieldConfig { /// used to format entity field name @@ -93,42 +97,68 @@ impl EntityQueryFieldBuilder { let context: &'static BuilderContext = self.context; - Field::new(self.type_name::(), type_ref, move |ctx| { - let context: &'static BuilderContext = context; - FieldFuture::new(async move { - let guard_flag = if let Some(guard) = guard { - (*guard)(&ctx) - } else { - GuardAction::Allow - }; - - if let GuardAction::Block(reason) = guard_flag { - return match reason { - Some(reason) => Err::, async_graphql::Error>(Error::new(reason)), - None => Err::, async_graphql::Error>(Error::new( - "Entity guard triggered.", - )), - }; - } + Field::new(self.type_name::(), type_ref, { + move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new({ + async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; - let filters = ctx.args.get(&context.entity_query_field.filters); - let filters = get_filter_conditions::(context, filters); - let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); - let pagination = ctx.args.get(&context.entity_query_field.pagination); - let pagination = PaginationInputBuilder { context }.parse_object(pagination); + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => { + Err::, async_graphql::Error>(Error::new(reason)) + } + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } - let stmt = T::find(); - let stmt = stmt.filter(filters); - let stmt = apply_order(stmt, order_by); + let filters = ctx.args.get(&context.entity_query_field.filters); + let filters = get_filter_conditions::(context, filters); + let order_by = ctx.args.get(&context.entity_query_field.order_by); + let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let pagination = ctx.args.get(&context.entity_query_field.pagination); + let pagination = + PaginationInputBuilder { context }.parse_object(pagination); + let cascades = ctx.args.get("cascade"); + let cascades = get_cascade_conditions(cascades); + let stmt = T::Relation::iter().fold(T::find(), |stmt, related_table| { + let related_table_name = related_table.def().to_tbl; + let related_table_name = match related_table_name { + sea_orm::sea_query::TableRef::Table(iden) => { + if cascades.contains(&iden.to_string()) { + stmt.join(JoinType::InnerJoin, related_table.def()) + .distinct() + } else { + stmt + } + } + _ => stmt, + }; + related_table_name + }); + let stmt = stmt.filter(filters); + let stmt = apply_order(stmt, order_by); - let db = ctx.data::()?; + let db = ctx.data::()?; - let object = apply_pagination::(db, stmt, pagination).await?; + let object = apply_pagination::(db, stmt, pagination).await?; - Ok(Some(resolver_fn(object))) - }) + Ok(Some(resolver_fn(object))) + } + }) + } }) + .argument(InputValue::new( + "cascade", + TypeRef::named_list(TypeRef::STRING), + )) .argument(InputValue::new( &self.context.entity_query_field.filters, TypeRef::named(filter_input_builder.type_name(&object_name)), diff --git a/src/query/mod.rs b/src/query/mod.rs index 4c9f9f15..b64258c8 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -18,3 +18,6 @@ pub use entity_object_relation::*; pub mod entity_object_via_relation; pub use entity_object_via_relation::*; + +pub mod cascading; +pub use cascading::*; \ No newline at end of file From 35468c9e17ef320f51ad14cebdabb345490a27af Mon Sep 17 00:00:00 2001 From: Heikel Date: Fri, 18 Oct 2024 11:09:54 +0100 Subject: [PATCH 05/22] make filter close to dgraph, and improve the cascade to work when we specify the database and schema --- src/query/entity_query_field.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index d53a3405..d96cf8e9 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -34,7 +34,7 @@ impl std::default::Default for EntityQueryFieldConfig { type_name: Box::new(|object_name: &str| -> String { object_name.to_lower_camel_case() }), - filters: "filters".into(), + filters: "filter".into(), order_by: "orderBy".into(), pagination: "pagination".into(), } @@ -130,7 +130,7 @@ impl EntityQueryFieldBuilder { let cascades = get_cascade_conditions(cascades); let stmt = T::Relation::iter().fold(T::find(), |stmt, related_table| { let related_table_name = related_table.def().to_tbl; - let related_table_name = match related_table_name { + match related_table_name { sea_orm::sea_query::TableRef::Table(iden) => { if cascades.contains(&iden.to_string()) { stmt.join(JoinType::InnerJoin, related_table.def()) @@ -139,9 +139,24 @@ impl EntityQueryFieldBuilder { stmt } } + sea_orm::sea_query::TableRef::SchemaTable(_, iden) => { + if cascades.contains(&iden.to_string()) { + stmt.join(JoinType::InnerJoin, related_table.def()) + .distinct() + } else { + stmt + } + } + sea_orm::sea_query::TableRef::DatabaseSchemaTable(_, _, iden) => { + if cascades.contains(&iden.to_string()) { + stmt.join(JoinType::InnerJoin, related_table.def()) + .distinct() + } else { + stmt + } + } _ => stmt, - }; - related_table_name + } }); let stmt = stmt.filter(filters); let stmt = apply_order(stmt, order_by); From 61a2ca84fd87d9e4bfb44f9ea9a3af5a6b9d4b7f Mon Sep 17 00:00:00 2001 From: Heikel Date: Sat, 26 Oct 2024 12:41:07 +0100 Subject: [PATCH 06/22] test: check webhook --- src/builder.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/builder.rs b/src/builder.rs index 44dea5fc..480f18e3 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -82,6 +82,7 @@ impl Builder { entity_object_builder.to_object::(), |entity_object, field| entity_object.field(field), ); + println!("test webhook"); if cfg!(feature = "offset-pagination") { self.outputs.extend(vec![entity_object]); From 8a8585edc9d10e672fb758c006b1f8b467c58c0d Mon Sep 17 00:00:00 2001 From: Heikel Date: Sat, 26 Oct 2024 12:48:01 +0100 Subject: [PATCH 07/22] test: check webhook --- src/builder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index 480f18e3..44dea5fc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -82,7 +82,6 @@ impl Builder { entity_object_builder.to_object::(), |entity_object, field| entity_object.field(field), ); - println!("test webhook"); if cfg!(feature = "offset-pagination") { self.outputs.extend(vec![entity_object]); From 74daa4126aefb590ae94d4475d84e6138863c99d Mon Sep 17 00:00:00 2001 From: Heikel Date: Sat, 26 Oct 2024 13:00:46 +0100 Subject: [PATCH 08/22] feat: make discord notification as discord action --- .github/workflows/discord-pr-notifications.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/discord-pr-notifications.yaml diff --git a/.github/workflows/discord-pr-notifications.yaml b/.github/workflows/discord-pr-notifications.yaml new file mode 100644 index 00000000..5fe96ef2 --- /dev/null +++ b/.github/workflows/discord-pr-notifications.yaml @@ -0,0 +1,17 @@ +name: Pull Request Notification to Discord + +on: + pull_request: + types: [opened, closed] + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Send notification to Discord + env: + WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + curl -X POST -H "Content-Type: application/json" \ + -d "{\"content\": \"🔔 New Pull Request: **${{ github.event.pull_request.title }}** by ${{ github.actor }} - ${{ github.event.pull_request.html_url }}\"}" \ + $WEBHOOK_URL \ No newline at end of file From 18941bff35aef21b0a67f440caeaaa448b09b1fb Mon Sep 17 00:00:00 2001 From: Heikel Date: Thu, 7 Nov 2024 01:43:49 +0100 Subject: [PATCH 09/22] feat: add first for the queries, add getEntity, and change is_in to in --- src/builder.rs | 18 ++-- src/builder_context.rs | 11 ++- src/builder_context/filter_types_map.rs | 11 +-- src/inputs/active_enum_filter_input.rs | 4 +- src/query/entity_get_field.rs | 123 ++++++++++++++++++++++++ src/query/entity_query_field.rs | 61 +++++++++++- src/query/mod.rs | 6 +- 7 files changed, 209 insertions(+), 25 deletions(-) create mode 100644 src/query/entity_get_field.rs diff --git a/src/builder.rs b/src/builder.rs index 44dea5fc..ce9658bf 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -7,11 +7,11 @@ use sea_orm::{ActiveEnum, ActiveModelTrait, EntityTrait, IntoActiveModel}; use crate::{ ActiveEnumBuilder, ActiveEnumFilterInputBuilder, BuilderContext, ConnectionObjectBuilder, CursorInputBuilder, EdgeObjectBuilder, EntityCreateBatchMutationBuilder, - EntityCreateOneMutationBuilder, EntityDeleteMutationBuilder, EntityInputBuilder, - EntityObjectBuilder, EntityQueryFieldBuilder, EntityUpdateMutationBuilder, FilterInputBuilder, - FilterTypesMapHelper, OffsetInputBuilder, OneToManyLoader, OneToOneLoader, OrderByEnumBuilder, - OrderInputBuilder, PageInfoObjectBuilder, PageInputBuilder, PaginationInfoObjectBuilder, - PaginationInputBuilder, + EntityCreateOneMutationBuilder, EntityDeleteMutationBuilder, EntityGetFieldBuilder, + EntityInputBuilder, EntityObjectBuilder, EntityQueryFieldBuilder, EntityUpdateMutationBuilder, + FilterInputBuilder, FilterTypesMapHelper, OffsetInputBuilder, OneToManyLoader, OneToOneLoader, + OrderByEnumBuilder, OrderInputBuilder, PageInfoObjectBuilder, PageInputBuilder, + PaginationInfoObjectBuilder, PaginationInputBuilder, }; /// The Builder is used to create the Schema for GraphQL @@ -117,6 +117,12 @@ impl Builder { }; let query = entity_query_field_builder.to_field::(); self.queries.push(query); + + let entity_get_field_builder = EntityGetFieldBuilder { + context: self.context, + }; + let get_query = entity_get_field_builder.to_field::(); + self.queries.push(get_query); } pub fn register_entity_mutations(&mut self) @@ -360,4 +366,4 @@ macro_rules! register_entities_without_relation { ($builder:expr, [$($module_paths:ident),+ $(,)?]) => { $(seaography::register_entity_without_relation!($builder, $module_paths);)* }; -} \ No newline at end of file +} diff --git a/src/builder_context.rs b/src/builder_context.rs index 7beaf99c..3e44bc21 100644 --- a/src/builder_context.rs +++ b/src/builder_context.rs @@ -1,10 +1,10 @@ use crate::{ ActiveEnumConfig, ActiveEnumFilterInputConfig, ConnectionObjectConfig, CursorInputConfig, EdgeObjectConfig, EntityCreateBatchMutationConfig, EntityCreateOneMutationConfig, - EntityDeleteMutationConfig, EntityInputConfig, EntityObjectConfig, EntityQueryFieldConfig, - EntityUpdateMutationConfig, FilterInputConfig, OffsetInputConfig, OrderByEnumConfig, - OrderInputConfig, PageInfoObjectConfig, PageInputConfig, PaginationInfoObjectConfig, - PaginationInputConfig, + EntityDeleteMutationConfig, EntityGetFieldConfig, EntityInputConfig, EntityObjectConfig, + EntityQueryFieldConfig, EntityUpdateMutationConfig, FilterInputConfig, OffsetInputConfig, + OrderByEnumConfig, OrderInputConfig, PageInfoObjectConfig, PageInputConfig, + PaginationInfoObjectConfig, PaginationInputConfig, }; pub mod guards; @@ -41,6 +41,7 @@ pub struct BuilderContext { pub entity_object: EntityObjectConfig, pub connection_object: ConnectionObjectConfig, pub entity_query_field: EntityQueryFieldConfig, + pub entity_get_field: EntityGetFieldConfig, pub entity_create_one_mutation: EntityCreateOneMutationConfig, pub entity_create_batch_mutation: EntityCreateBatchMutationConfig, @@ -54,4 +55,4 @@ pub struct BuilderContext { pub filter_types: FilterTypesMapConfig, // is_skipped function // naming function -} +} \ No newline at end of file diff --git a/src/builder_context/filter_types_map.rs b/src/builder_context/filter_types_map.rs index 2282e513..f4f8a7c5 100644 --- a/src/builder_context/filter_types_map.rs +++ b/src/builder_context/filter_types_map.rs @@ -343,10 +343,9 @@ impl FilterTypesMapHelper { FilterOperation::LessThanEquals => { InputValue::new("lte", TypeRef::named(filter_info.base_type.clone())) } - FilterOperation::IsIn => InputValue::new( - "is_in", - TypeRef::named_nn_list(filter_info.base_type.clone()), - ), + FilterOperation::IsIn => { + InputValue::new("in", TypeRef::named_nn_list(filter_info.base_type.clone())) + } FilterOperation::IsNotIn => InputValue::new( "is_not_in", TypeRef::named_nn_list(filter_info.base_type.clone()), @@ -483,7 +482,7 @@ impl FilterTypesMapHelper { } } FilterOperation::IsIn => { - if let Some(value) = filter.get("is_in") { + if let Some(value) = filter.get("in") { let value = value .list()? .iter() @@ -632,4 +631,4 @@ pub enum FilterOperation { NotLike, Between, NotBetween, -} +} \ No newline at end of file diff --git a/src/inputs/active_enum_filter_input.rs b/src/inputs/active_enum_filter_input.rs index 8c24fe49..a2cd8286 100644 --- a/src/inputs/active_enum_filter_input.rs +++ b/src/inputs/active_enum_filter_input.rs @@ -139,7 +139,7 @@ where condition }; - let condition = match filter.get("is_in") { + let condition = match filter.get("in") { Some(data) => { let data: Vec<_> = data .list() @@ -183,4 +183,4 @@ where }; Ok(condition) -} +} \ No newline at end of file diff --git a/src/query/entity_get_field.rs b/src/query/entity_get_field.rs new file mode 100644 index 00000000..cc860efa --- /dev/null +++ b/src/query/entity_get_field.rs @@ -0,0 +1,123 @@ +use async_graphql::{ + dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef}, + Error, +}; +use heck::ToLowerCamelCase; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, Iterable, QueryFilter}; + +use crate::{BuilderContext, EntityObjectBuilder, GuardAction, TypesMapHelper}; + +/// The configuration structure for EntityQueryFieldBuilder +pub struct EntityGetFieldConfig { + /// used to format entity field name + pub type_name: crate::SimpleNamingFn, +} + +impl std::default::Default for EntityGetFieldConfig { + fn default() -> Self { + EntityGetFieldConfig { + type_name: Box::new(|object_name: &str| -> String { + ("get".to_owned() + object_name).to_lower_camel_case() + }), + } + } +} + +/// This builder produces a field for the Query object that queries a SeaORM entity +pub struct EntityGetFieldBuilder { + pub context: &'static BuilderContext, +} + +impl EntityGetFieldBuilder { + /// used to get field name for a SeaORM entity + pub fn type_name(&self) -> String + where + T: EntityTrait, + ::Model: Sync, + { + let entity_object = EntityObjectBuilder { + context: self.context, + }; + let object_name = entity_object.type_name::(); + self.context.entity_get_field.type_name.as_ref()(&object_name) + } + + /// used to get the Query object field for a SeaORM entity + pub fn to_field(&self) -> Field + where + T: EntityTrait, + ::Model: Sync, + { + let entity_object = EntityObjectBuilder { + context: self.context, + }; + + let object_name = entity_object.type_name::(); + let type_ref = TypeRef::named(&object_name); + let resolver_fn = |object: T::Model| FieldValue::owned_any(object); + let guard = self.context.guards.entity_guards.get(&object_name); + + let context: &'static BuilderContext = self.context; + + let field = Field::new(self.type_name::(), type_ref, { + move |ctx| { + let context: &'static BuilderContext = context; + FieldFuture::new({ + async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; + + if let GuardAction::Block(reason) = guard_flag { + return match reason { + Some(reason) => { + Err::, async_graphql::Error>(Error::new(reason)) + } + None => Err::, async_graphql::Error>(Error::new( + "Entity guard triggered.", + )), + }; + } + + let stmt = T::find(); + let stmt = T::Column::iter().fold(stmt, |stmt, column| { + let entity_object_builder = EntityObjectBuilder { context }; + let column_name = entity_object_builder.column_name::(&column); + let types_map_helper = TypesMapHelper { context }; + match ctx.args.get(&column_name) { + Some(val) => stmt.filter( + column.eq(types_map_helper + .async_graphql_value_to_sea_orm_value::(&column, &val) + .unwrap()), + ), + _ => stmt, + } + }); + + let db = ctx.data::()?; + + let object = stmt.one(db).await?; + + match object { + Some(object) => Ok(Some(resolver_fn(object))), + _ => Ok(Some(FieldValue::NULL)), + } + } + }) + } + }); + + T::Column::iter().fold(field, |field, column| { + let column_name = entity_object.column_name::(&column); + let types_map_helper = TypesMapHelper { context }; + field.argument(InputValue::new( + column_name, + types_map_helper + .sea_orm_column_type_to_graphql_type(column.def().get_column_type(), false) + .unwrap(), + )) + }) + } +} \ No newline at end of file diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index d96cf8e9..3466c124 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -3,15 +3,18 @@ use async_graphql::{ Error, }; use heck::ToLowerCamelCase; +use itertools::Itertools; use sea_orm::{ - DatabaseConnection, EntityTrait, Iterable, JoinType, QueryFilter, QuerySelect, RelationTrait, + ColumnTrait, DatabaseConnection, EntityTrait, Iterable, JoinType, PrimaryKeyToColumn, + QueryFilter, QuerySelect, RelationTrait, }; #[cfg(not(feature = "offset-pagination"))] use crate::ConnectionObjectBuilder; use crate::{ - apply_order, apply_pagination, get_filter_conditions, BuilderContext, EntityObjectBuilder, - FilterInputBuilder, GuardAction, OrderInputBuilder, PaginationInputBuilder, + apply_order, apply_pagination, filter_types_map, get_filter_conditions, BuilderContext, + EntityObjectBuilder, FilterInputBuilder, FilterTypesMapHelper, GuardAction, OrderInputBuilder, + PaginationInputBuilder, TypesMapHelper, }; use super::get_cascade_conditions; @@ -159,11 +162,28 @@ impl EntityQueryFieldBuilder { } }); let stmt = stmt.filter(filters); + let stmt = apply_order(stmt, order_by); let db = ctx.data::()?; - let object = apply_pagination::(db, stmt, pagination).await?; + #[cfg(feature = "offset-pagination")] + let first = ctx.args.get("first"); + #[cfg(feature = "offset-pagination")] + let object = match first { + Some(first_value) => match first_value.u64() { + Ok(first_num) => { + apply_stmt_cursor_by::(stmt) + .first(first_num) + .all(db) + .await? + } + _error => apply_pagination(db, stmt, pagination).await?, + }, + None => apply_pagination::(db, stmt, pagination).await?, + }; + #[cfg(not(feature = "offset-pagination"))] + let object = apply_pagination(db, stmt, pagination).await?; Ok(Some(resolver_fn(object))) } @@ -186,5 +206,36 @@ impl EntityQueryFieldBuilder { &self.context.entity_query_field.pagination, TypeRef::named(pagination_input_builder.type_name()), )) + .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } -} \ No newline at end of file +} +#[cfg(feature = "offset-pagination")] +fn apply_stmt_cursor_by( + stmt: sea_orm::entity::prelude::Select, +) -> sea_orm::Cursor> +where + T: EntityTrait, + ::Model: Sync, +{ + let size = T::PrimaryKey::iter().fold(0, |acc, _| acc + 1); + if size == 1 { + let column = T::PrimaryKey::iter() + .map(|variant| variant.into_column()) + .collect::>()[0]; + stmt.cursor_by(column) + } else if size == 2 { + let columns = T::PrimaryKey::iter() + .map(|variant| variant.into_column()) + .collect_tuple::<(T::Column, T::Column)>() + .unwrap(); + stmt.cursor_by(columns) + } else if size == 3 { + let columns = T::PrimaryKey::iter() + .map(|variant| variant.into_column()) + .collect_tuple::<(T::Column, T::Column, T::Column)>() + .unwrap(); + stmt.cursor_by(columns) + } else { + panic!("seaography does not support cursors with size greater than 3") + } +} diff --git a/src/query/mod.rs b/src/query/mod.rs index b64258c8..36d8dc22 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -20,4 +20,8 @@ pub mod entity_object_via_relation; pub use entity_object_via_relation::*; pub mod cascading; -pub use cascading::*; \ No newline at end of file +pub use cascading::*; + + +pub mod entity_get_field; +pub use entity_get_field::*; \ No newline at end of file From 5c443507f77ddfc18254c5e13bb2961e60878632 Mon Sep 17 00:00:00 2001 From: Heikel Date: Thu, 7 Nov 2024 21:08:36 +0100 Subject: [PATCH 10/22] feat: add not in the filter --- src/inputs/filter_input.rs | 3 ++- src/query/filtering.rs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/inputs/filter_input.rs b/src/inputs/filter_input.rs index d591e3c3..5db75daa 100644 --- a/src/inputs/filter_input.rs +++ b/src/inputs/filter_input.rs @@ -57,5 +57,6 @@ impl FilterInputBuilder { object .field(InputValue::new("and", TypeRef::named_nn_list(&filter_name))) .field(InputValue::new("or", TypeRef::named_nn_list(&filter_name))) + .field(InputValue::new("not", TypeRef::named(&filter_name))) } -} +} \ No newline at end of file diff --git a/src/query/filtering.rs b/src/query/filtering.rs index 8a27a45b..634c7f0e 100644 --- a/src/query/filtering.rs +++ b/src/query/filtering.rs @@ -80,5 +80,16 @@ where condition }; + let condition = if let Some(not) = filters.get("not") { + let filter = not.object().unwrap(); + condition.add( + Condition::all() + .add(recursive_prepare_condition::(context, filter)) + .not(), + ) + } else { + condition + }; + condition -} +} \ No newline at end of file From 4f79dc2cb0b6e32dd31f98105917f75ae251c7f0 Mon Sep 17 00:00:00 2001 From: Heikel Date: Sat, 9 Nov 2024 12:54:51 +0100 Subject: [PATCH 11/22] fix: Enum values --- src/enumerations/active_enum.rs | 4 ++-- src/inputs/active_enum_filter_input.rs | 7 ++----- src/outputs/entity_object.rs | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/enumerations/active_enum.rs b/src/enumerations/active_enum.rs index 163e3e14..e39c4ece 100644 --- a/src/enumerations/active_enum.rs +++ b/src/enumerations/active_enum.rs @@ -19,7 +19,7 @@ impl std::default::Default for ActiveEnumConfig { format!("{}Enum", name.to_upper_camel_case()) }), variant_name: Box::new(|_enum_name: &str, variant: &str| -> String { - variant.to_upper_camel_case().to_ascii_uppercase() + variant.to_upper_camel_case() }), } } @@ -60,4 +60,4 @@ impl ActiveEnumBuilder { enumeration.item(self.variant_name(&enum_name, &variant)) }) } -} +} \ No newline at end of file diff --git a/src/inputs/active_enum_filter_input.rs b/src/inputs/active_enum_filter_input.rs index a2cd8286..9251cf78 100644 --- a/src/inputs/active_enum_filter_input.rs +++ b/src/inputs/active_enum_filter_input.rs @@ -88,10 +88,7 @@ where let extract_variant = move |input: &str| -> String { let variant = variants.iter().find(|variant| { - let variant = variant - .to_string() - .to_upper_camel_case() - .to_ascii_uppercase(); + let variant = variant.to_string().to_upper_camel_case(); variant.eq(input) }); variant.unwrap().to_string() @@ -183,4 +180,4 @@ where }; Ok(condition) -} \ No newline at end of file +} diff --git a/src/outputs/entity_object.rs b/src/outputs/entity_object.rs index fbc3789f..5f3d47af 100644 --- a/src/outputs/entity_object.rs +++ b/src/outputs/entity_object.rs @@ -209,7 +209,7 @@ fn sea_query_value_to_graphql_value( sea_orm::Value::Float(value) => value.map(Value::from), sea_orm::Value::Double(value) => value.map(Value::from), sea_orm::Value::String(value) if is_enum => { - value.map(|it| Value::from(it.as_str().to_upper_camel_case().to_ascii_uppercase())) + value.map(|it| Value::from(it.as_str().to_upper_camel_case())) } sea_orm::Value::String(value) => value.map(|it| Value::from(it.as_str())), sea_orm::Value::Char(value) => value.map(|it| Value::from(it.to_string())), @@ -305,4 +305,4 @@ fn sea_query_value_to_graphql_value( #[allow(unreachable_patterns)] _ => panic!("Cannot convert SeaORM value"), } -} +} \ No newline at end of file From cc2dbdac97fd4de6d83e1fc1a871b04e2c054740 Mon Sep 17 00:00:00 2001 From: Heikel Date: Sat, 9 Nov 2024 14:27:11 +0100 Subject: [PATCH 12/22] fix: Enum values --- src/enumerations/active_enum.rs | 4 ++-- src/inputs/active_enum_filter_input.rs | 4 ++-- src/outputs/entity_object.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/enumerations/active_enum.rs b/src/enumerations/active_enum.rs index e39c4ece..c83b58ae 100644 --- a/src/enumerations/active_enum.rs +++ b/src/enumerations/active_enum.rs @@ -19,7 +19,7 @@ impl std::default::Default for ActiveEnumConfig { format!("{}Enum", name.to_upper_camel_case()) }), variant_name: Box::new(|_enum_name: &str, variant: &str| -> String { - variant.to_upper_camel_case() + variant.to_owned().replace("'", "") }), } } @@ -60,4 +60,4 @@ impl ActiveEnumBuilder { enumeration.item(self.variant_name(&enum_name, &variant)) }) } -} \ No newline at end of file +} diff --git a/src/inputs/active_enum_filter_input.rs b/src/inputs/active_enum_filter_input.rs index 9251cf78..f631e127 100644 --- a/src/inputs/active_enum_filter_input.rs +++ b/src/inputs/active_enum_filter_input.rs @@ -88,7 +88,7 @@ where let extract_variant = move |input: &str| -> String { let variant = variants.iter().find(|variant| { - let variant = variant.to_string().to_upper_camel_case(); + let variant = variant.to_string().replace("'", ""); variant.eq(input) }); variant.unwrap().to_string() @@ -180,4 +180,4 @@ where }; Ok(condition) -} +} \ No newline at end of file diff --git a/src/outputs/entity_object.rs b/src/outputs/entity_object.rs index 5f3d47af..31725621 100644 --- a/src/outputs/entity_object.rs +++ b/src/outputs/entity_object.rs @@ -209,7 +209,7 @@ fn sea_query_value_to_graphql_value( sea_orm::Value::Float(value) => value.map(Value::from), sea_orm::Value::Double(value) => value.map(Value::from), sea_orm::Value::String(value) if is_enum => { - value.map(|it| Value::from(it.as_str().to_upper_camel_case())) + value.map(|it| Value::from(it.replace("'", "").as_str())) } sea_orm::Value::String(value) => value.map(|it| Value::from(it.as_str())), sea_orm::Value::Char(value) => value.map(|it| Value::from(it.to_string())), From f6d5c2c501a8f067adee4eb3b36f960d2a8aae76 Mon Sep 17 00:00:00 2001 From: Heikel Date: Sun, 10 Nov 2024 12:26:55 +0100 Subject: [PATCH 13/22] feat: add order like dgraph, and correct first --- src/builder.rs | 30 +++++--- src/builder_context.rs | 10 +-- src/enumerations/mod.rs | 3 + src/enumerations/order_enum.rs | 48 +++++++++++++ src/inputs/mod.rs | 3 + src/inputs/new_order_input.rs | 92 +++++++++++++++++++++++++ src/query/entity_object_relation.rs | 90 ++++++++++++++++++------ src/query/entity_object_via_relation.rs | 89 ++++++++++++++++++------ src/query/entity_query_field.rs | 61 ++++++++++++---- 9 files changed, 358 insertions(+), 68 deletions(-) create mode 100644 src/enumerations/order_enum.rs create mode 100644 src/inputs/new_order_input.rs diff --git a/src/builder.rs b/src/builder.rs index ce9658bf..dcd74e49 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5,13 +5,14 @@ use async_graphql::{ use sea_orm::{ActiveEnum, ActiveModelTrait, EntityTrait, IntoActiveModel}; use crate::{ - ActiveEnumBuilder, ActiveEnumFilterInputBuilder, BuilderContext, ConnectionObjectBuilder, - CursorInputBuilder, EdgeObjectBuilder, EntityCreateBatchMutationBuilder, - EntityCreateOneMutationBuilder, EntityDeleteMutationBuilder, EntityGetFieldBuilder, - EntityInputBuilder, EntityObjectBuilder, EntityQueryFieldBuilder, EntityUpdateMutationBuilder, - FilterInputBuilder, FilterTypesMapHelper, OffsetInputBuilder, OneToManyLoader, OneToOneLoader, - OrderByEnumBuilder, OrderInputBuilder, PageInfoObjectBuilder, PageInputBuilder, - PaginationInfoObjectBuilder, PaginationInputBuilder, + order_enum, ActiveEnumBuilder, ActiveEnumFilterInputBuilder, BuilderContext, + ConnectionObjectBuilder, CursorInputBuilder, EdgeObjectBuilder, + EntityCreateBatchMutationBuilder, EntityCreateOneMutationBuilder, EntityDeleteMutationBuilder, + EntityGetFieldBuilder, EntityInputBuilder, EntityObjectBuilder, EntityQueryFieldBuilder, + EntityUpdateMutationBuilder, FilterInputBuilder, FilterTypesMapHelper, NewOrderInputBuilder, + OffsetInputBuilder, OneToManyLoader, OneToOneLoader, OrderByEnumBuilder, OrderEnumBuilder, + OrderInputBuilder, PageInfoObjectBuilder, PageInputBuilder, PaginationInfoObjectBuilder, + PaginationInputBuilder, }; /// The Builder is used to create the Schema for GraphQL @@ -110,7 +111,18 @@ impl Builder { context: self.context, }; let order = order_input_builder.to_object::(); - self.inputs.extend(vec![filter, order]); + + let new_order_input_builder = NewOrderInputBuilder { + context: self.context, + }; + let new_order = new_order_input_builder.to_object::(); + self.inputs.extend(vec![filter, order, new_order]); + + let order_enum_builder = OrderEnumBuilder { + context: self.context, + }; + let order_enum = order_enum_builder.enumeration::(); + self.enumerations.push(order_enum); let entity_query_field_builder = EntityQueryFieldBuilder { context: self.context, @@ -366,4 +378,4 @@ macro_rules! register_entities_without_relation { ($builder:expr, [$($module_paths:ident),+ $(,)?]) => { $(seaography::register_entity_without_relation!($builder, $module_paths);)* }; -} +} \ No newline at end of file diff --git a/src/builder_context.rs b/src/builder_context.rs index 3e44bc21..66d12a28 100644 --- a/src/builder_context.rs +++ b/src/builder_context.rs @@ -2,9 +2,9 @@ use crate::{ ActiveEnumConfig, ActiveEnumFilterInputConfig, ConnectionObjectConfig, CursorInputConfig, EdgeObjectConfig, EntityCreateBatchMutationConfig, EntityCreateOneMutationConfig, EntityDeleteMutationConfig, EntityGetFieldConfig, EntityInputConfig, EntityObjectConfig, - EntityQueryFieldConfig, EntityUpdateMutationConfig, FilterInputConfig, OffsetInputConfig, - OrderByEnumConfig, OrderInputConfig, PageInfoObjectConfig, PageInputConfig, - PaginationInfoObjectConfig, PaginationInputConfig, + EntityQueryFieldConfig, EntityUpdateMutationConfig, FilterInputConfig, NewOrderInputConfig, + OffsetInputConfig, OrderByEnumConfig, OrderEnumConfig, OrderInputConfig, PageInfoObjectConfig, + PageInputConfig, PaginationInfoObjectConfig, PaginationInputConfig, }; pub mod guards; @@ -23,6 +23,7 @@ pub use filter_types_map::*; #[derive(Default)] pub struct BuilderContext { pub order_by_enum: OrderByEnumConfig, + pub order_enum: OrderEnumConfig, pub active_enum: ActiveEnumConfig, pub cursor_input: CursorInputConfig, @@ -31,6 +32,7 @@ pub struct BuilderContext { pub pagination_input: PaginationInputConfig, pub order_input: OrderInputConfig, + pub new_order_input: NewOrderInputConfig, pub filter_input: FilterInputConfig, pub active_enum_filter_input: ActiveEnumFilterInputConfig, @@ -55,4 +57,4 @@ pub struct BuilderContext { pub filter_types: FilterTypesMapConfig, // is_skipped function // naming function -} \ No newline at end of file +} diff --git a/src/enumerations/mod.rs b/src/enumerations/mod.rs index 28e20c28..3b5d360a 100644 --- a/src/enumerations/mod.rs +++ b/src/enumerations/mod.rs @@ -3,3 +3,6 @@ pub use order_by_enum::*; pub mod active_enum; pub use active_enum::*; + +pub mod order_enum; +pub use order_enum::*; \ No newline at end of file diff --git a/src/enumerations/order_enum.rs b/src/enumerations/order_enum.rs new file mode 100644 index 00000000..2e210590 --- /dev/null +++ b/src/enumerations/order_enum.rs @@ -0,0 +1,48 @@ +use async_graphql::dynamic::{Enum, EnumItem}; +use sea_orm::{EntityTrait, Iterable}; + +use crate::{BuilderContext, EntityObjectBuilder}; + +/// The configuration structure for OrderByEnumBuilder +pub struct OrderEnumConfig { + /// the enumeration name + pub type_name: crate::SimpleNamingFn, +} + +impl std::default::Default for OrderEnumConfig { + fn default() -> Self { + OrderEnumConfig { + type_name: Box::new(|object_name: &str| -> String { + format!("{object_name}NewOrderEnumInput") + }), + } + } +} + +/// The OrderByEnumeration is used for Entities Fields sorting +pub struct OrderEnumBuilder { + pub context: &'static BuilderContext, +} + +impl OrderEnumBuilder { + pub fn type_name(&self, object_name: &str) -> String { + self.context.order_enum.type_name.as_ref()(object_name) + } + + /// used to get the GraphQL enumeration config + pub fn enumeration(&self) -> Enum + where + T: EntityTrait, + ::Model: Sync, + { + let entity_object_builder = EntityObjectBuilder { + context: self.context, + }; + let object_name = entity_object_builder.type_name::(); + T::Column::iter().fold(Enum::new(self.type_name(&object_name)), |enu, column| { + enu.item(EnumItem::new( + entity_object_builder.column_name::(&column), + )) + }) + } +} \ No newline at end of file diff --git a/src/inputs/mod.rs b/src/inputs/mod.rs index 5b83c6c2..815164d7 100644 --- a/src/inputs/mod.rs +++ b/src/inputs/mod.rs @@ -22,3 +22,6 @@ pub use entity_input::*; pub mod active_enum_filter_input; pub use active_enum_filter_input::*; + +pub mod new_order_input; +pub use new_order_input::*; \ No newline at end of file diff --git a/src/inputs/new_order_input.rs b/src/inputs/new_order_input.rs new file mode 100644 index 00000000..48f2e1c9 --- /dev/null +++ b/src/inputs/new_order_input.rs @@ -0,0 +1,92 @@ +use async_graphql::dynamic::{InputObject, InputValue, TypeRef, ValueAccessor}; +use sea_orm::{EntityTrait, Iterable}; + +use crate::{BuilderContext, EntityObjectBuilder}; + +/// The configuration structure for OrderInputBuilder +pub struct NewOrderInputConfig { + /// used to format OrderInput object name + pub type_name: crate::SimpleNamingFn, +} + +impl std::default::Default for NewOrderInputConfig { + fn default() -> Self { + NewOrderInputConfig { + type_name: Box::new(|object_name: &str| -> String { + format!("{object_name}NewOrderInput") + }), + } + } +} + +/// This builder produces the OrderInput object of a SeaORM entity +pub struct NewOrderInputBuilder { + pub context: &'static BuilderContext, +} + +impl NewOrderInputBuilder { + /// used to get type name + pub fn type_name(&self, object_name: &str) -> String { + self.context.new_order_input.type_name.as_ref()(object_name) + } + + /// used to get the OrderInput object of a SeaORM entity + pub fn to_object(&self) -> InputObject + where + T: EntityTrait, + ::Model: Sync, + { + let entity_object_builder = EntityObjectBuilder { + context: self.context, + }; + + let object_name = entity_object_builder.type_name::(); + let name = self.type_name(&object_name); + + InputObject::new(name) + .field(InputValue::new( + "asc", + TypeRef::named(&(self.context.order_enum.type_name)(&object_name)), + )) + .field(InputValue::new( + "desc", + TypeRef::named(&(self.context.order_enum.type_name)(&object_name)), + )) + } + + pub fn parse_object( + &self, + value: Option>, + ) -> Vec<(T::Column, sea_orm::sea_query::Order)> + where + T: EntityTrait, + ::Model: Sync, + { + match value { + Some(value) => { + let mut data = Vec::new(); + let order = value.object().unwrap(); + let entity_object = EntityObjectBuilder { + context: self.context, + }; + for col in T::Column::iter() { + let column_name = entity_object.column_name::(&col); + dbg!(&column_name); + if let Some(order) = order.get("asc") { + if column_name == order.enum_name().unwrap() { + data.push((col, sea_orm::Order::Asc)); + } + } + + if let Some(order) = order.get("desc") { + if column_name == order.enum_name().unwrap() { + data.push((col, sea_orm::Order::Desc)); + } + } + } + data + } + None => Vec::new(), + } + } +} \ No newline at end of file diff --git a/src/query/entity_object_relation.rs b/src/query/entity_object_relation.rs index ca500be2..04395a30 100644 --- a/src/query/entity_object_relation.rs +++ b/src/query/entity_object_relation.rs @@ -10,8 +10,9 @@ use sea_orm::{EntityTrait, Iden, ModelTrait, RelationDef}; use crate::ConnectionObjectBuilder; use crate::{ apply_memory_pagination, get_filter_conditions, BuilderContext, EntityObjectBuilder, - FilterInputBuilder, GuardAction, HashableGroupKey, KeyComplex, OneToManyLoader, OneToOneLoader, - OrderInputBuilder, PaginationInputBuilder, + FilterInputBuilder, GuardAction, HashableGroupKey, KeyComplex, NewOrderInputBuilder, + OffsetInput, OneToManyLoader, OneToOneLoader, OrderInputBuilder, PageInput, PaginationInput, + PaginationInputBuilder, }; /// This builder produces a GraphQL field for an SeaORM entity relationship @@ -37,6 +38,7 @@ impl EntityObjectRelationBuilder { let connection_object_builder = ConnectionObjectBuilder { context }; let filter_input_builder = FilterInputBuilder { context }; let order_input_builder = OrderInputBuilder { context }; + let new_order_input_builder = NewOrderInputBuilder { context }; let object_name: String = entity_object_builder.type_name::(); #[cfg(feature = "offset-pagination")] @@ -100,7 +102,11 @@ impl EntityObjectRelationBuilder { let filters = ctx.args.get(&context.entity_query_field.filters); let filters = get_filter_conditions::(context, filters); let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let mut order_by = OrderInputBuilder { context }.parse_object::(order_by); + let order = ctx.args.get(&context.entity_query_field.order); + let order = NewOrderInputBuilder { context }.parse_object::(order); + order_by.extend(order); + let key = KeyComplex:: { key: vec![parent.get(from_col)], meta: HashableGroupKey:: { @@ -151,7 +157,10 @@ impl EntityObjectRelationBuilder { let filters = ctx.args.get(&context.entity_query_field.filters); let filters = get_filter_conditions::(context, filters); let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let mut order_by = OrderInputBuilder { context }.parse_object::(order_by); + let order = ctx.args.get(&context.entity_query_field.order); + let order = NewOrderInputBuilder { context }.parse_object::(order); + order_by.extend(order); let key = KeyComplex:: { key: vec![parent.get(from_col)], meta: HashableGroupKey:: { @@ -165,6 +174,43 @@ impl EntityObjectRelationBuilder { let values = loader.load_one(key).await?; let pagination = ctx.args.get(&context.entity_query_field.pagination); let pagination = PaginationInputBuilder { context }.parse_object(pagination); + let first = ctx.args.get("first"); + let pagination = match first { + Some(first_value) => match first_value.u64() { + Ok(first_num) => { + if let Some(offset) = pagination.offset { + PaginationInput { + offset: Some(OffsetInput { + offset: offset.offset, + limit: first_num, + }), + page: None, + cursor: None, + } + } else if let Some(page) = pagination.page { + PaginationInput { + offset: None, + page: Some(PageInput { + page: page.page, + limit: first_num, + }), + cursor: None, + } + } else { + PaginationInput { + offset: Some(OffsetInput { + offset: 0, + limit: first_num, + }), + page: None, + cursor: None, + } + } + } + _error => pagination, + }, + None => pagination, + }; let object = apply_memory_pagination::(values, pagination); @@ -173,21 +219,23 @@ impl EntityObjectRelationBuilder { }), }; - match relation_definition.is_owner { - false => field, - true => field - .argument(InputValue::new( - &context.entity_query_field.filters, - TypeRef::named(filter_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &context.entity_query_field.order_by, - TypeRef::named(order_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &context.entity_query_field.pagination, - TypeRef::named(&context.pagination_input.type_name), - )), - } + field + .argument(InputValue::new( + &context.entity_query_field.filters, + TypeRef::named(filter_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &context.entity_query_field.order_by, + TypeRef::named(order_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &self.context.entity_query_field.order, + TypeRef::named(new_order_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &context.entity_query_field.pagination, + TypeRef::named(&context.pagination_input.type_name), + )) + .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } -} \ No newline at end of file +} diff --git a/src/query/entity_object_via_relation.rs b/src/query/entity_object_via_relation.rs index 8bf1c280..9bfe4045 100644 --- a/src/query/entity_object_via_relation.rs +++ b/src/query/entity_object_via_relation.rs @@ -13,7 +13,8 @@ use crate::ConnectionObjectBuilder; use crate::{ apply_memory_pagination, apply_order, apply_pagination, get_filter_conditions, BuilderContext, EntityObjectBuilder, FilterInputBuilder, GuardAction, HashableGroupKey, KeyComplex, - OneToManyLoader, OneToOneLoader, OrderInputBuilder, PaginationInputBuilder, + NewOrderInputBuilder, OffsetInput, OneToManyLoader, OneToOneLoader, OrderInputBuilder, + PageInput, PaginationInput, PaginationInputBuilder, }; /// This builder produces a GraphQL field for an SeaORM entity related trait @@ -46,6 +47,7 @@ impl EntityObjectViaRelationBuilder { let connection_object_builder = ConnectionObjectBuilder { context }; let filter_input_builder = FilterInputBuilder { context }; let order_input_builder = OrderInputBuilder { context }; + let new_order_input_builder = NewOrderInputBuilder { context }; let object_name: String = entity_object_builder.type_name::(); #[cfg(feature = "offset-pagination")] let type_ref = TypeRef::named_list(&object_name); @@ -113,7 +115,10 @@ impl EntityObjectViaRelationBuilder { let filters = ctx.args.get(&context.entity_query_field.filters); let filters = get_filter_conditions::(context, filters); let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let mut order_by = OrderInputBuilder { context }.parse_object::(order_by); + let order = ctx.args.get(&context.entity_query_field.order); + let order = NewOrderInputBuilder { context }.parse_object::(order); + order_by.extend(order); let key = KeyComplex:: { key: vec![parent.get(from_col)], meta: HashableGroupKey:: { @@ -170,11 +175,51 @@ impl EntityObjectViaRelationBuilder { let filters = get_filter_conditions::(context, filters); let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let mut order_by = OrderInputBuilder { context }.parse_object::(order_by); + + let order = ctx.args.get(&context.entity_query_field.order); + let order = NewOrderInputBuilder { context }.parse_object::(order); + order_by.extend(order); let pagination = ctx.args.get(&context.entity_query_field.pagination); let pagination = PaginationInputBuilder { context }.parse_object(pagination); - + let first = ctx.args.get("first"); + let pagination = match first { + Some(first_value) => match first_value.u64() { + Ok(first_num) => { + if let Some(offset) = pagination.offset { + PaginationInput { + offset: Some(OffsetInput { + offset: offset.offset, + limit: first_num, + }), + page: None, + cursor: None, + } + } else if let Some(page) = pagination.page { + PaginationInput { + offset: None, + page: Some(PageInput { + page: page.page, + limit: first_num, + }), + cursor: None, + } + } else { + PaginationInput { + offset: Some(OffsetInput { + offset: 0, + limit: first_num, + }), + page: None, + cursor: None, + } + } + } + _error => pagination, + }, + None => pagination, + }; let db = ctx.data::()?; let object = if is_via_relation { @@ -206,21 +251,23 @@ impl EntityObjectViaRelationBuilder { }), }; - match via_relation_definition.is_owner { - false => field, - true => field - .argument(InputValue::new( - &context.entity_query_field.filters, - TypeRef::named(filter_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &context.entity_query_field.order_by, - TypeRef::named(order_input_builder.type_name(&object_name)), - )) - .argument(InputValue::new( - &context.entity_query_field.pagination, - TypeRef::named(&context.pagination_input.type_name), - )), - } + field + .argument(InputValue::new( + &context.entity_query_field.filters, + TypeRef::named(filter_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &context.entity_query_field.order_by, + TypeRef::named(order_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &self.context.entity_query_field.order, + TypeRef::named(new_order_input_builder.type_name(&object_name)), + )) + .argument(InputValue::new( + &context.entity_query_field.pagination, + TypeRef::named(&context.pagination_input.type_name), + )) + .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } -} \ No newline at end of file +} diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index 3466c124..32328ea0 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -13,7 +13,8 @@ use sea_orm::{ use crate::ConnectionObjectBuilder; use crate::{ apply_order, apply_pagination, filter_types_map, get_filter_conditions, BuilderContext, - EntityObjectBuilder, FilterInputBuilder, FilterTypesMapHelper, GuardAction, OrderInputBuilder, + EntityObjectBuilder, FilterInputBuilder, FilterTypesMapHelper, GuardAction, + NewOrderInputBuilder, OffsetInput, OrderInputBuilder, PageInput, PaginationInput, PaginationInputBuilder, TypesMapHelper, }; @@ -29,6 +30,7 @@ pub struct EntityQueryFieldConfig { pub order_by: String, /// name for 'pagination' field pub pagination: String, + pub order: String, } impl std::default::Default for EntityQueryFieldConfig { @@ -40,6 +42,7 @@ impl std::default::Default for EntityQueryFieldConfig { filters: "filter".into(), order_by: "orderBy".into(), pagination: "pagination".into(), + order: "order".into(), } } } @@ -82,6 +85,9 @@ impl EntityQueryFieldBuilder { let pagination_input_builder = PaginationInputBuilder { context: self.context, }; + let new_order_input_builder = NewOrderInputBuilder { + context: self.context, + }; let entity_object = EntityObjectBuilder { context: self.context, }; @@ -125,7 +131,11 @@ impl EntityQueryFieldBuilder { let filters = ctx.args.get(&context.entity_query_field.filters); let filters = get_filter_conditions::(context, filters); let order_by = ctx.args.get(&context.entity_query_field.order_by); - let order_by = OrderInputBuilder { context }.parse_object::(order_by); + let mut order_by = + OrderInputBuilder { context }.parse_object::(order_by); + let order = ctx.args.get(&context.entity_query_field.order); + let order = NewOrderInputBuilder { context }.parse_object::(order); + order_by.extend(order); let pagination = ctx.args.get(&context.entity_query_field.pagination); let pagination = PaginationInputBuilder { context }.parse_object(pagination); @@ -167,22 +177,43 @@ impl EntityQueryFieldBuilder { let db = ctx.data::()?; - #[cfg(feature = "offset-pagination")] let first = ctx.args.get("first"); - #[cfg(feature = "offset-pagination")] - let object = match first { + let pagination = match first { Some(first_value) => match first_value.u64() { Ok(first_num) => { - apply_stmt_cursor_by::(stmt) - .first(first_num) - .all(db) - .await? + if let Some(offset) = pagination.offset { + PaginationInput { + offset: Some(OffsetInput { + offset: offset.offset, + limit: first_num, + }), + page: None, + cursor: None, + } + } else if let Some(page) = pagination.page { + PaginationInput { + offset: None, + page: Some(PageInput { + page: page.page, + limit: first_num, + }), + cursor: None, + } + } else { + PaginationInput { + offset: Some(OffsetInput { + offset: 0, + limit: first_num, + }), + page: None, + cursor: None, + } + } } - _error => apply_pagination(db, stmt, pagination).await?, + _error => pagination, }, - None => apply_pagination::(db, stmt, pagination).await?, + None => pagination, }; - #[cfg(not(feature = "offset-pagination"))] let object = apply_pagination(db, stmt, pagination).await?; Ok(Some(resolver_fn(object))) @@ -202,6 +233,10 @@ impl EntityQueryFieldBuilder { &self.context.entity_query_field.order_by, TypeRef::named(order_input_builder.type_name(&object_name)), )) + .argument(InputValue::new( + &self.context.entity_query_field.order, + TypeRef::named(new_order_input_builder.type_name(&object_name)), + )) .argument(InputValue::new( &self.context.entity_query_field.pagination, TypeRef::named(pagination_input_builder.type_name()), @@ -238,4 +273,4 @@ where } else { panic!("seaography does not support cursors with size greater than 3") } -} +} \ No newline at end of file From e20c81f26b78b39fe566b7970b0bbcc4aa37834d Mon Sep 17 00:00:00 2001 From: Heikel Date: Sun, 10 Nov 2024 13:09:02 +0100 Subject: [PATCH 14/22] fix: put cascade on hold --- src/query/entity_query_field.rs | 93 ++++++++++++--------------------- 1 file changed, 32 insertions(+), 61 deletions(-) diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index 32328ea0..2d6c96a9 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -141,37 +141,38 @@ impl EntityQueryFieldBuilder { PaginationInputBuilder { context }.parse_object(pagination); let cascades = ctx.args.get("cascade"); let cascades = get_cascade_conditions(cascades); - let stmt = T::Relation::iter().fold(T::find(), |stmt, related_table| { - let related_table_name = related_table.def().to_tbl; - match related_table_name { - sea_orm::sea_query::TableRef::Table(iden) => { - if cascades.contains(&iden.to_string()) { - stmt.join(JoinType::InnerJoin, related_table.def()) - .distinct() - } else { - stmt - } - } - sea_orm::sea_query::TableRef::SchemaTable(_, iden) => { - if cascades.contains(&iden.to_string()) { - stmt.join(JoinType::InnerJoin, related_table.def()) - .distinct() - } else { - stmt - } - } - sea_orm::sea_query::TableRef::DatabaseSchemaTable(_, _, iden) => { - if cascades.contains(&iden.to_string()) { - stmt.join(JoinType::InnerJoin, related_table.def()) - .distinct() - } else { - stmt - } - } - _ => stmt, - } - }); - let stmt = stmt.filter(filters); + let stmt = T::find(); + // let stmt = T::Relation::iter().fold(T::find(), |stmt, related_table| { + // let related_table_name = related_table.def().to_tbl; + // match related_table_name { + // sea_orm::sea_query::TableRef::Table(iden) => { + // if cascades.contains(&iden.to_string()) { + // stmt.join(JoinType::InnerJoin, related_table.def()) + // .distinct() + // } else { + // stmt + // } + // } + // sea_orm::sea_query::TableRef::SchemaTable(_, iden) => { + // if cascades.contains(&iden.to_string()) { + // stmt.join(JoinType::InnerJoin, related_table.def()) + // .distinct() + // } else { + // stmt + // } + // } + // sea_orm::sea_query::TableRef::DatabaseSchemaTable(_, _, iden) => { + // if cascades.contains(&iden.to_string()) { + // stmt.join(JoinType::InnerJoin, related_table.def()) + // .distinct() + // } else { + // stmt + // } + // } + // _ => stmt, + // } + // }); + // let stmt = stmt.filter(filters); let stmt = apply_order(stmt, order_by); @@ -243,34 +244,4 @@ impl EntityQueryFieldBuilder { )) .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } -} -#[cfg(feature = "offset-pagination")] -fn apply_stmt_cursor_by( - stmt: sea_orm::entity::prelude::Select, -) -> sea_orm::Cursor> -where - T: EntityTrait, - ::Model: Sync, -{ - let size = T::PrimaryKey::iter().fold(0, |acc, _| acc + 1); - if size == 1 { - let column = T::PrimaryKey::iter() - .map(|variant| variant.into_column()) - .collect::>()[0]; - stmt.cursor_by(column) - } else if size == 2 { - let columns = T::PrimaryKey::iter() - .map(|variant| variant.into_column()) - .collect_tuple::<(T::Column, T::Column)>() - .unwrap(); - stmt.cursor_by(columns) - } else if size == 3 { - let columns = T::PrimaryKey::iter() - .map(|variant| variant.into_column()) - .collect_tuple::<(T::Column, T::Column, T::Column)>() - .unwrap(); - stmt.cursor_by(columns) - } else { - panic!("seaography does not support cursors with size greater than 3") - } } \ No newline at end of file From 242c712bf77b5210c8bed0dabff1d0e0287538b5 Mon Sep 17 00:00:00 2001 From: Heikel Date: Sun, 10 Nov 2024 14:27:06 +0100 Subject: [PATCH 15/22] fix: put cascade on hold --- src/inputs/new_order_input.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/inputs/new_order_input.rs b/src/inputs/new_order_input.rs index 48f2e1c9..84f5543b 100644 --- a/src/inputs/new_order_input.rs +++ b/src/inputs/new_order_input.rs @@ -71,7 +71,6 @@ impl NewOrderInputBuilder { }; for col in T::Column::iter() { let column_name = entity_object.column_name::(&col); - dbg!(&column_name); if let Some(order) = order.get("asc") { if column_name == order.enum_name().unwrap() { data.push((col, sea_orm::Order::Asc)); From e50c929878028461dbec1872665df82f283e1a2c Mon Sep 17 00:00:00 2001 From: Heikel Date: Sun, 10 Nov 2024 20:28:52 +0100 Subject: [PATCH 16/22] fix: remove non-alphanumeric from enums --- src/enumerations/active_enum.rs | 5 ++++- src/inputs/active_enum_filter_input.rs | 6 +++++- src/outputs/entity_object.rs | 11 ++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/enumerations/active_enum.rs b/src/enumerations/active_enum.rs index c83b58ae..6d732313 100644 --- a/src/enumerations/active_enum.rs +++ b/src/enumerations/active_enum.rs @@ -19,7 +19,10 @@ impl std::default::Default for ActiveEnumConfig { format!("{}Enum", name.to_upper_camel_case()) }), variant_name: Box::new(|_enum_name: &str, variant: &str| -> String { - variant.to_owned().replace("'", "") + variant + .chars() + .filter(|c| c.is_ascii_alphanumeric()) + .collect() }), } } diff --git a/src/inputs/active_enum_filter_input.rs b/src/inputs/active_enum_filter_input.rs index f631e127..ef73e5c0 100644 --- a/src/inputs/active_enum_filter_input.rs +++ b/src/inputs/active_enum_filter_input.rs @@ -88,7 +88,11 @@ where let extract_variant = move |input: &str| -> String { let variant = variants.iter().find(|variant| { - let variant = variant.to_string().replace("'", ""); + let variant = variant + .to_string() + .chars() + .filter(|c| c.is_alphanumeric()) + .collect::(); variant.eq(input) }); variant.unwrap().to_string() diff --git a/src/outputs/entity_object.rs b/src/outputs/entity_object.rs index 31725621..e6eaca62 100644 --- a/src/outputs/entity_object.rs +++ b/src/outputs/entity_object.rs @@ -208,9 +208,14 @@ fn sea_query_value_to_graphql_value( sea_orm::Value::BigUnsigned(value) => value.map(Value::from), sea_orm::Value::Float(value) => value.map(Value::from), sea_orm::Value::Double(value) => value.map(Value::from), - sea_orm::Value::String(value) if is_enum => { - value.map(|it| Value::from(it.replace("'", "").as_str())) - } + sea_orm::Value::String(value) if is_enum => value.map(|it| { + Value::from( + it.chars() + .filter(|c| c.is_alphanumeric()) + .collect::() + .as_str(), + ) + }), sea_orm::Value::String(value) => value.map(|it| Value::from(it.as_str())), sea_orm::Value::Char(value) => value.map(|it| Value::from(it.to_string())), From 5f9319a5bac96a8a984c6aac6974b782028f3370 Mon Sep 17 00:00:00 2001 From: Heikel Date: Tue, 12 Nov 2024 11:28:55 +0100 Subject: [PATCH 17/22] fix: filter --- src/query/entity_query_field.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index 2d6c96a9..f2f3ee7b 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -172,7 +172,7 @@ impl EntityQueryFieldBuilder { // _ => stmt, // } // }); - // let stmt = stmt.filter(filters); + let stmt = stmt.filter(filters); let stmt = apply_order(stmt, order_by); @@ -244,4 +244,4 @@ impl EntityQueryFieldBuilder { )) .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } -} \ No newline at end of file +} From 2c78a4618b41e27152d07aad5bff77c0026cb64e Mon Sep 17 00:00:00 2001 From: Heikel Date: Wed, 20 Nov 2024 11:08:09 +0100 Subject: [PATCH 18/22] chore: update to latest version of seaography --- Cargo.toml | 6 +----- src/enumerations/active_enum.rs | 14 ++++++-------- src/inputs/active_enum_filter_input.rs | 17 ++++++++++++----- src/outputs/entity_object.rs | 20 +++++++++++++------- src/query/entity_query_field.rs | 7 +------ 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b94cc03c..6934404e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,10 +34,6 @@ with-bigdecimal = ["sea-orm/with-bigdecimal", "async-graphql/bigdecimal"] with-postgres-array = ["sea-orm/postgres-array"] offset-pagination = [] # with-ipnetwork = ["sea-orm/with-ipnetwork"] -<<<<<<< HEAD -# with-mac_address = ["sea-orm/with-mac_address"] -======= # with-mac_address = ["sea-orm/with-mac_address"] field-snake-case = [] -field-camel-case = [] ->>>>>>> main \ No newline at end of file +field-camel-case = [] \ No newline at end of file diff --git a/src/enumerations/active_enum.rs b/src/enumerations/active_enum.rs index 085ae567..f69ad770 100644 --- a/src/enumerations/active_enum.rs +++ b/src/enumerations/active_enum.rs @@ -19,18 +19,16 @@ impl std::default::Default for ActiveEnumConfig { format!("{}Enum", name.to_upper_camel_case()) }), variant_name: Box::new(|_enum_name: &str, variant: &str| -> String { -<<<<<<< HEAD - variant - .chars() - .filter(|c| c.is_ascii_alphanumeric()) - .collect() -======= if cfg!(feature = "field-snake-case") { variant.to_snake_case() + } else if cfg!(feature = "offset-pagination") { + variant + .chars() + .filter(|c| c.is_ascii_alphanumeric()) + .collect() } else { variant.to_upper_camel_case().to_ascii_uppercase() } ->>>>>>> main }), } } @@ -71,4 +69,4 @@ impl ActiveEnumBuilder { enumeration.item(self.variant_name(&enum_name, &variant)) }) } -} +} \ No newline at end of file diff --git a/src/inputs/active_enum_filter_input.rs b/src/inputs/active_enum_filter_input.rs index d109915f..7ab32944 100644 --- a/src/inputs/active_enum_filter_input.rs +++ b/src/inputs/active_enum_filter_input.rs @@ -88,11 +88,18 @@ where let extract_variant = move |input: &str| -> String { let variant = variants.iter().find(|variant| { - let variant = variant - .to_string() - .chars() - .filter(|c| c.is_alphanumeric()) - .collect::(); + let variant = variant.to_string(); + let variant = if cfg!(feature = "field-snake-case") { + variant.to_snake_case() + } else if cfg!(feature = "offset-pagination") { + variant + .chars() + .filter(|c| c.is_alphanumeric()) + .collect::() + } else { + variant.to_upper_camel_case().to_ascii_uppercase() + }; + variant.eq(input) }); variant.unwrap().to_string() diff --git a/src/outputs/entity_object.rs b/src/outputs/entity_object.rs index bbc1deef..563ac941 100644 --- a/src/outputs/entity_object.rs +++ b/src/outputs/entity_object.rs @@ -213,12 +213,18 @@ fn sea_query_value_to_graphql_value( sea_orm::Value::Float(value) => value.map(Value::from), sea_orm::Value::Double(value) => value.map(Value::from), sea_orm::Value::String(value) if is_enum => value.map(|it| { - Value::from( - it.chars() - .filter(|c| c.is_alphanumeric()) - .collect::() - .as_str(), - ) + if cfg!(feature = "field-snake-case") { + Value::from(it.as_str().to_snake_case()) + } else if cfg!(feature = "offset-pagination") { + Value::from( + it.chars() + .filter(|c| c.is_alphanumeric()) + .collect::() + .as_str(), + ) + } else { + Value::from(it.as_str().to_upper_camel_case().to_ascii_uppercase()) + } }), sea_orm::Value::String(value) => value.map(|it| Value::from(it.as_str())), sea_orm::Value::Char(value) => value.map(|it| Value::from(it.to_string())), @@ -314,4 +320,4 @@ fn sea_query_value_to_graphql_value( #[allow(unreachable_patterns)] _ => panic!("Cannot convert SeaORM value"), } -} \ No newline at end of file +} diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index f5af8519..91779214 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -2,16 +2,11 @@ use async_graphql::{ dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef}, Error, }; +use heck::{ToLowerCamelCase, ToSnakeCase}; use itertools::Itertools; use sea_orm::{ ColumnTrait, DatabaseConnection, EntityTrait, Iterable, JoinType, PrimaryKeyToColumn, QueryFilter, QuerySelect, RelationTrait, -use heck::{ToLowerCamelCase, ToSnakeCase}; - -use crate::{ - apply_order, apply_pagination, get_filter_conditions, BuilderContext, ConnectionObjectBuilder, - EntityObjectBuilder, FilterInputBuilder, GuardAction, OrderInputBuilder, - PaginationInputBuilder, }; #[cfg(not(feature = "offset-pagination"))] From 5212e47b4c06322ba56413de97c120bbb17db9d3 Mon Sep 17 00:00:00 2001 From: Heikel Date: Mon, 16 Dec 2024 11:43:10 +0100 Subject: [PATCH 19/22] fix: merge main --- src/builder.rs | 27 ++++- src/builder_context.rs | 16 +-- src/inputs/cascade_input.rs | 132 ++++++++++++++++++++++++ src/inputs/mod.rs | 6 +- src/query/cascading.rs | 15 +-- src/query/entity_object_relation.rs | 19 +++- src/query/entity_object_via_relation.rs | 24 ++++- src/query/entity_query_field.rs | 92 +++-------------- src/query/first.rs | 41 ++++++++ src/query/mod.rs | 7 +- 10 files changed, 273 insertions(+), 106 deletions(-) create mode 100644 src/inputs/cascade_input.rs create mode 100644 src/query/first.rs diff --git a/src/builder.rs b/src/builder.rs index dbd07c1d..f0979766 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,12 +1,16 @@ use async_graphql::{ + context, dataloader::DataLoader, - dynamic::{Enum, Field, FieldFuture, InputObject, Object, Schema, SchemaBuilder, TypeRef}, + dynamic::{ + Enum, Field, FieldFuture, InputObject, Object, Schema, SchemaBuilder, TypeRef, + ValueAccessor, + }, }; -use sea_orm::{ActiveEnum, ActiveModelTrait, EntityTrait, IntoActiveModel}; +use sea_orm::{ActiveEnum, ActiveModelTrait, EntityTrait, IntoActiveModel, RelationDef}; use crate::{ order_enum, ActiveEnumBuilder, ActiveEnumFilterInputBuilder, BuilderContext, - ConnectionObjectBuilder, CursorInputBuilder, EdgeObjectBuilder, + CascadeInputBuilder, ConnectionObjectBuilder, CursorInputBuilder, EdgeObjectBuilder, EntityCreateBatchMutationBuilder, EntityCreateOneMutationBuilder, EntityDeleteMutationBuilder, EntityGetFieldBuilder, EntityInputBuilder, EntityObjectBuilder, EntityQueryFieldBuilder, EntityUpdateMutationBuilder, FilterInputBuilder, FilterTypesMapHelper, NewOrderInputBuilder, @@ -75,6 +79,7 @@ impl Builder { where T: EntityTrait, ::Model: Sync, + ::Relation: CascadeBuilder, { let entity_object_builder = EntityObjectBuilder { context: self.context, @@ -107,6 +112,11 @@ impl Builder { }; let filter = filter_input_builder.to_object::(); + let cascade_input_builder = CascadeInputBuilder { + context: self.context, + }; + let cascade = cascade_input_builder.to_object::(); + let order_input_builder = OrderInputBuilder { context: self.context, }; @@ -116,7 +126,7 @@ impl Builder { context: self.context, }; let new_order = new_order_input_builder.to_object::(); - self.inputs.extend(vec![filter, order, new_order]); + self.inputs.extend(vec![filter, order, new_order, cascade]); let order_enum_builder = OrderEnumBuilder { context: self.context, @@ -343,6 +353,14 @@ pub trait RelationBuilder { ) -> async_graphql::dynamic::Field; } +pub trait CascadeBuilder { + fn get_join( + &self, + context: &'static crate::BuilderContext, + filters: Option, + ) -> RelationDef; +} + #[macro_export] macro_rules! register_entity { ($builder:expr, $module_path:ident) => { @@ -380,7 +398,6 @@ macro_rules! register_entities_without_relation { }; } - #[macro_export] macro_rules! register_entity_modules { ([$($module_paths:ident),+ $(,)?]) => { diff --git a/src/builder_context.rs b/src/builder_context.rs index 66d12a28..72f8ee68 100644 --- a/src/builder_context.rs +++ b/src/builder_context.rs @@ -1,10 +1,11 @@ use crate::{ - ActiveEnumConfig, ActiveEnumFilterInputConfig, ConnectionObjectConfig, CursorInputConfig, - EdgeObjectConfig, EntityCreateBatchMutationConfig, EntityCreateOneMutationConfig, - EntityDeleteMutationConfig, EntityGetFieldConfig, EntityInputConfig, EntityObjectConfig, - EntityQueryFieldConfig, EntityUpdateMutationConfig, FilterInputConfig, NewOrderInputConfig, - OffsetInputConfig, OrderByEnumConfig, OrderEnumConfig, OrderInputConfig, PageInfoObjectConfig, - PageInputConfig, PaginationInfoObjectConfig, PaginationInputConfig, + ActiveEnumConfig, ActiveEnumFilterInputConfig, CascadeInputConfig, ConnectionObjectConfig, + CursorInputConfig, EdgeObjectConfig, EntityCreateBatchMutationConfig, + EntityCreateOneMutationConfig, EntityDeleteMutationConfig, EntityGetFieldConfig, + EntityInputConfig, EntityObjectConfig, EntityQueryFieldConfig, EntityUpdateMutationConfig, + FilterInputConfig, NewOrderInputConfig, OffsetInputConfig, OrderByEnumConfig, OrderEnumConfig, + OrderInputConfig, PageInfoObjectConfig, PageInputConfig, PaginationInfoObjectConfig, + PaginationInputConfig, }; pub mod guards; @@ -35,6 +36,7 @@ pub struct BuilderContext { pub new_order_input: NewOrderInputConfig, pub filter_input: FilterInputConfig, + pub cascade_input: CascadeInputConfig, pub active_enum_filter_input: ActiveEnumFilterInputConfig, pub page_info_object: PageInfoObjectConfig, @@ -57,4 +59,4 @@ pub struct BuilderContext { pub filter_types: FilterTypesMapConfig, // is_skipped function // naming function -} +} \ No newline at end of file diff --git a/src/inputs/cascade_input.rs b/src/inputs/cascade_input.rs new file mode 100644 index 00000000..83325778 --- /dev/null +++ b/src/inputs/cascade_input.rs @@ -0,0 +1,132 @@ +use async_graphql::dynamic::{InputObject, InputValue, ObjectAccessor, TypeRef, ValueAccessor}; +use sea_orm::{EntityTrait, Iterable, JoinType, QuerySelect, RelationTrait}; + +use crate::{BuilderContext, CascadeBuilder, EntityObjectBuilder, FilterInputBuilder}; +use heck::ToUpperCamelCase; + +/// The configuration structure for FilterInputBuilder +pub struct CascadeInputConfig { + /// the filter input type name formatter function + pub type_name: crate::SimpleNamingFn, +} + +impl std::default::Default for CascadeInputConfig { + fn default() -> Self { + CascadeInputConfig { + type_name: Box::new(|object_name: &str| -> String { + format!("{object_name}CascadeInput") + }), + } + } +} + +/// This builder is used to produce the filter input object of a SeaORM entity +pub struct CascadeInputBuilder { + pub context: &'static BuilderContext, +} + +impl CascadeInputBuilder { + /// used to get the filter input object name + /// object_name is the name of the SeaORM Entity GraphQL object + pub fn type_name(&self, object_name: &str) -> String { + self.context.cascade_input.type_name.as_ref()(object_name) + } + + /// used to produce the filter input object of a SeaORM entity + pub fn to_object(&self) -> InputObject + where + T: EntityTrait, + ::Model: Sync, + { + let entity_object_builder = EntityObjectBuilder { + context: self.context, + }; + let filter_input_builder = FilterInputBuilder { + context: self.context, + }; + + let entity_name = entity_object_builder.type_name::(); + let cascade_name = self.type_name(&entity_name); + + let object = + T::Relation::iter().fold(InputObject::new(&cascade_name), |object, related_table| { + let related_table_name = related_table.def().to_tbl; + let fk_name = related_table.def().fk_name; + let relation_name = if let Some(fk_name) = fk_name { + fk_name + } else { + "".to_string() + }; + if relation_name.is_empty() { + return object; + } + dbg!(&entity_name); + match related_table_name { + sea_orm::sea_query::TableRef::Table(iden) => { + let name = iden.to_string(); + object.field(InputValue::new( + relation_name, + TypeRef::named( + filter_input_builder.type_name(&name.to_upper_camel_case()), + ), + )) + } + sea_orm::sea_query::TableRef::SchemaTable(_, iden) => { + let name = iden.to_string(); + object.field(InputValue::new( + relation_name, + TypeRef::named( + filter_input_builder.type_name(&name.to_upper_camel_case()), + ), + )) + } + sea_orm::sea_query::TableRef::DatabaseSchemaTable(_, _, iden) => { + let name = iden.to_string().to_upper_camel_case(); + object.field(InputValue::new( + relation_name, + TypeRef::named( + filter_input_builder.type_name(&name.to_upper_camel_case()), + ), + )) + } + _ => object, + } + }); + + object + } + pub fn parse_object( + &self, + context: &'static BuilderContext, + cascades: Option>, + ) -> sea_orm::Select + where + T: EntityTrait, + ::Model: Sync, + ::Relation: CascadeBuilder, + { + if let Some(cascades) = cascades { + T::Relation::iter().fold(T::find(), |stmt, relation_definition| { + let context: &'static BuilderContext = context; + let fk_name = relation_definition.def().fk_name; + let relation_name = if let Some(fk_name) = fk_name { + fk_name + } else { + "".to_string() + }; + + if let Some(cascade) = cascades.get(&relation_name) { + stmt.join( + JoinType::InnerJoin, + CascadeBuilder::get_join(&relation_definition, &context, Some(cascade)), + ) + .distinct() + } else { + stmt + } + }) + } else { + T::find() + } + } +} diff --git a/src/inputs/mod.rs b/src/inputs/mod.rs index 815164d7..a0087761 100644 --- a/src/inputs/mod.rs +++ b/src/inputs/mod.rs @@ -24,4 +24,8 @@ pub mod active_enum_filter_input; pub use active_enum_filter_input::*; pub mod new_order_input; -pub use new_order_input::*; \ No newline at end of file +pub use new_order_input::*; + + +pub mod cascade_input; +pub use cascade_input::*; \ No newline at end of file diff --git a/src/query/cascading.rs b/src/query/cascading.rs index e5dd2e6f..d698abcb 100644 --- a/src/query/cascading.rs +++ b/src/query/cascading.rs @@ -1,14 +1,9 @@ -use async_graphql::dynamic::ValueAccessor; +use async_graphql::dynamic::{ObjectAccessor, ValueAccessor}; -pub fn get_cascade_conditions(cascades: Option) -> Vec { +pub fn get_cascade_conditions(cascades: Option) -> Option { if let Some(cascades) = cascades { - cascades - .list() - .unwrap() - .iter() - .map(|field| field.string().unwrap().to_string()) - .collect::>() + Some(cascades.object().unwrap()) } else { - Vec::new() + None } -} \ No newline at end of file +} diff --git a/src/query/entity_object_relation.rs b/src/query/entity_object_relation.rs index 620be195..c081c461 100644 --- a/src/query/entity_object_relation.rs +++ b/src/query/entity_object_relation.rs @@ -1,6 +1,6 @@ use async_graphql::{ dataloader::DataLoader, - dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef}, + dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef, ValueAccessor}, Error, }; use heck::{ToLowerCamelCase, ToSnakeCase}; @@ -243,4 +243,21 @@ impl EntityObjectRelationBuilder { )) .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } + + pub fn joiin( + &self, + relation_definition: RelationDef, + filter: Option, + ) -> RelationDef + where + T: EntityTrait, + ::Model: Sync, + <::Column as std::str::FromStr>::Err: core::fmt::Debug, + R: EntityTrait, + ::Model: Sync, + <::Column as std::str::FromStr>::Err: core::fmt::Debug, + { + let filters = get_filter_conditions::(self.context, filter); + relation_definition.on_condition(move |_left, _right| filters.to_owned()) + } } diff --git a/src/query/entity_object_via_relation.rs b/src/query/entity_object_via_relation.rs index da2cbfaf..60fe62b6 100644 --- a/src/query/entity_object_via_relation.rs +++ b/src/query/entity_object_via_relation.rs @@ -1,11 +1,12 @@ use async_graphql::{ dataloader::DataLoader, - dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef}, + dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef, ValueAccessor}, Error, }; use heck::{ToLowerCamelCase, ToSnakeCase}; use sea_orm::{ - ColumnTrait, Condition, DatabaseConnection, EntityTrait, Iden, ModelTrait, QueryFilter, Related, + ColumnTrait, Condition, DatabaseConnection, EntityTrait, Iden, ModelTrait, QueryFilter, + Related, RelationDef, }; #[cfg(not(feature = "offset-pagination"))] @@ -275,4 +276,21 @@ impl EntityObjectViaRelationBuilder { )) .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } -} + + pub fn joiin( + &self, + relation_definition: RelationDef, + filter: Option, + ) -> RelationDef + where + T: EntityTrait, + ::Model: Sync, + <::Column as std::str::FromStr>::Err: core::fmt::Debug, + R: EntityTrait, + ::Model: Sync, + <::Column as std::str::FromStr>::Err: core::fmt::Debug, + { + let filters = get_filter_conditions::(self.context, filter); + relation_definition.on_condition(move |_left, _right| filters.to_owned()) + } +} \ No newline at end of file diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index 91779214..0f9db49f 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -5,17 +5,15 @@ use async_graphql::{ use heck::{ToLowerCamelCase, ToSnakeCase}; use itertools::Itertools; use sea_orm::{ - ColumnTrait, DatabaseConnection, EntityTrait, Iterable, JoinType, PrimaryKeyToColumn, - QueryFilter, QuerySelect, RelationTrait, + DatabaseConnection, EntityTrait, Iterable, JoinType, QueryFilter, QuerySelect, RelationTrait, }; #[cfg(not(feature = "offset-pagination"))] use crate::ConnectionObjectBuilder; use crate::{ - apply_order, apply_pagination, filter_types_map, get_filter_conditions, BuilderContext, - EntityObjectBuilder, FilterInputBuilder, FilterTypesMapHelper, GuardAction, - NewOrderInputBuilder, OffsetInput, OrderInputBuilder, PageInput, PaginationInput, - PaginationInputBuilder, TypesMapHelper, + apply_order, apply_pagination, get_filter_conditions, get_first, BuilderContext, + CascadeBuilder, CascadeInputBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, + NewOrderInputBuilder, OrderInputBuilder, PaginationInputBuilder, }; use super::get_cascade_conditions; @@ -82,6 +80,7 @@ impl EntityQueryFieldBuilder { where T: EntityTrait, ::Model: Sync, + ::Relation: crate::CascadeBuilder, { #[cfg(not(feature = "offset-pagination"))] let connection_object_builder = ConnectionObjectBuilder { @@ -90,6 +89,9 @@ impl EntityQueryFieldBuilder { let filter_input_builder = FilterInputBuilder { context: self.context, }; + let cascade_input_builder = CascadeInputBuilder { + context: self.context, + }; let order_input_builder = OrderInputBuilder { context: self.context, }; @@ -148,84 +150,20 @@ impl EntityQueryFieldBuilder { let order = NewOrderInputBuilder { context }.parse_object::(order); order_by.extend(order); let pagination = ctx.args.get(&context.entity_query_field.pagination); + let first = ctx.args.get("first"); let pagination = PaginationInputBuilder { context }.parse_object(pagination); + let pagination = get_first(first, pagination); let cascades = ctx.args.get("cascade"); let cascades = get_cascade_conditions(cascades); - let stmt = T::find(); - // let stmt = T::Relation::iter().fold(T::find(), |stmt, related_table| { - // let related_table_name = related_table.def().to_tbl; - // match related_table_name { - // sea_orm::sea_query::TableRef::Table(iden) => { - // if cascades.contains(&iden.to_string()) { - // stmt.join(JoinType::InnerJoin, related_table.def()) - // .distinct() - // } else { - // stmt - // } - // } - // sea_orm::sea_query::TableRef::SchemaTable(_, iden) => { - // if cascades.contains(&iden.to_string()) { - // stmt.join(JoinType::InnerJoin, related_table.def()) - // .distinct() - // } else { - // stmt - // } - // } - // sea_orm::sea_query::TableRef::DatabaseSchemaTable(_, _, iden) => { - // if cascades.contains(&iden.to_string()) { - // stmt.join(JoinType::InnerJoin, related_table.def()) - // .distinct() - // } else { - // stmt - // } - // } - // _ => stmt, - // } - // }); - let stmt = stmt.filter(filters); + let stmt = + CascadeInputBuilder { context }.parse_object::(context, cascades); + let stmt = stmt.filter(filters); let stmt = apply_order(stmt, order_by); let db = ctx.data::()?; - let first = ctx.args.get("first"); - let pagination = match first { - Some(first_value) => match first_value.u64() { - Ok(first_num) => { - if let Some(offset) = pagination.offset { - PaginationInput { - offset: Some(OffsetInput { - offset: offset.offset, - limit: first_num, - }), - page: None, - cursor: None, - } - } else if let Some(page) = pagination.page { - PaginationInput { - offset: None, - page: Some(PageInput { - page: page.page, - limit: first_num, - }), - cursor: None, - } - } else { - PaginationInput { - offset: Some(OffsetInput { - offset: 0, - limit: first_num, - }), - page: None, - cursor: None, - } - } - } - _error => pagination, - }, - None => pagination, - }; let object = apply_pagination(db, stmt, pagination).await?; Ok(Some(resolver_fn(object))) @@ -235,7 +173,7 @@ impl EntityQueryFieldBuilder { }) .argument(InputValue::new( "cascade", - TypeRef::named_list(TypeRef::STRING), + TypeRef::named(cascade_input_builder.type_name(&object_name)), )) .argument(InputValue::new( &self.context.entity_query_field.filters, @@ -255,4 +193,4 @@ impl EntityQueryFieldBuilder { )) .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } -} \ No newline at end of file +} diff --git a/src/query/first.rs b/src/query/first.rs new file mode 100644 index 00000000..51cde060 --- /dev/null +++ b/src/query/first.rs @@ -0,0 +1,41 @@ +use crate::{OffsetInput, PageInput, PaginationInput}; +use async_graphql::dynamic::ValueAccessor; + +pub fn get_first(first: Option, pagination: PaginationInput) -> PaginationInput { + match first { + Some(first_value) => match first_value.u64() { + Ok(first_num) => { + if let Some(offset) = pagination.offset { + PaginationInput { + offset: Some(OffsetInput { + offset: offset.offset, + limit: first_num, + }), + page: None, + cursor: None, + } + } else if let Some(page) = pagination.page { + PaginationInput { + offset: None, + page: Some(PageInput { + page: page.page, + limit: first_num, + }), + cursor: None, + } + } else { + PaginationInput { + offset: Some(OffsetInput { + offset: 0, + limit: first_num, + }), + page: None, + cursor: None, + } + } + } + _error => pagination, + }, + None => pagination, + } +} \ No newline at end of file diff --git a/src/query/mod.rs b/src/query/mod.rs index 36d8dc22..4dc77f85 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -22,6 +22,9 @@ pub use entity_object_via_relation::*; pub mod cascading; pub use cascading::*; - pub mod entity_get_field; -pub use entity_get_field::*; \ No newline at end of file +pub use entity_get_field::*; + + +pub mod first; +pub use first::*; \ No newline at end of file From 249902f76063a33999460d21b40e9513a760d94f Mon Sep 17 00:00:00 2001 From: Heikel Date: Fri, 10 Jan 2025 00:10:03 +0100 Subject: [PATCH 20/22] feat: add interface --- Cargo.toml | 13 ++++++++++--- src/builder.rs | 31 +++++++++++++++++++++++-------- src/outputs/entity_object.rs | 2 +- src/query/entity_query_field.rs | 8 ++++---- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6934404e..7b6fa37d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,15 @@ keywords = ["async", "graphql", "mysql", "postgres", "sqlite"] categories = ["database"] [dependencies] -async-graphql = { version = "7.0", features = ["decimal", "chrono", "dataloader", "dynamic-schema"] } -sea-orm = { version = "~1.1.0", default-features = false, features = ["seaography"] } +async-graphql = { version = "7.0", features = [ + "decimal", + "chrono", + "dataloader", + "dynamic-schema", +] } +sea-orm = { version = "1.1.*", default-features = false, features = [ + "seaography", +] } itertools = { version = "0.12.0" } heck = { version = "0.4.1" } thiserror = { version = "1.0.44" } @@ -36,4 +43,4 @@ offset-pagination = [] # with-ipnetwork = ["sea-orm/with-ipnetwork"] # with-mac_address = ["sea-orm/with-mac_address"] field-snake-case = [] -field-camel-case = [] \ No newline at end of file +field-camel-case = [] diff --git a/src/builder.rs b/src/builder.rs index f0979766..e0da13c3 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -2,14 +2,14 @@ use async_graphql::{ context, dataloader::DataLoader, dynamic::{ - Enum, Field, FieldFuture, InputObject, Object, Schema, SchemaBuilder, TypeRef, + Enum, Field, FieldFuture, InputObject, Interface, Object, Schema, SchemaBuilder, TypeRef, ValueAccessor, }, }; use sea_orm::{ActiveEnum, ActiveModelTrait, EntityTrait, IntoActiveModel, RelationDef}; use crate::{ - order_enum, ActiveEnumBuilder, ActiveEnumFilterInputBuilder, BuilderContext, + entity_object, order_enum, ActiveEnumBuilder, ActiveEnumFilterInputBuilder, BuilderContext, CascadeInputBuilder, ConnectionObjectBuilder, CursorInputBuilder, EdgeObjectBuilder, EntityCreateBatchMutationBuilder, EntityCreateOneMutationBuilder, EntityDeleteMutationBuilder, EntityGetFieldBuilder, EntityInputBuilder, EntityObjectBuilder, EntityQueryFieldBuilder, @@ -30,6 +30,9 @@ pub struct Builder { /// holds all output object types pub outputs: Vec, + // holds all interfaces object types + pub interfaces: Vec, + /// holds all input object types pub inputs: Vec, @@ -65,6 +68,7 @@ impl Builder { mutation, schema, outputs: Vec::new(), + interfaces: Vec::new(), inputs: Vec::new(), enumerations: Vec::new(), queries: Vec::new(), @@ -75,11 +79,10 @@ impl Builder { } /// used to register a new entity to the Builder context - pub fn register_entity(&mut self, relations: Vec) + pub fn register_entity(&mut self, relations: Vec, interfaces: Vec<&str>) where T: EntityTrait, ::Model: Sync, - ::Relation: CascadeBuilder, { let entity_object_builder = EntityObjectBuilder { context: self.context, @@ -88,6 +91,11 @@ impl Builder { entity_object_builder.to_object::(), |entity_object, field| entity_object.field(field), ); + let entity_object = interfaces + .into_iter() + .fold(entity_object, |entity_object, interface| { + entity_object.implement(interface) + }); if cfg!(feature = "offset-pagination") { self.outputs.extend(vec![entity_object]); @@ -271,6 +279,12 @@ impl Builder { .into_iter() .fold(mutation, |mutation, field| mutation.field(field)); + // register interfaces to schema + let schema = self + .interfaces + .into_iter() + .fold(schema, |schema, interface| schema.register(interface)); + // register entities to schema let schema = self .outputs @@ -363,11 +377,12 @@ pub trait CascadeBuilder { #[macro_export] macro_rules! register_entity { - ($builder:expr, $module_path:ident) => { + ($builder:expr, $module_path:ident,$interfaces:expr) => { $builder.register_entity::<$module_path::Entity>( <$module_path::RelatedEntity as sea_orm::Iterable>::iter() .map(|rel| seaography::RelationBuilder::get_relation(&rel, $builder.context)) .collect(), + $interfaces, ); $builder = $builder.register_entity_dataloader_one_to_one($module_path::Entity, tokio::spawn); @@ -379,8 +394,8 @@ macro_rules! register_entity { #[macro_export] macro_rules! register_entities { - ($builder:expr, [$($module_paths:ident),+ $(,)?]) => { - $(seaography::register_entity!($builder, $module_paths);)* + ($builder:expr, [$(($module_paths:ident,$interfaces:expr)),+ $(,)?]) => { + $(seaography::register_entity!($builder, $module_paths,$interfaces);)* }; } @@ -411,4 +426,4 @@ macro_rules! register_entity_modules { builder } }; -} \ No newline at end of file +} diff --git a/src/outputs/entity_object.rs b/src/outputs/entity_object.rs index 563ac941..662e8d18 100644 --- a/src/outputs/entity_object.rs +++ b/src/outputs/entity_object.rs @@ -320,4 +320,4 @@ fn sea_query_value_to_graphql_value( #[allow(unreachable_patterns)] _ => panic!("Cannot convert SeaORM value"), } -} +} \ No newline at end of file diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index 0f9db49f..8c39b399 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -80,7 +80,6 @@ impl EntityQueryFieldBuilder { where T: EntityTrait, ::Model: Sync, - ::Relation: crate::CascadeBuilder, { #[cfg(not(feature = "offset-pagination"))] let connection_object_builder = ConnectionObjectBuilder { @@ -157,8 +156,9 @@ impl EntityQueryFieldBuilder { let cascades = ctx.args.get("cascade"); let cascades = get_cascade_conditions(cascades); - let stmt = - CascadeInputBuilder { context }.parse_object::(context, cascades); + //let stmt = + // CascadeInputBuilder { context }.parse_object::(context, cascades); + let stmt = T::find(); let stmt = stmt.filter(filters); let stmt = apply_order(stmt, order_by); @@ -193,4 +193,4 @@ impl EntityQueryFieldBuilder { )) .argument(InputValue::new("first", TypeRef::named(TypeRef::INT))) } -} +} \ No newline at end of file From 32119a0dafcf0d25c1455d063df63804c2d05537 Mon Sep 17 00:00:00 2001 From: Heikel Date: Mon, 13 Jan 2025 23:08:19 +0100 Subject: [PATCH 21/22] fix: sea-orm version --- Cargo.toml | 2 +- README.md | 214 ++++++++++++++++---------------- src/builder.rs | 5 +- src/inputs/cascade_input.rs | 4 +- src/inputs/entity_input.rs | 7 +- src/query/entity_query_field.rs | 9 +- 6 files changed, 121 insertions(+), 120 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7b6fa37d..11e21f86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ async-graphql = { version = "7.0", features = [ "dataloader", "dynamic-schema", ] } -sea-orm = { version = "1.1.*", default-features = false, features = [ +sea-orm = { version = "1.0.*", default-features = false, features = [ "seaography", ] } itertools = { version = "0.12.0" } diff --git a/README.md b/README.md index 674838d1..0ba4c997 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ 🧭 A GraphQL framework and code generator for SeaORM

- [![crate](https://img.shields.io/crates/v/seaography.svg)](https://crates.io/crates/seaography) - [![docs](https://docs.rs/seaography/badge.svg)](https://docs.rs/seaography) - [![build status](https://github.com/SeaQL/seaography/actions/workflows/tests.yaml/badge.svg)](https://github.com/SeaQL/seaography/actions/workflows/tests.yaml) +[![crate](https://img.shields.io/crates/v/seaography.svg)](https://crates.io/crates/seaography) +[![docs](https://docs.rs/seaography/badge.svg)](https://docs.rs/seaography) +[![build status](https://github.com/SeaQL/seaography/actions/workflows/tests.yaml/badge.svg)](https://github.com/SeaQL/seaography/actions/workflows/tests.yaml) @@ -18,27 +18,27 @@ ## Benefits -* Quick and easy to get started -* Generates readable code -* Extensible project structure -* Based on popular async libraries: [async-graphql](https://github.com/async-graphql/async-graphql) and [SeaORM](https://github.com/SeaQL/sea-orm) +- Quick and easy to get started +- Generates readable code +- Extensible project structure +- Based on popular async libraries: [async-graphql](https://github.com/async-graphql/async-graphql) and [SeaORM](https://github.com/SeaQL/sea-orm) ## Features -* Relational query (1-to-1, 1-to-N) -* Pagination for queries and relations (1-N) -* Filtering with operators (e.g. gt, lt, eq) -* Order by any column -* Guard fields, queries or relations -* Rename fields -* Mutations (create, update, delete) +- Relational query (1-to-1, 1-to-N) +- Pagination for queries and relations (1-N) +- Filtering with operators (e.g. gt, lt, eq) +- Order by any column +- Guard fields, queries or relations +- Rename fields +- Mutations (create, update, delete) (Right now there is no mutation, but it's on our plan!) ## SeaORM Version Compatibility -| Seaography | SeaORM | -|----------------------------------------------------------|-------------------------------------------------------| +| Seaography | SeaORM | +| -------------------------------------------------------- | ----------------------------------------------------- | | [1.1-rc](https://crates.io/crates/seaography/1.1.0-rc.1) | [1.1-rc](https://crates.io/crates/sea-orm/1.1.0-rc.1) | | [1.0](https://crates.io/crates/seaography/1.0.0) | [1.0](https://crates.io/crates/sea-orm/1.0.0) | | [0.12](https://crates.io/crates/seaography/0.12.0) | [0.12](https://crates.io/crates/sea-orm/0.12.14) | @@ -70,19 +70,22 @@ Go to http://localhost:8000/ and try out the following queries: ```graphql { - film(pagination: { page: { limit: 10, page: 0 } }, orderBy: { title: ASC }) { - nodes { - title - description - releaseYear - actor { + film( + pagination: { page: { limit: 10, page: 0 } } + orderBy: { title: ASC } + ) { nodes { - firstName - lastName + title + description + releaseYear + actor { + nodes { + firstName + lastName + } + } } - } } - } } ``` @@ -90,19 +93,19 @@ Go to http://localhost:8000/ and try out the following queries: ```graphql { - store(filters: { storeId: { eq: 1 } }) { - nodes { - storeId - address { - address - address2 - } - staff { - firstName - lastName - } + store(filters: { storeId: { eq: 1 } }) { + nodes { + storeId + address { + address + address2 + } + staff { + firstName + lastName + } + } } - } } ``` @@ -110,20 +113,20 @@ Go to http://localhost:8000/ and try out the following queries: ```graphql { - customer( - filters: { active: { eq: 0 } } - pagination: { page: { page: 2, limit: 3 } } - ) { - nodes { - customerId - lastName - email - } - paginationInfo { - pages - current + customer( + filters: { active: { eq: 0 } } + pagination: { page: { page: 2, limit: 3 } } + ) { + nodes { + customerId + lastName + email + } + paginationInfo { + pages + current + } } - } } ``` @@ -131,21 +134,21 @@ Go to http://localhost:8000/ and try out the following queries: ```graphql { - customer( - filters: { active: { eq: 0 } } - pagination: { cursor: { limit: 3, cursor: "Int[3]:271" } } - ) { - nodes { - customerId - lastName - email - } - pageInfo { - hasPreviousPage - hasNextPage - endCursor + customer( + filters: { active: { eq: 0 } } + pagination: { cursor: { limit: 3, cursor: "Int[3]:271" } } + ) { + nodes { + customerId + lastName + email + } + pageInfo { + hasPreviousPage + hasNextPage + endCursor + } } - } } ``` @@ -155,57 +158,58 @@ Find all inactive customers, include their address, and their payments with amou ```graphql { - customer( - filters: { active: { eq: 0 } } - pagination: { cursor: { limit: 3, cursor: "Int[3]:271" } } - ) { - nodes { - customerId - lastName - email - address { - address - } - payment( - filters: { amount: { gt: "7" } } - orderBy: { amount: ASC } - pagination: { page: { limit: 1, page: 1 } } - ) { + customer( + filters: { active: { eq: 0 } } + pagination: { cursor: { limit: 3, cursor: "Int[3]:271" } } + ) { nodes { - paymentId - amount - } - paginationInfo { - pages - current + customerId + lastName + email + address { + address + } + payment( + filters: { amount: { gt: "7" } } + orderBy: { amount: ASC } + pagination: { page: { limit: 1, page: 1 } } + ) { + nodes { + paymentId + amount + } + paginationInfo { + pages + current + } + pageInfo { + hasPreviousPage + hasNextPage + } + } } pageInfo { - hasPreviousPage - hasNextPage + hasPreviousPage + hasNextPage + endCursor } - } } - pageInfo { - hasPreviousPage - hasNextPage - endCursor - } - } } ``` ### Filter using enumeration + ```graphql { - film( - filters: { rating: { eq: NC17 } } - pagination: { page: { page: 1, limit: 5 } } - ) { - nodes { - filmId - rating + film( + filters: { rating: { eq: NC17 } } + pagination: { page: { page: 1, limit: 5 } } + ) { + nodes { + filmId + rating + } } - } } ``` @@ -233,4 +237,4 @@ cargo run Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -Seaography is a community driven project. We welcome you to participate, contribute and together build for Rust's future. +Seaography is a community driven project. We welcome you to participate, contribute and together build for Rust's future. \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index e0da13c3..f24dcd7a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,5 +1,4 @@ use async_graphql::{ - context, dataloader::DataLoader, dynamic::{ Enum, Field, FieldFuture, InputObject, Interface, Object, Schema, SchemaBuilder, TypeRef, @@ -9,8 +8,8 @@ use async_graphql::{ use sea_orm::{ActiveEnum, ActiveModelTrait, EntityTrait, IntoActiveModel, RelationDef}; use crate::{ - entity_object, order_enum, ActiveEnumBuilder, ActiveEnumFilterInputBuilder, BuilderContext, - CascadeInputBuilder, ConnectionObjectBuilder, CursorInputBuilder, EdgeObjectBuilder, + ActiveEnumBuilder, ActiveEnumFilterInputBuilder, BuilderContext, CascadeInputBuilder, + ConnectionObjectBuilder, CursorInputBuilder, EdgeObjectBuilder, EntityCreateBatchMutationBuilder, EntityCreateOneMutationBuilder, EntityDeleteMutationBuilder, EntityGetFieldBuilder, EntityInputBuilder, EntityObjectBuilder, EntityQueryFieldBuilder, EntityUpdateMutationBuilder, FilterInputBuilder, FilterTypesMapHelper, NewOrderInputBuilder, diff --git a/src/inputs/cascade_input.rs b/src/inputs/cascade_input.rs index 83325778..4371aa91 100644 --- a/src/inputs/cascade_input.rs +++ b/src/inputs/cascade_input.rs @@ -1,4 +1,4 @@ -use async_graphql::dynamic::{InputObject, InputValue, ObjectAccessor, TypeRef, ValueAccessor}; +use async_graphql::dynamic::{InputObject, InputValue, ObjectAccessor, TypeRef}; use sea_orm::{EntityTrait, Iterable, JoinType, QuerySelect, RelationTrait}; use crate::{BuilderContext, CascadeBuilder, EntityObjectBuilder, FilterInputBuilder}; @@ -129,4 +129,4 @@ impl CascadeInputBuilder { T::find() } } -} +} \ No newline at end of file diff --git a/src/inputs/entity_input.rs b/src/inputs/entity_input.rs index 123dec7a..9981c2c9 100644 --- a/src/inputs/entity_input.rs +++ b/src/inputs/entity_input.rs @@ -100,9 +100,8 @@ impl EntityInputBuilder { Some(_) => T::PrimaryKey::auto_increment(), None => false, }; - let has_default_expr = column_def.get_column_default().is_some(); - let is_insert_not_nullable = - is_insert && !(column_def.is_null() || auto_increment || has_default_expr); + //let has_default_expr = column_def.get_column_default().is_some(); + let is_insert_not_nullable = is_insert && !(column_def.is_null() || auto_increment); let graphql_type = match types_map_helper.sea_orm_column_type_to_graphql_type( column_def.get_column_type(), @@ -167,4 +166,4 @@ impl EntityInputBuilder { Ok(map) } -} +} \ No newline at end of file diff --git a/src/query/entity_query_field.rs b/src/query/entity_query_field.rs index 8c39b399..26bc9d4c 100644 --- a/src/query/entity_query_field.rs +++ b/src/query/entity_query_field.rs @@ -3,16 +3,15 @@ use async_graphql::{ Error, }; use heck::{ToLowerCamelCase, ToSnakeCase}; -use itertools::Itertools; use sea_orm::{ - DatabaseConnection, EntityTrait, Iterable, JoinType, QueryFilter, QuerySelect, RelationTrait, + DatabaseConnection, EntityTrait, QueryFilter }; #[cfg(not(feature = "offset-pagination"))] use crate::ConnectionObjectBuilder; use crate::{ apply_order, apply_pagination, get_filter_conditions, get_first, BuilderContext, - CascadeBuilder, CascadeInputBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, + CascadeInputBuilder, EntityObjectBuilder, FilterInputBuilder, GuardAction, NewOrderInputBuilder, OrderInputBuilder, PaginationInputBuilder, }; @@ -153,8 +152,8 @@ impl EntityQueryFieldBuilder { let pagination = PaginationInputBuilder { context }.parse_object(pagination); let pagination = get_first(first, pagination); - let cascades = ctx.args.get("cascade"); - let cascades = get_cascade_conditions(cascades); + let _cascades = ctx.args.get("cascade"); + let _cascades = get_cascade_conditions(_cascades); //let stmt = // CascadeInputBuilder { context }.parse_object::(context, cascades); From e23746ef6babffb2fa683e5c528e21d7a113a9a1 Mon Sep 17 00:00:00 2001 From: Heikel Date: Mon, 20 Jan 2025 00:07:15 +0100 Subject: [PATCH 22/22] fix: map in connection object --- src/outputs/connection_object.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/outputs/connection_object.rs b/src/outputs/connection_object.rs index 87b77e35..22b641ad 100644 --- a/src/outputs/connection_object.rs +++ b/src/outputs/connection_object.rs @@ -110,7 +110,7 @@ impl ConnectionObjectBuilder { if let Some(value) = connection .pagination_info .as_ref() - .map(FieldValue::borrowed_any) + .map(|info| FieldValue::borrowed_any(info)) { Ok(Some(value)) } else { @@ -147,4 +147,4 @@ impl ConnectionObjectBuilder { }, )) } -} +} \ No newline at end of file