From e86172cca39ef4963cf0b3d845136125f2744c27 Mon Sep 17 00:00:00 2001 From: Heikel Bouzayene Date: Wed, 11 Sep 2024 17:26:02 +0100 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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